函数上下文
在函数内部,this 的值取决于函数如何被调用。可以将 this 看作是函数的一个隐藏参数(就像函数定义中声明的参数一样),this 是语言在函数体被执行时为你创建的绑定。
对于典型的函数,this 的值是函数被访问的对象。换句话说,如果函数调用的形式是 obj.f(),那么 this 就指向 obj。例如:
jsfunction getThis() {
return this;
}
const obj1 = { name: "obj1" };
const obj2 = { name: "obj2" };
obj1.getThis = getThis;
obj2.getThis = getThis;
console.log(obj1.getThis()); // { name: 'obj1', getThis: [Function: getThis] }
console.log(obj2.getThis()); // { name: 'obj2', getThis: [Function: getThis] }
注意,虽然函数是相同的,但是根据其调用的方式,this 的值是不同的。这与函数参数的工作方式类似。
this 的值不是拥有此函数作为自己属性的对象,而是用于调用此函数的对象。你可以通过调用对象在原型链中的方法来证明这一点。
jsconst obj3 = {
__proto__: obj1,
name: "obj3",
};
console.log(obj3.getThis()); // { name: 'obj3' }
this 的值总是根据调用函数的方式而改变,即使函数是在创建对象时定义的:
jsconst obj4 = {
name: "obj4",
getThis() {
return this;
},
};
const obj5 = { name: "obj5" };
obj5.getThis = obj4.getThis;
console.log(obj5.getThis()); // { name: 'obj5', getThis: [Function: getThis] }
如果方法被访问的值是一个原始值,this 也将是一个原始值——但只有当函数处于严格模式下会如此。
jsfunction getThisStrict() {
"use strict"; // 进入严格模式
return this;
}
// 仅用于演示——你不应该改变内置的原型对象
Number.prototype.getThisStrict = getThisStrict;
console.log(typeof (1).getThisStrict()); // "number"
如果函数在没有被任何东西访问的情况下被调用,this 将是 undefined——但只有在函数处于严格模式下会如此。
jsconsole.log(typeof getThisStrict()); // "undefined"
在非严格模式下,一个特殊的过程称为 this 替换确保 this 的值总是一个对象。这意味着:
如果一个函数被调用时 this 被设置为 undefined 或 null,this 会被替换为 globalThis。
如果函数被调用时 this 被设置为一个原始值,this 会被替换为原始值的包装对象。
jsfunction getThis() {
return this;
}
// 仅用于演示——你不应该修改内置的原型对象
Number.prototype.getThis = getThis;
console.log(typeof (1).getThis()); // "object"
console.log(getThis() === globalThis); // true
在典型的函数调用中,this 是通过函数的前缀(点之前的部分)隐式传递的,就像一个参数。你也可以使用 Function.prototype.call()、Function.prototype.apply() 或 Reflect.apply() 方法显式设置 this 的值。使用 Function.prototype.bind(),你可以创建一个新的函数,无论函数如何被调用,其 this 的值都不会改变。当使用这些方法时,如果函数是在非严格模式下,上述 this 替换规则仍然适用。
回调
当一个函数作为回调函数传递时,this 的值取决于如何调用回调,这由 API 的实现者决定。回调函数通常以 undefined 作为 this 的值被调用(直接调用,而不附加到任何对象上),这意味着如果函数是在非严格模式,this 的值会是全局对象(globalThis)。这在迭代数组方法、Promise() 构造函数等例子中都是适用的。
jsfunction logThis() {
"use strict";
console.log(this);
}
[1, 2, 3].forEach(logThis); // undefined、undefined、undefined
一些 API 允许你为回调函数的调用设置一个 this 值。例如,所有的迭代数组方法和相关的方法,如Set.prototype.forEach(),都接受一个可选的 thisArg 参数。
js[1, 2, 3].forEach(logThis, { name: "obj" });
// { name: 'obj' }, { name: 'obj' }, { name: 'obj' }
偶尔,回调函数会以一个非 undefined 的 this 值被调用。例如,JSON.parse() 的 reviver 参数和 JSON.stringify() 的 replacer 参数都会把 this 设置为正在被解析/序列化的属性所属的对象。
箭头函数
在箭头函数中,this 保留了闭合词法上下文的 this 值。换句话说,当对箭头函数求值时,语言不会创建一个新的 this 绑定。
例如,在全局代码中,无论是否在严格模式下,由于全局上下文绑定,this 值总是 globalThis。
jsconst globalObject = this;
const foo = () => this;
console.log(foo() === globalObject); // true
箭头函数在其周围的作用域上创建一个 this 值的闭包,这意味着箭头函数的行为就像它们是“自动绑定”的——无论如何调用,this 都绑定到函数创建时的值(在上面的例子中,是全局对象)。在其他函数内部创建的箭头函数也是如此:它们的 this 值保持为闭合词法上下文的 this。参见下面的例子。
此外,当使用 call()、bind() 或 apply() 调用箭头函数时,thisArg 参数会被忽略。不过,你仍然可以使用这些方法传递其他参数。
jsconst obj = { name: "obj" };
// 尝试使用 call 设置 this
console.log(foo.call(obj) === globalObject); // true
// 尝试使用 bind 设置 this
const boundFoo = foo.bind(obj);
console.log(boundFoo() === globalObject); // true
构造函数
当一个函数被用作构造函数(使用 new 关键字)时,无论构造函数是在哪个对象上被访问的,其 this 都会被绑定到正在构造的新对象上。除非构造函数返回另一个非原始值,不然 this 的值会成为 new 表达式的值。
jsfunction C() {
this.a = 37;
}
let o = new C();
console.log(o.a); // 37
function C2() {
this.a = 37;
return { a: 38 };
}
o = new C2();
console.log(o.a); // 38
在第二个例子(C2)中,因为在构造过程中返回了一个对象,this 被绑定的新对象被丢弃。(这基本上使得语句 this.a = 37; 成为了死代码。它并不完全是死代码,因为它被执行了,但是它可以被消除而不产生任何外部效果。)
super
当一个函数以 super.method() 的形式被调用时,method 函数内的 this 与 super.method() 调用周围的 this 值相同,通常不等于 super 所指向的对象。这是因为 super.method 不是像上面的对象成员访问——它是一种特殊的语法,有不同的绑定规则。有关示例,请参见 super 参考。