在 JavaScript 中确定 this
的值可能很棘手,请按以下步骤操作…
JavaScript 的 this
经常被拿来开玩笑,原因在于它非常复杂。不过,我见过开发者为了避免处理此 this
,而执行更复杂且特定于领域的操作。如果您对 this
不确定,希望这对您有所帮助。这是我的 this
指南。
我将从最具体的情况开始,以最不具体的情况结束。本文有点像一个大 if (…) … else if () … else if (…) …
,因此您可以直接跳转到与您正在查看的代码匹配的第一部分。
- 如果函数定义为箭头函数
- 否则,如果使用
new
调用函数/类 - 否则,如果函数具有“绑定”
this
值 - 否则,如果在通话时设置了
this
- 否则,如果通过父级对象 (
parent.func()
) 调用函数 - 否则,如果函数或父级作用域处于严格模式
- 否则
如果函数定义为箭头函数:
const arrowFunction = () => {
console.log(this);
};
在本例中,this
的值始终与父作用域中的 this
相同:
const outerThis = this;
const arrowFunction = () => {
// Always logs `true`:
console.log(this === outerThis);
};
箭头函数非常棒,因为 this
的内部值无法更改,它始终与外部 this
相同。
其他示例
使用箭头函数时,无法使用 bind
更改 this
的值:
// Logs `true` - bound `this` value is ignored:
arrowFunction.bind({foo: 'bar'})();
使用箭头函数时,无法使用 call
或 apply
更改 this
的值:
// Logs `true` - called `this` value is ignored:
arrowFunction.call({foo: 'bar'});
// Logs `true` - applied `this` value is ignored:
arrowFunction.apply({foo: 'bar'});
使用箭头函数时,不能通过将该函数作为另一个对象的成员来调用来更改 this
的值:
const obj = {arrowFunction};
// Logs `true` - parent object is ignored:
obj.arrowFunction();
对于箭头函数,无法通过将函数作为构造函数调用来更改 this
的值:
// TypeError: arrowFunction is not a constructor
new arrowFunction();
“绑定”实例方法
对于实例方法,如果您想确保 this
始终引用类实例,最好的方法是使用箭头函数和类字段:
class Whatever {
someMethod = () => {
// Always the instance of Whatever:
console.log(this);
};
}
在组件(如 React 组件或 Web 组件)中将实例方法用作事件监听器时,此模式非常有用。
上面的代码可能看起来违反了“this
将与父级作用域中的 this
相同”规则,但如果您将类字段视为在构造函数中设置内容的语法糖,就会发现它开始变得有意义:
class Whatever {
someMethod = (() => {
const outerThis = this;
return () => {
// Always logs `true`:
console.log(this === outerThis);
};
})();
}
// …is roughly equivalent to:
class Whatever {
constructor() {
const outerThis = this;
this.someMethod = () => {
// Always logs `true`:
console.log(this === outerThis);
};
}
}
其他模式包括在构造函数中绑定现有函数,或在构造函数中分配函数。如果您因某种原因而无法使用类字段,则在构造函数中分配函数是一个合理的替代方案:
class Whatever {
constructor() {
this.someMethod = () => {
// …
};
}
}
否则,如果使用 new
调用函数/类:
new Whatever();
上面的代码会调用 Whatever
(如果是类,则调用其构造函数函数),并将 this
设置为 Object.create(Whatever.prototype)
的结果。
class MyClass {
constructor() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
}
// Logs `true`:
new MyClass();
旧版构造函数也是如此:
function MyClass() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
// Logs `true`:
new MyClass();
其他示例
使用 new
调用时,this
的值不能使用 bind
更改:
const BoundMyClass = MyClass.bind({foo: 'bar'});
// Logs `true` - bound `this` value is ignored:
new BoundMyClass();
使用 new
调用时,this
的值不能通过将函数作为另一个对象的成员调用来更改:
const obj = {MyClass};
// Logs `true` - parent object is ignored:
new obj.MyClass();
否则,如果函数具有“绑定”this
值:
function someFunction() {
return this;
}
const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);
每当调用 boundFunction
时,其 this
值将是传递给 bind
(boundObject
) 的对象。
// Logs `false`:
console.log(someFunction() === boundObject);
// Logs `true`:
console.log(boundFunction() === boundObject);
其他示例
调用绑定的函数时,无法使用 call
或 apply
更改 this
的值:
// Logs `true` - called `this` value is ignored:
console.log(boundFunction.call({foo: 'bar'}) === boundObject);
// Logs `true` - applied `this` value is ignored:
console.log(boundFunction.apply({foo: 'bar'}) === boundObject);
调用绑定的函数时,无法通过将函数作为其他对象的成员调用来更改 this
的值:
const obj = {boundFunction};
// Logs `true` - parent object is ignored:
console.log(obj.boundFunction() === boundObject);
否则,如果在通话时设置了 this
:
function someFunction() {
return this;
}
const someObject = {hello: 'world'};
// Logs `true`:
console.log(someFunction.call(someObject) === someObject);
// Logs `true`:
console.log(someFunction.apply(someObject) === someObject);
this
的值是传递给 call
/apply
的对象。
遗憾的是,DOM 事件监听器等会将 this
设置为其他值,使用它可能会导致代码难以理解:
element.addEventListener('click', function (event) { // Logs `element`, since the DOM spec sets `this` to // the element the handler is attached to. console.log(this); });
在上述情况下,我会避免使用 this
,而是:
element.addEventListener('click', (event) => { // Ideally, grab it from a parent scope: console.log(element); // But if you can't do that, get it from the event object: console.log(event.currentTarget); });
否则,如果通过父对象 (parent.func()
) 调用该函数:
const obj = {
someMethod() {
return this;
},
};
// Logs `true`:
console.log(obj.someMethod() === obj);
在本例中,该函数是作为 obj
的成员进行调用的,因此 this
将为 obj
。这种情况发生在调用时,因此如果在调用函数时没有其父对象或使用其他父对象,则链接会断开:
const {someMethod} = obj;
// Logs `false`:
console.log(someMethod() === obj);
const anotherObj = {someMethod};
// Logs `false`:
console.log(anotherObj.someMethod() === obj);
// Logs `true`:
console.log(anotherObj.someMethod() === anotherObj);
someMethod() === obj
为 false,因为 someMethod
未作为 obj
的成员被调用。在尝试以下操作时,您可能遇到了以下问题:
const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');
这是因为 querySelector
的实现会查看自己的 this
值,并希望它是某种 DOM 节点,而上述代码会破坏这种关联。要正确实现上述目标,请执行以下操作:
const $ = document.querySelector.bind(document);
// Or:
const $ = (...args) => document.querySelector(...args);
趣味小知识:并非所有 API 在内部使用 this
。更改了 console.log
等控制台方法以避免 this
引用,因此 log
无需绑定到 console
。
否则,如果函数或父作用域处于严格模式:
function someFunction() {
'use strict';
return this;
}
// Logs `true`:
console.log(someFunction() === undefined);
在本例中,this
的值未定义。如果父作用域处于严格模式(并且所有模块都处于严格模式),则无需在函数中使用 'use strict'
。
否则:
function someFunction() {
return this;
}
// Logs `true`:
console.log(someFunction() === globalThis);
在本例中,this
的值与 globalThis
相同。
呼!
这样就大功告成了!这就是我对 this
的全部了解。有问题吗?我是否遗漏了什么?欢迎随时给我发 Twitter 微博。
感谢 Mathias Bynens、Ingvar Stepanyan 和 Thomas Steiner 的审核。