this
2020 javascript当一个函数被调用时,会创建一个执行上下文。这个上下文会包含函数的调用栈,函数的调用方式,传入参数等信息。this 就是这个上下文的一个属性,会在函数的执行过程中用到。
误解
1. 指向函数自身
人们容易把 this 理解成指向函数自身。
function foo(num) {
this.count++;
}
foo.count = 0;
for (var i = 0; i < 5; i++) {
foo(i);
}
foo.count; // 0
count; // NaN
执行 foo.count = 0 时,函数对象 foo 添加了一个属性 count。但是函数内部代码 this.count 中的 this 并不是指向那个函数对象,虽然属性名相同,根对象并不相同,代码中无意中创建了一个全局变量 count,他的值为 NaN。
可以使用 call(..) 确保 this 指向函数对象 foo 本身,foo.call(foo, i);
2. 函数的作用域
this 在任何情况下都不指向函数的词法作用域。
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log(this.a);
}
foo(); // ReferenceError: a is not defined
代码试图通过 this.bar() 来引用 bar() 函数,这样调用成功纯属意外,调用 bar() 最自然的办法是省略前面的 this,直接使用词法引用标识符。这段代码还试图使用 this 联通 foo() 和 bar() 的词法作用域,从而让 bar() 可以访问 foo() 作用域里的变量 a,这是不可能的。
绑定规则
1. 默认绑定
在独立函数调用的时候,就会使用默认绑定,this 会指向全局对象。
如果使用严格模式,则不能讲全局对象用于默认绑定,因此 this 会绑定到 undefined
function foo() {
console.log(this.a);
}
var a = 2;
foo(); // 2
2. 隐式绑定
判断调用位置是否有上下文对象
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo,
};
obj.foo(); // 2
我们必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把 this 间接(隐式)绑定到这个对象上。
3. 显式绑定
显示绑定主要是用到了 call(..) 和 apply(..) 方法。
4. new 绑定
在 JavaScript 中,构造函数只是一些使用 new 操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上,他们甚至都不能说是一种特殊的函数类型,它们只是被 new 操作符调用的普通函数而已。
使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:
- 创建(或者说构造)一个全新的对象。
- 这个新对象会被执行 [[Prototype]] 连接。
- 这个新对象会绑定到函数调用的 this。
- 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
function foo(a) {
this.a = a;
}
var bar = new foo(2);
bar.a; // 2
优先级
new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
箭头函数
箭头函数不会创建自己的 this ,它只会从自己的作用域链的上一层继承 this。所以箭头函数中的 this 是在定义函数的时候绑定,而不是在执行函数的时候绑定。
var foo = 'foo';
var bar = {
foo: 'bar',
log: () => {
console.log(this.foo);
},
};
bar.log(); // 'foo'
通过 https://babeljs.io/repl 编译过程,我们发现箭头函数会被转成 console.log((void 0).foo)
其他
setTimeout 中所执行函数中的 this,永远指向 window,严格模式指向 undefined。
题目
const obj = {
msg: 'foo',
getMsg() {
const msg = 'bar',
return this.msg;
}
}
obj.getMsg();
obj.getMsg() 是一个方法调用,调用该方法的上下文是 obj,所有 this 是指 obj。 答案是foo
,可以参考误解 1。
var length = 4;
function cb() {
console.log(this.length);
}
const obj = {
length: 5,
method(cb) {
cb();
},
};
obj.method(cb, 1, 2);
cb() 是在 method() 内部使用常规函数调用来调用的。由于在常规函数调用期间的 this 值等于 window。答案是 4
。
var length = 4;
function cb() {
console.log(this.length);
}
const obj = {
length: 5,
method() {
arguments[0]();
},
};
obj.method(cb, 1, 2);
obj.method(cb, 1, 2) 被调用时有 3 个参数。arguments[0]()
是 arguments 对象上的回调的方法调用,所以回调内部的参数等于 arguments。所以 callback() 中的 this.length 与 arguments.length 相同,答案是 3
。
参考链接
- 《你不知道的 JavaScript(上卷)》
- 7 个关于”this”面试题,你能回答上来吗?