如何在回调中访问正确的 this
- 2024-11-02 21:00:00
- admin 原创
- 35
问题描述:
我有一个注册事件处理程序的构造函数:
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', function () {
alert(this.data);
});
}
// Mock transport object
var transport = {
on: function(event, callback) {
setTimeout(callback, 1000);
}
};
// called as
var obj = new MyConstructor('foo', transport);
运行代码片段Hide results展开片段
但是,我无法data
在回调中访问已创建对象的属性。它似乎this
不引用已创建的对象,而是引用另一个对象。
我还尝试使用对象方法而不是匿名函数:
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', this.alert);
}
MyConstructor.prototype.alert = function() {
alert(this.name);
};
但它也存在同样的问题。
我如何才能访问正确的对象?
解决方案 1:
您应该了解的内容this
this
(又名“上下文”)是每个函数内的一个特殊关键字,其值仅取决于函数的调用方式,而不是定义方式/时间/地点。它不受词法作用域的影响,就像其他变量一样(箭头函数除外,见下文)。以下是一些示例:
function foo() {
console.log(this);
}
// normal function call
foo(); // `this` will refer to `window`
// as object method
var obj = {bar: foo};
obj.bar(); // `this` will refer to `obj`
// as constructor function
new foo(); // `this` will refer to an object that inherits from `foo.prototype`
要了解更多信息this
,请查看MDN 文档。
如何引用正确的this
使用箭头函数
ECMAScript 6 引入了箭头函数,可以将其视为 lambda 函数。它们没有自己的this
绑定。相反,this
它会像普通变量一样在作用域中查找。这意味着您不必调用.bind
。这不是它们唯一的特殊行为,请参阅 MDN 文档了解更多信息。
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => alert(this.data));
}
不要使用this
实际上,您并不想访问this
特定的对象,而是访问它所引用的对象。因此,一个简单的解决方案就是创建一个也引用该对象的新变量。变量可以有任何名称,但常见的名称是self
和that
。
function MyConstructor(data, transport) {
this.data = data;
var self = this;
transport.on('data', function() {
alert(self.data);
});
}
由于self
是普通变量,因此它遵循词法作用域规则,并且可以在回调内部访问。这还有一个优点,即您可以访问this
回调本身的值。
明确设置this
回调 - 第 1 部分
可能看起来您无法控制的值,this
因为它的值是自动设置的,但事实并非如此。
每个函数都有方法.bind
[docs],它返回一个this
绑定到值的新函数。该函数的行为与您调用的函数完全相同.bind
,只是this
由您设置。无论如何或何时调用该函数,this
将始终引用传递的值。
function MyConstructor(data, transport) {
this.data = data;
var boundFunction = (function() { // parenthesis are not necessary
alert(this.data); // but might improve readability
}).bind(this); // <- here we are calling `.bind()`
transport.on('data', boundFunction);
}
在这种情况下,我们将回调与的this
值绑定。MyConstructor
`this`
注意:当绑定 jQuery 上下文时,请改用jQuery.proxy
[docs]。这样做的原因是,在解除事件回调绑定时,您无需存储对函数的引用。jQuery 会在内部处理该问题。
this
回调函数设置- 第 2 部分
一些接受回调的函数/方法也接受回调this
应该引用的值。这基本上与自己绑定相同,但函数/方法会为您完成绑定。Array#map
[docs]就是这样一种方法。它的签名是:
array.map(callback[, thisArg])
第一个参数是回调,第二个参数是this
应该引用的值。这是一个人为的例子:
var arr = [1, 2, 3];
var obj = {multiplier: 42};
var new_arr = arr.map(function(v) {
return v * this.multiplier;
}, obj); // <- here we are passing `obj` as second argument
注意:是否可以传递值this
通常会在该函数/方法的文档中提及。例如,jQuery 的$.ajax
方法[docs]描述了一个名为的选项context
:
该对象将成为所有 Ajax 相关回调的上下文。
常见问题:使用对象方法作为回调/事件处理程序
此问题的另一个常见表现是将对象方法用作回调/事件处理程序。函数是 JavaScript 中的一等公民,术语“方法”只是函数的口语术语,该函数是对象属性的值。但该函数与其“包含”对象没有特定的链接。
请考虑以下示例:
function Foo() {
this.data = 42,
document.body.onclick = this.method;
}
Foo.prototype.method = function() {
console.log(this.data);
};
函数this.method
被指定为点击事件处理程序,但如果document.body
单击了,则记录的值将是undefined
,因为在事件处理程序中,this
引用的是document.body
,而不是的实例Foo
。
正如开头所提到的,引用this
取决于函数的调用方式,而不是定义方式。
如果代码如下所示,函数没有对对象的隐式引用可能更明显:
function method() {
console.log(this.data);
}
function Foo() {
this.data = 42,
document.body.onclick = this.method;
}
Foo.prototype.method = method;
解决方案与上面提到的相同:如果可用,使用.bind
显式绑定this
到特定值
document.body.onclick = this.method.bind(this);
或者明确地将该函数作为对象的“方法”来调用,通过使用匿名函数作为回调/事件处理程序并将对象(this
)分配给另一个变量:
var self = this;
document.body.onclick = function() {
self.method();
};
或者使用箭头函数:
document.body.onclick = () => this.method();
解决方案 2:
以下是在子上下文中访问父上下文的几种方法 -
您可以使用该
bind()
功能。将对 context/this 的引用存储在另一个变量中(参见下面的示例)。
使用 ES6箭头函数。
改变代码、功能设计和架构——为此您应该掌握JavaScript 中的设计模式。
使用
bind()
函数
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', ( function () {
alert(this.data);
}).bind(this) );
}
// Mock transport object
var transport = {
on: function(event, callback) {
setTimeout(callback, 1000);
}
};
// called as
var obj = new MyConstructor('foo', transport);
如果你使用 Underscore.js - http://underscorejs.org/#bind
transport.on('data', _.bind(function () {
alert(this.data);
}, this));
将对 context/this 的引用存储在另一个变量中
function MyConstructor(data, transport) {
var self = this;
this.data = data;
transport.on('data', function() {
alert(self.data);
});
}
3.箭头函数
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => {
alert(this.data);
});
}
解决方案 3:
这一切都在调用方法的“神奇”语法中:
object.property();
当你从对象中获取属性并一次性调用它时,对象将成为该方法的上下文。如果你调用相同的方法,但在不同的步骤中,上下文将是全局范围(窗口):
var f = object.property;
f();
当你获取方法的引用时,它不再附加到对象。它只是对普通函数的引用。当你获取引用以用作回调时,也会发生同样的情况:
this.saveNextLevelData(this.setAll);
这就是你将上下文绑定到函数的地方:
this.saveNextLevelData(this.setAll.bind(this));
如果您使用 jQuery,则应改用该$.proxy
方法,因为bind
并非所有浏览器都支持该方法:
this.saveNextLevelData($.proxy(this.setAll, this));
解决方案 4:
您 应该 了解“this”关键字。
按照我的观点,你可以通过三种方式实现“this”
(Self|Arrow function|Bind Method)
与其他语言相比,函数this
关键字在 JavaScript 中的行为略有不同。
严格模式和非严格模式也有一些区别。
大多数情况下,this 的值由函数的调用方式决定。
它不能在执行过程中通过赋值来设置,并且每次调用函数时它可能都不同。
ES5 引入了 bind() 方法来设置函数的值,this
而不管它是如何调用的,
ES2015 引入了不提供自身this
绑定的箭头函数(它保留了封闭词法上下文的 this 值)。
方法 1: Self - 即使上下文发生变化,Self 也用于维护对原始 this 的引用。这是事件处理程序中经常使用的一种技术(尤其是在闭包中)。
参考:这个
function MyConstructor(data, transport) {
this.data = data;
var self = this;
transport.on('data', function () {
alert(self.data);
});
}
方法 2:箭头函数 - 箭头函数表达式是正则函数表达式的语法紧凑的替代方法,尽管它没有与 this、arguments、super 或 new.target 关键字的绑定。
箭头函数表达式不适合用作方法,并且不能用作构造函数。
参考:箭头函数表达式
function MyConstructor(data, transport) {
this.data = data;
transport.on('data',()=> {
alert(this.data);
});
}
方法 3:绑定 - bind() 方法创建一个新函数,当调用该函数时,其this
关键字设置为提供的值,并且在调用新函数时,在提供的任何参数之前设置给定的参数序列。
参考: Function.prototype.bind()
function MyConstructor(data, transport) {
this.data = data;
transport.on('data',(function() {
alert(this.data);
}).bind(this);
解决方案 5:
“背景”的问题
术语“上下文”有时用于指代this所引用的对象。它的使用并不恰当,因为它在语义上和技术上都不符合ECMAScript 的this。
“上下文”是指围绕某事物的环境,这些环境赋予了事物意义,或者一些前后信息赋予了额外的意义。ECMAScript 中使用的术语“上下文”是指执行上下文,即某些执行代码范围内的所有参数、作用域和this。
ECMA-262 第 10.4.2 节中显示了这一点:
将 ThisBinding 设置为与调用执行上下文的 ThisBinding 相同的值
这清楚地表明这是执行上下文的一部分。
执行上下文提供了周围信息,为正在执行的代码添加了意义。它包含的信息远不止thisBinding。
this的值不是“上下文”。它只是执行上下文的一部分。它本质上是一个局部变量,可以通过对任何对象的调用来设置,在严格模式下,可以将其设置为任何值。
解决方案 6:
首先,需要对关键字的上下文scope
及其行为有一个清晰的认识。this
scope
this
& scope
:
JavaScript 中有两种类型的作用域。它们是:
全局范围
函数作用域
简而言之,全局作用域指的是窗口对象。在全局作用域中声明的变量可以从任何地方访问。
另一方面,函数作用域位于函数内部。函数内部声明的变量通常无法从外部访问。
全局范围内的关键字this
引用窗口对象。this
函数内部也引用窗口对象。因此this
将始终引用窗口,直到我们找到一种操作方法this
来指示我们自己选择的上下文。
--------------------------------------------------------------------------------
- -
- Global Scope -
- (globally "this" refers to window object) -
- -
- function outer_function(callback){ -
- -
- // Outer function scope -
- // Inside the outer function, the "this" keyword -
- // refers to window object -
- callback() // "this" inside callback also refers to the window object -
- } -
- -
- function callback_function(){ -
- -
- // Function to be passed as callback -
- -
- // Here "THIS" refers to the window object also -
- } -
- -
- outer_function(callback_function) -
- // Invoke with callback -
- -
--------------------------------------------------------------------------------
操作this
回调函数内部的不同方法:
这里我有一个名为 Person 的构造函数。它有一个名为的属性name
和四个方法,分别称为、、sayNameVersion1
和。这四个方法都有一个特定的任务。接受回调并调用它。回调有一个特定的任务,即记录 Person 构造函数实例的 name 属性。sayNameVersion2
sayNameVersion3
sayNameVersion4
function Person(name){
this.name = name
this.sayNameVersion1 = function(callback){
callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
callback()
}
this.sayNameVersion3 = function(callback){
callback.call(this)
}
this.sayNameVersion4 = function(callback){
callback.apply(this)
}
}
function niceCallback(){
// Function to be used as callback
var parentObject = this
console.log(parentObject)
}
现在让我们从 person 构造函数创建一个实例,并调用不同版本sayNameVersionX
(X 指的是 1,2,3,4) 方法,niceCallback
看看我们可以通过多少种方式操纵this
内部回调来引用该person
实例。
var p1 = new Person('zami') // Create an instance of Person constructor
绑定(bind):
bind 所做的是创建一个新函数,并将this
关键字设置为提供的值。
sayNameVersion1
并sayNameVersion2
使用bind来操作this
回调函数。
this.sayNameVersion1 = function(callback){
callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
callback()
}
第一个方法this
在方法本身内部绑定回调函数。而对于第二个方法,回调函数通过绑定到它的对象传递。
p1.sayNameVersion1(niceCallback) // pass simply the callback and bind happens inside the sayNameVersion1 method
p1.sayNameVersion2(niceCallback.bind(p1)) // uses bind before passing callback
称呼:
first argument
方法的用作call
附加到它的this
调用函数的内部call
。
sayNameVersion3
用来call
操纵this
引用我们创建的 person 对象,而不是 window 对象。
this.sayNameVersion3 = function(callback){
callback.call(this)
}
它的调用方式如下:
p1.sayNameVersion3(niceCallback)
申请:
与 类似call
, 的第一个参数apply
指的是将由关键字指示的对象this
。
sayNameVersion4
用来apply
操纵this
指代人对象
this.sayNameVersion4 = function(callback){
callback.apply(this)
}
它的调用方式如下。只需传递回调即可,
p1.sayNameVersion4(niceCallback)
解决方案 7:
我们不能将 this 绑定到,因为它总是与全局对象 (Window)setTimeout()
一起执行。如果您想在回调函数中访问上下文,那么通过使用回调函数,我们可以实现它:this
`bind()`
setTimeout(function(){
this.methodName();
}.bind(this), 2000);
解决方案 8:
问题围绕this
关键字在 JavaScript 中的行为方式。this
其行为方式不同,如下所示,
的值
this
通常由函数执行上下文决定。在全局作用域中,
this
指的是全局对象(window
即对象)。如果任何函数启用了严格模式,那么的值
this
将与严格模式下一样,全局对象代替对象undefined
引用。undefined
`window`点之前的对象就是关键字
this
将要绑定的对象。call()
我们可以使用、bind()
和明确设置此值apply()
当
new
使用关键字(构造函数)时,this 会绑定到正在创建的新对象。箭头函数不绑定
this
- 而是this
按词汇绑定(即基于原始上下文)
正如大多数答案所建议的那样,我们可以使用箭头函数或bind()
方法或 Self变量。我将引用Google JavaScript 样式指南中关于 lambda(箭头函数)的一点
优先使用箭头函数,而不是 f.bind(this),尤其是 goog.bind(f, this)。避免写 const self = this。箭头函数对于回调特别有用,回调有时会传递意外的额外参数。
Google 明确建议使用 lambda,而不是 bind 或const self = this
因此最好的解决方案是使用如下的 lambda,
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => {
alert(this.data);
});
}
参考:
解决方案 9:
当前,如果在代码中使用类,则还有另一种可行的方法。
在类字段的支持下,可以按以下方式实现:
class someView {
onSomeInputKeyUp = (event) => {
console.log(this); // This refers to the correct value
// ....
someInitMethod() {
//...
someInput.addEventListener('input', this.onSomeInputKeyUp)
当然,在底层,它都是绑定上下文的旧的好箭头函数,但在这种形式下,它看起来比显式绑定更清晰。
因为它是第 3 阶段提案,所以目前(2018 年 8 月)您将需要Babel和适当的Babel 插件来处理它。
解决方案 10:
另一种方法是自 DOM2 以来在事件监听器中绑定的标准方法,它允许您随时删除监听器(还有其他好处),它是来自接口的方法:this
`handleEvent(evt)`EventListener
var obj = {
handleEvent(e) {
// always true
console.log(this === obj);
}
};
document.body.addEventListener('click', obj);
有关使用的详细信息handleEvent
可以在这里找到:DOM handleEvent:自 2000 年以来的跨平台标准
解决方案 11:
Ngx
我在使用折线图函数时遇到了一个问题xAxisTickFormatting
,它是从 HTML 调用的,如下所示:[xAxisTickFormatting]="xFormat"
。
我无法从声明的函数访问组件的变量。此解决方案帮助我解决了问题并找到了正确的 this。
不要使用这样的函数:
xFormat (value): string {
return value.toString() + this.oneComponentVariable; //gives wrong result
}
使用这个:
xFormat = (value) => {
// console.log(this);
// now you have access to your component variables
return value + this.oneComponentVariable
}
解决方案 12:
this
在 JavaScript 中:
JavaScript 中的 的值this
100% 取决于函数的调用方式,而不是定义方式。我们可以相对轻松地this
通过“点左侧规则”找到 的值:
当使用 function 关键字创建函数时,其值
this
是被调用函数的点左边的对象如果点后面没有对象,那么
this
函数内部的值通常是全局对象(global
在 Node.js 和window
浏览器中)。我不建议this
在这里使用关键字,因为它不如使用window
!之类的东西那么明确。存在某些结构,例如箭头函数和使用
Function.prototype.bind()
a 函数创建的函数,它们可以固定 的值this
。这些是规则的例外,但它们对于固定 的值确实很有帮助this
。
Node.js 中的示例
module.exports.data = 'module data';
// This outside a function in node refers to module.exports object
console.log(this);
const obj1 = {
data: "obj1 data",
met1: function () {
console.log(this.data);
},
met2: () => {
console.log(this.data);
},
};
const obj2 = {
data: "obj2 data",
test1: function () {
console.log(this.data);
},
test2: function () {
console.log(this.data);
}.bind(obj1),
test3: obj1.met1,
test4: obj1.met2,
};
obj2.test1();
obj2.test2();
obj2.test3();
obj2.test4();
obj1.met1.call(obj2);
输出:
让我逐一向您介绍输出(从第二条日志开始忽略第一条日志):
this
是obj2
因为点左边的规则,我们可以看到test1
是如何调用的obj2.test1();
。obj2
在点的左边,因此是this
值的左边。尽管
obj2
位于点的左侧,但仍通过方法test2
绑定到。其值为。obj1
`bind()this
obj1`obj2
位于所调用函数的点的左侧:obj2.test3()
。因此obj2
的值将是this
。在这种情况下:
obj2.test4()
obj2
在点的左边。但是,箭头函数没有自己的this
绑定。因此它将绑定到this
外部范围的值,即module.exports
在开始时记录的对象。this
我们还可以使用函数指定值call
。在这里,我们可以将所需的值作为参数传入this
,在本例中就是obj2
。
解决方案 13:
其他人已经谈到了如何使用 .bind() 方法,但具体来说,如果有人在让它们一起工作时遇到困难,这里是如何将它与 .then() 一起使用:
someFunction()
.then(function(response) {
//'this' wasn't accessible here before but now it is
}.bind(this))
正如评论中提到的,另一种方法是使用没有自己的“this”值的箭头函数
someFunction()
.then((response)=>{
//'this' was always accessible here
})
解决方案 14:
您可以使用箭头函数来避免这个问题。
const functionToTest = (dataToSet , transport) => {
this.dataToSet = dataToSet ;
transport.on('dataToSet ', () => {
console.log(this.dataToSet);
});
}
解决方案 15:
2024 年答案
这里有很多信息已经过时了。这个答案主要讨论如何在 2024 年及以后解决
调用函数时使用实例
永远不要将函数“从其实例中”取出。如果您稍后需要调用该函数,只需保留整个实例,例如这样:
class A {
constructor(id) {
this.id = id;
}
someFunction(){
console.log(this);
}
}
const a1 = new A(5);
const a2 = new A(20);
const arr = [a1, a2];
// some business logic
arr.forEach(a => a.someFunction());
运行代码片段Hide results展开片段
不要过度使用
this
如果没有必要,请不要使用this
和类。当您需要存储库、控制器、服务等文件时,它默认会创建单例,这是您想要的(它基本上类似于 Java 中的 @Autowired,但是是原生的)。单例不需要类和“this”,无论您需要定义什么,请在文件根目录下定义。然后只需导出您想要的函数/变量。如果您不使用this
,就不会有问题this
。
例如somethingService.js
看起来类似于下面的代码:
const someDefaultValue = 10;
export function doSomething() {
console.log(someDefaultValue);
}
那么您不必考虑直接使用doSomething()
assomethingService.doSomething()
或doSomething()
。 (它将发挥相同的作用)
使用 apply/call/bind
我个人并不喜欢广泛使用apply
(或call
或,bind
它们基本上相同,只是叫法略有不同),根据我的经验,这会给代码带来更多混乱。但在某些情况下你可能需要它,所以我会在这里列出它。你基本上可以this
在函数内部“注入”任何你喜欢的对象。
class Simple {
constructor(id) {
this.id = id;
}
showThis(text) {
console.log(this, ` ** ${text} **`)
}
}
const simple = new Simple(25);
const showThisFn = simple.showThis;
showThisFn("using it without apply");
showThisFn.apply(simple, ["using it with apply"])
showThisFn.call(simple, "using it with call - basically same as apply, only syntactic sugar difference today")
boundShowThisFn = showThisFn.bind(simple);
boundShowThisFn("now it is bound with the function, so you dont have to specify it when calling it");
运行代码片段Hide results展开片段
正确使用箭头函数 =>
如果函数内部有函数,并且使用this
,箭头函数将以this
您期望的方式传递上下文。使用function
关键字将改变 的上下文this
。
(基本上,只要可能并且安全,就使用箭头函数)
class A {
constructor(baseValue) {
this.baseValue = baseValue;
}
doMathArrowFunction(arr){
arr.forEach(item => {
console.log(`Arrow function: item is ${item} and this?.baseValue is ${this?.baseValue}`);
console.log(item + this?.baseValue);
});
}
doMathOldWay(arr) {
arr.forEach(function(item) {
console.log(`Old fashion function: item is ${item} and this?.baseValue is ${this?.baseValue}`);
console.log(item + this?.baseValue);
});
}
}
const x = [1,2];
const a = new A(10);
a.doMathArrowFunction(x);
a.doMathOldWay(x);
运行代码片段Hide results展开片段
过去(在 Arrow 函数出现之前),这个问题通常用self/that
关键字来解决。如果你遇到这个问题并想了解更多信息 - 请查看此主题中已接受的答案。
解决方案 16:
这就是我解决问题的方法
class myClass
{
constructor(parent)
{
this.callback = (function() {
this.callbackFunctionOfParent();
}).bind(parent);
}
callCallback() {
this.callback();
}
}
class Class2
{
constructor()
{
this.Name = "CLASS 2";
this.test = new myClass(this);
this.test.callCallback();
}
callbackFunctionOfParent()
{
console.log("parent is: " + this.Name);
}
}
var c2 = new Class2;
运行代码片段Hide results展开片段
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件