git文章
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: 函数形参与arguments
|
||||
date: 2018-4-14 00:28:25
|
||||
tags:
|
||||
- JavaScript
|
||||
- 函数
|
||||
categories:
|
||||
- JavaScript
|
||||
---
|
||||
|
||||
在之前我们知道 , 在JS当中函数的形参与调用时传入的实参并不需要必须对应
|
||||
在函数的内部有一个对象`arguments` , 这是一个类数组 , 其中包含调用时传入的实参
|
||||
当然函数在调用的时候 , 会按照形参的位置给形参赋值
|
||||
<!-- more -->
|
||||
但是如果我们在函数内部对形参进行赋值 , 或者对arguments当中的元素进行赋值 , 会出现怎样的情况呢
|
||||
|
||||
这在一般模式和严格模式下会有不同的行为
|
||||
### 一般模式
|
||||
调用时对于没有对应到实参的形参进行赋值
|
||||
```javascript
|
||||
function func1(arg0, arg1) {
|
||||
arg1 = 10;
|
||||
console.log(arg1, arguments[1]);
|
||||
}
|
||||
func1(19);// 10 undefined
|
||||
|
||||
function func2(arg0, arg1) {
|
||||
arguments[1] = 10;
|
||||
console.log(arg1, arguments[1]);
|
||||
}
|
||||
func2(21);// undefined 10
|
||||
```
|
||||
上面两个函数都有两个形参 , 实际调用时传入了一个实参 , **arg1**是没有与实参对应的
|
||||
这时候对形参赋值 , 或者对arguments当中对应的元素赋值 , 各自的行为都是**独立**的 , 彼此不会产生影响
|
||||
|
||||
形参与实参有对应的情况
|
||||
```javascript
|
||||
function func1(arg0) {
|
||||
arg0 = 10;
|
||||
console.log(arg0, arguments[0]);
|
||||
}
|
||||
func1(19);// 10 10
|
||||
|
||||
function func2(arg0) {
|
||||
arguments[0] = 10;
|
||||
console.log(arg0, arguments[0]);
|
||||
}
|
||||
func2(21);// 10 10
|
||||
```
|
||||
可以看到 , 在这种情况下 , 形参与arguments当中的元素是双向绑定的 , 修改一个会自动影响另一个
|
||||
|
||||
### 严格模式
|
||||
在严格模式下同样执行与上面类似的代码
|
||||
```javascript
|
||||
"use strict";
|
||||
function func1(arg0) {
|
||||
arg0 = 10;
|
||||
console.log(arg0, arguments[0]);
|
||||
}
|
||||
func1(19);// 10 19
|
||||
|
||||
function func2(arg0) {
|
||||
arguments[0] = 10;
|
||||
console.log(arg0, arguments[0]);
|
||||
}
|
||||
func2(21);// 21 10
|
||||
|
||||
function func3(arg0) {
|
||||
arguments[0] = 10;
|
||||
console.log(arg0, arguments[0]);
|
||||
}
|
||||
func3();// undefined 10
|
||||
|
||||
function func4(arg0) {
|
||||
arg0 = 10;
|
||||
console.log(arg0, arguments[0]);
|
||||
}
|
||||
func4();// 10 undefined
|
||||
```
|
||||
可见严格模式之下 , 无论是哪种情况 , arguments与形参都是独立的 , 不会进行绑定 , 之间互不影响
|
||||
@@ -0,0 +1,114 @@
|
||||
---
|
||||
title: 变量的定义提升
|
||||
date: 2018-4-14 00:30:18
|
||||
tags:
|
||||
- JavaScript
|
||||
- 函数
|
||||
categories:
|
||||
- JavaScript
|
||||
---
|
||||
|
||||
|
||||
从一道笔试题说起
|
||||
```javascript
|
||||
function Foo() {
|
||||
getName = function () { console.log (1); };
|
||||
return this;
|
||||
}
|
||||
Foo.getName = function () { console.log (2);};
|
||||
Foo.prototype.getName = function () { console.log (3);};
|
||||
var getName = function () { console.log (4);};
|
||||
function getName() { console.log (5);}
|
||||
|
||||
//请写出以下输出结果:
|
||||
Foo.getName();
|
||||
getName();
|
||||
Foo().getName();
|
||||
getName();
|
||||
new Foo.getName();
|
||||
new Foo().getName();
|
||||
new new Foo().getName();
|
||||
```
|
||||
<!-- more -->
|
||||
#### 第一问
|
||||
Foo是一个函数对象 , `Foo.getName = … `在其中定义了一个名为getName的属性 , 也是一个函数
|
||||
所以第一问当中的调用输出的应该是2
|
||||
|
||||
#### 第二问
|
||||
这里涉及到了变量的定义提升问题 , 如果不了解容易误认为答案是5
|
||||
例如如下语句
|
||||
```javascript
|
||||
console.log("x" in window);
|
||||
var x;
|
||||
```
|
||||
虽然变量的定义是在第二个语句 , 但是输出的结果仍然是true
|
||||
因为代码执行时 JS引擎会把变量的声明语句提升到最上方
|
||||
但是提升的仅仅是变量声明 , 如果声明变量的时候包含初始化
|
||||
则会将初始化作为单纯的一条赋值语句保留在原处
|
||||
例如
|
||||
```javascript
|
||||
console.log("x" in window);
|
||||
var x = 10;
|
||||
```
|
||||
在JS引擎处理之后会改变为
|
||||
```javascript
|
||||
var x;
|
||||
console.log("x" in window);
|
||||
x = 10;
|
||||
```
|
||||
> 补充说明 : 只有显式的变量定义才会被提升
|
||||
> 隐式的变量定义并不会
|
||||
> 比如
|
||||
```
|
||||
console.log("x" in window);//false
|
||||
x = 10;
|
||||
```
|
||||
> 输出就是false , 因为x未定义
|
||||
|
||||
全局函数的定义与全局变量的定义本质是一样的
|
||||
所以它也会被提升到顶部
|
||||
但是 `var x = function(){}`与`function x(){}`两种定义方式最终效果相同
|
||||
执行过程却是不同的
|
||||
```javascript
|
||||
console.log(x);//function x(){}
|
||||
function x(){}
|
||||
console.log(x);//function x(){}
|
||||
```
|
||||
或者
|
||||
```javascript
|
||||
console.log(x);//undefined
|
||||
var x = function(){}
|
||||
console.log(x);//function x(){}
|
||||
```
|
||||
那么具体到这个题目
|
||||
代码
|
||||
```javascript
|
||||
var getName = function () { console.log (4);};
|
||||
function getName() { console.log (5);}
|
||||
```
|
||||
会被JS引擎在解析时修改为
|
||||
```javascript
|
||||
var getName;
|
||||
function getName() { console.log (5);}
|
||||
getName = function () { console.log (4);};
|
||||
```
|
||||
显然 , 第三行的赋值会将第二行的定义覆盖掉
|
||||
最终这个全局的getName函数输出的值是4
|
||||
|
||||
#### 第三问
|
||||
Foo() 是在全局的基础上调用的 , 也就是由window对象调用的
|
||||
所以return this返回的是window对象
|
||||
但是在这个函数的内部 , 修改了全局的getName的值
|
||||
现在名为getName的全局函数输出是1了
|
||||
所以再调用window.getName() 的输出当然是1
|
||||
|
||||
#### 第四问
|
||||
同样是调用全局的getName函数 , 所以结果和第三问一样 , 也是1
|
||||
|
||||
#### 第五问
|
||||
成员访问的点号( . )优先级高于 new , 所以结果与第一问相同 , 也是2
|
||||
|
||||
#### 第六问
|
||||
括号运算符的优先级高于点号( . )
|
||||
实际执行为 `( new Foo( ) ).getName()`
|
||||
所以调用的是原型对象上的getName函数 , 输出是3
|
||||
@@ -0,0 +1,87 @@
|
||||
---
|
||||
title: 实现bind方法
|
||||
date: 2018-4-14 00:35:19
|
||||
tags:
|
||||
- JavaScript
|
||||
- prototype
|
||||
categories:
|
||||
- JavaScript
|
||||
---
|
||||
|
||||
|
||||
bind方法来自于`Function.prototype`
|
||||
这个方法会创建一个新函数 , 当这个函数被调用时 , 第一个参数将会作为它运行时的this , 之后的参数会作为实际调用时传递的实参前作为实参
|
||||
<!-- more -->
|
||||
语法
|
||||
<pre>
|
||||
func.bind(thisArg [,arg1[,arg2[, ...]]])
|
||||
</pre>
|
||||
**应用示例 :**
|
||||
```javascript
|
||||
var obj = {
|
||||
name : "Sookie",
|
||||
show : function() {
|
||||
console.log(this.name);
|
||||
console.log(arguments);
|
||||
}
|
||||
}
|
||||
var _show = obj.show.bind(obj,"aa","bb");
|
||||
_show("cc");
|
||||
/*output:
|
||||
Sookie
|
||||
{ '0': 'aa', '1': 'bb', '2': 'cc' }
|
||||
*/
|
||||
```
|
||||
如果要实现一个bind方法 , 需要用到柯里化
|
||||
```javascript
|
||||
Function.prototype.bind = function(context) {
|
||||
var me = this;//调用bind的函数对象
|
||||
var args = Array.prototype.slice.call(arguments, 1);//传入的固定实参
|
||||
return function () {
|
||||
//实际调用时传入的参数
|
||||
var innerArgs = Array.prototype.slice.call(arguments);
|
||||
//合并两个参数数组
|
||||
var finalArgs = args.concat(innerArgs);
|
||||
//调用该函数
|
||||
return me.apply(context, finalArgs);
|
||||
}
|
||||
}
|
||||
```
|
||||
这里解释一下 , arguments并不是数组对象 , 而是**类数组**
|
||||
+ 具有 : 指向对象元素的数字索引 , 以及length属性
|
||||
+ 不具有 : push , slice等数组对象的方法
|
||||
|
||||
> 数组对象的\_\_proto\_\_是Array
|
||||
而arguments的 \_\_proto\_\_是Object
|
||||
|
||||
如果要把一个arguments转化为一个数组对象 , 可以使用如下方式
|
||||
<pre>
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
</pre>
|
||||
|
||||
#### 嗅探
|
||||
在运行时如果对标准库当中的内容有修改
|
||||
( 可能是出于兼容性的需要 , 比如旧版本的标准库当中无此函数 )
|
||||
```javascript
|
||||
Function.prototype.bind = Function.prototype.bind || function(context) {
|
||||
//...
|
||||
}
|
||||
```
|
||||
这属于一个加分项
|
||||
由于标准库当中的函数多是经过了深度优化的
|
||||
一般要比我们自己写的函数效率更高 更健壮
|
||||
所以如果标准库中存在 , 其实没有必要用我们自己写的方法去替换
|
||||
这里就是进行`嗅探`
|
||||
> 这是一个典型的 `Monkey patch(猴子补丁)`
|
||||
> 主要有以下几个用处
|
||||
> + 在运行时替换方法 属性等
|
||||
> + 在不修改第三方代码的情况下增加原来不支持的功能
|
||||
> + 在运行时为内存中的对象增加patch而不是在磁盘的源代码中增加
|
||||
|
||||
#### 更严谨的做法
|
||||
由于需要调用bind方法的一定是一个函数 , 所以有必要在bind的内部做一个校验
|
||||
```javascript
|
||||
if(typeof this !== "function") {
|
||||
throw new TypeError("what is trying to be bound is not callable");
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,86 @@
|
||||
---
|
||||
title: 实现call方法
|
||||
date: 2018-4-14 00:56:44
|
||||
tags:
|
||||
- JavaScript
|
||||
- prototype
|
||||
categories:
|
||||
- JavaScript
|
||||
---
|
||||
|
||||
> call方法在使用一个指定的this值和若干个指定参数值的前提下调用某个函数
|
||||
<!-- more -->
|
||||
常规调用方式
|
||||
```javascript
|
||||
var obj = {
|
||||
name : "Sookie",
|
||||
func : function(msg) {
|
||||
console.log(this.name + " : " + msg);
|
||||
}
|
||||
}
|
||||
var fn1 = obj.func;
|
||||
fn1.call(obj, "Hello");
|
||||
```
|
||||
|
||||
现在如果要尝试实现一个call方法
|
||||
|
||||
#### 雏形
|
||||
```javascript
|
||||
Function.prototype.call2 = function(context) {
|
||||
context.fn = this;
|
||||
context.fn();
|
||||
delete context.fn;
|
||||
}
|
||||
```
|
||||
初步实现了不传参情况下的函数调用
|
||||
但是还有不少缺陷
|
||||
|
||||
#### 对参数的处理
|
||||
|
||||
由于需要处理实际参数 , 而且实际参数的数量又是不确定的
|
||||
所以我们可以考虑使用`eval`来执行这个函数
|
||||
|
||||
```javascript
|
||||
Function.prototype.call2 = function(context) {
|
||||
context.fn = this;
|
||||
var args = [];
|
||||
for(let i=1 ; i<arguments.length ; i++) {
|
||||
args.push("arguments[" + i + "]");
|
||||
}
|
||||
eval("context.fn(" + args + ")");
|
||||
delete context.fn;
|
||||
};
|
||||
```
|
||||
比如实际调用函数的时候需要传入2个参数 , 包括前面的context , 所以arguments当中就有3个元素
|
||||
|
||||
那么就相当于执行
|
||||
context.fn(arguments[1], arguments[2])
|
||||
|
||||
#### 返回值与完善
|
||||
|
||||
还需要处理函数的返回值
|
||||
另外 , 在我们的call方法当中 , 对上下文对象添加了fn属性 , 最后又删除了它
|
||||
如果这个对象上原本就有这个属性 , 那么就对这个对象产生了破坏
|
||||
可以预先把这个对象保存下来
|
||||
```javascript
|
||||
Function.prototype.call2 = function(context) {
|
||||
var _fn = null;
|
||||
var flag = false;
|
||||
if("fn" in context) {
|
||||
_fn = context.fn;
|
||||
}
|
||||
_fn = context.fn;
|
||||
context.fn = this;
|
||||
var args = [];
|
||||
for(let i=1 ; i<arguments.length ; i++) {
|
||||
args.push("arguments[" + i + "]");
|
||||
}
|
||||
var result = eval("context.fn(" + args + ")");
|
||||
if(flag) {
|
||||
context.fn = _fn;
|
||||
} else {
|
||||
delete context.fn;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
```
|
||||
Reference in New Issue
Block a user