JavaScript:这是什么意思?
在 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
调用时,无法用bind
改变this
的值:
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
的对象。
不幸的是, this
被诸如 DOM 事件侦听器之类的东西设置为其他一些值,并且使用它可能会导致难以理解的代码:
错误做法
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
的一切。有任何疑问?我漏掉了什么?请随时给我发推文。
感谢 Mathias Bynens 、Ingvar Stepanyan 和 Thomas Steiner的校对。