Javascript 如何在回调中访问正确的'this'?
我有一个注册事件处理程序的构造函数: 函数MyConstructordata,传输{ 这个数据=数据; 传输。关于“数据”,函数{ 警报此数据; }; } //模拟传输对象 var传输={ 开启:functionevent,回调{ setTimeoutcallback,1000; } }; //称为 var obj=新的MyConstructor'foo',transport;关于这件事你应该知道什么 上下文是每个函数中的一个特殊关键字,其值仅取决于函数的调用方式,而不取决于函数的定义方式/时间/地点。除了箭头函数之外,它与其他变量一样不受词法作用域的影响,见下文。以下是一些例子:Javascript 如何在回调中访问正确的'this'?,javascript,callback,this,Javascript,Callback,This,我有一个注册事件处理程序的构造函数: 函数MyConstructordata,传输{ 这个数据=数据; 传输。关于“数据”,函数{ 警报此数据; }; } //模拟传输对象 var传输={ 开启:functionevent,回调{ setTimeoutcallback,1000; } }; //称为 var obj=新的MyConstructor'foo',transport;关于这件事你应该知道什么 上下文是每个函数中的一个特殊关键字,其值仅取决于函数的调用方式,而不取决于函数的定义方式/时
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`
要了解更多信息,请查看
如何参考本手册中的正确答案
使用
ECMAScript 6引入了箭头函数,可以将其视为lambda函数。他们没有自己的这个绑定。相反,这是在范围内查找的,就像普通变量一样。这意味着你不必打电话给bind。这不是他们唯一的特殊行为,更多信息请参考MDN文档
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => alert(this.data));
}
不要用这个
实际上,您不想特别访问它,但它所引用的对象。这就是为什么一个简单的解决方案是创建一个新的变量,该变量也引用该对象。变量可以有任何名称,但常用的名称是self,即self
因为self是一个普通变量,所以它遵守词法范围规则,并且可以在回调中访问。这还有一个优点,即您可以访问回调本身的This值
显式设置回调函数的这一部分-第1部分
看起来您可能无法控制它的值,因为它的值是自动设置的,但事实并非如此
每个函数都有一个方法,该方法返回一个新函数,并将其绑定到一个值。该函数的行为与您调用的.bind on完全相同,只是这是由您设置的。无论如何或何时调用该函数,它都将引用传递的值
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);
}
第一个参数是回调,第二个参数是它应该引用的值。下面是一个人为的例子:
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.method被指定为click事件处理程序,但是如果单击document.body,则记录的值将是未定义的,因为在事件处理程序中,它指的是document.body,而不是Foo的实例。
正如前面提到的,它所指的内容取决于函数的调用方式,而不是函数的定义方式。
如果代码如下所示,则函数没有对对象的隐式引用可能更为明显:
function method() {
console.log(this.data);
}
function Foo() {
this.data = 42,
document.body.onclick = this.method;
}
Foo.prototype.method = method;
解决方案与上面提到的相同:如果可用,请使用.bind将其显式绑定到特定值
document.body.onclick = this.method.bind(this);
或者使用匿名函数作为回调/事件处理程序显式调用函数作为对象的方法,并将此对象分配给另一个变量:
var self = this;
document.body.onclick = function() {
self.method();
};
或使用箭头功能:
document.body.onclick = () => this.method();
this.saveNextLevelData(this.setAll.bind(this));
这一切都体现在调用方法的神奇语法中:
object.property();
当您从对象获取属性并一次性调用它时,该对象将成为该方法的上下文。如果调用相同的方法,但在单独的步骤中,则上下文将改为全局范围窗口:
var f = object.property;
f();
当您获得方法的引用时,它不再附加到对象,它只是对普通函数的引用。当获取要用作回调的引用时,也会发生同样的情况:
this.saveNextLevelData(this.setAll);
这就是将上下文绑定到函数的位置:
document.body.onclick = () => this.method();
this.saveNextLevelData(this.setAll.bind(this));
如果使用jQuery,则应改用$.proxy方法,因为并非所有浏览器都支持绑定:
this.saveNextLevelData($.proxy(this.setAll, this));
语境的问题
术语“上下文”有时用于指本文档引用的对象。它的使用是不恰当的,因为它在语义上或技术上都不适合
意思是指围绕某事物而增加意义的环境,或是一些赋予额外意义的前后信息。术语context在ECMAScript中用来指代,它是一些正在执行的代码范围内的所有参数、范围和内容
如图所示:
将ThisBinding设置为与的ThisBinding相同的值
调用执行上下文
这清楚地表明这是执行上下文的一部分
执行上下文提供周围信息,为正在执行的代码添加意义。它所包含的信息远不止这些
因此,它的价值不是上下文,它只是执行上下文的一部分。它本质上是一个局部变量,可以通过调用任何对象并在严格模式下设置为任何值。以下是在子上下文中访问父上下文的几种方法-
您可以使用函数。
将对context/this的引用存储在另一个变量中参见下面的示例。
使用ES6功能。
更改代码/功能设计/归档
体系结构-对于这一点,您应该拥有javascript中的命令。
1.使用绑定函数
函数MyConstructordata,传输{
这个数据=数据;
传输。关于“数据”,函数{
警报此数据;
}.这一点;
}
//模拟传输对象
var传输={
开启:functionevent,回调{
setTimeoutcallback,1000;
}
};
//称为
var obj=新的MyConstructor'foo',transport;
如果您使用的是下划线.js-
2将对上下文/此的引用存储在另一个变量中
函数MyConstructordata,传输{
var self=这个;
这个数据=数据;
传输。关于“数据”,函数{
警报自我数据;
};
}
3箭头功能
函数MyConstructordata,传输{
这个数据=数据;
传输。在“数据”上,=>{
警报此数据;
};
}
首先,您需要清楚地了解这个关键字在范围上下文中的范围和行为 这是一个范围(&S): 总之,,全局作用域指窗口对象。全局作用域中声明的变量可以从任何地方访问。另一方面,函数作用域位于函数内部。函数内部声明的变量通常不能从外部世界访问。全局作用域中的此关键字指窗口对象。此内部函数也指窗口对象。所以它总是指窗口,直到我们找到一种方法来处理它,以表示我们自己选择的上下文
--------------------------------------------------------------------------------
- -
- Global Scope -
- ( globally "this" refers to window object) -
- -
- function outer_function(callback){ -
- -
- // outer function scope -
- // inside outer function"this" keyword refers to window object - -
- callback() // "this" inside callback also refers window object -
- } -
- -
- function callback_function(){ -
- -
- // function to be passed as callback -
- -
- // here "THIS" refers to window object also -
- -
- } -
- -
- outer_function(callback_function) -
- // invoke with callback -
--------------------------------------------------------------------------------
在回调函数中操作此函数的不同方法:
这里有一个名为Person的构造函数。它有一个名为name的属性和四个名为sayNameVersion1、sayNameVersion2、sayNameVersion3、sayNameVersion4的方法。这四个函数都有一个特定的任务。接受回调并调用它。回调有一个特定的任务,即记录Person构造函数实例的name属性
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)
}
this.sayNameVersion1 = function(callback){
callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
callback()
}
现在,让我们从person构造函数创建一个实例,并使用niceCallback调用不同版本的sayNameVersionX引用1,2,3,4方法,看看有多少种方法可以操作this内部回调来引用person实例
var p1 = new Person('zami') // create an instance of Person constructor
bind要做的是创建一个新函数,将this关键字设置为提供的值
sayNameVersion1和sayNameVersion2使用bind来操作回调函数的这个部分
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)
}
this.sayNameVersion1 = function(callback){
callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
callback()
}
第一个是在方法本身内部绑定回调,第二个是在对象绑定的情况下传递回调
p1.sayNameVersion1(niceCallback) // pass simply the callback and bind happens inside the sayNameVersion1 method
p1.sayNameVersion2(niceCallback.bind(p1)) // uses bind before passing callback
call方法的第一个参数在函数中使用,函数调用时附加了call
sayNameVersion3使用调用操纵this来引用我们创建的person对象,而不是window对象
this.sayNameVersion3 = function(callback){
callback.call(this)
}
它的名称如下所示:
p1.sayNameVersion3(niceCallback)
与call类似,apply的第一个参数引用将由该关键字指示的对象
sayNameVersion4使用apply来处理此对象以引用person对象
this.sayNameVersion4 = function(callback){
callback.apply(this)
}
它的调用如下所示。只需传递回调
p1.sayNameVersion4(niceCallback)
我们不能将其绑定到setTimeout,因为它总是在全局对象窗口中执行,如果您想在回调函数中访问此上下文,那么通过使用绑定到回调函数,我们可以实现如下:
setTimeout(function(){
this.methodName();
}.bind(this), 2000);
另一种方法是EventListener接口中的HandleEventVTMethod,这是自DOM2以来在事件侦听器中绑定此功能的标准方法,它让您始终可以删除侦听器,还有其他好处:
var obj = {
handleEvent(e) {
// always true
console.log(this === obj);
}
};
document.body.addEventListener('click', obj);
有关使用handleEvent的详细信息可在此处找到:如果在代码中使用类,则目前还有另一种方法 有了支持,就有可能实现下一步:
class someView {
onSomeInputKeyUp = (event) => {
console.log(this); // this refers to correct value
// ....
someInitMethod() {
//...
someInput.addEventListener('input', this.onSomeInputKeyUp)
当然,在幕后,绑定上下文的都是旧的GoodArrow函数,但在这种形式下,显式绑定看起来更清晰
由于这是第3阶段的提案,您将需要巴别塔,并在2018年8月开始适当地处理它。您应该知道这个关键词
根据我的观点,你可以通过三种方式实现这一点
Self/Arrow函数/绑定方法
与其他语言相比,函数的this关键字在JavaScript中的行为稍有不同
严格模式和非严格模式也有一些区别
在大多数情况下,其值取决于函数的调用方式
它不能在执行期间通过赋值进行设置,每次调用函数时可能会有所不同
ES5引入了bind方法来设置函数this的值,不管它是如何调用的
ES2015引入了arrow函数,这些函数不提供自己的绑定,它保留了封闭词法上下文的值
方法1:Self-Self被用来维护对原始文档的引用,即使上下文在变化。这是一种经常在事件处理程序中使用的技术,尤其是在闭包中
参考:
方法2:Arrow函数-Arrow函数表达式是常规函数表达式的一种语法紧凑的替代方法
离子
尽管没有它自己对this、arguments、super或new.target关键字的绑定
箭头函数表达式不适合用作方法,并且不能用作构造函数
参考:
方法3:Bind-Bind方法创建一个新函数
调用时,将其this关键字设置为提供的值
调用新函数时,在任何参数之前提供给定的参数序列
参考:
问题围绕着这个关键字在javascript中的行为。其行为方式如下所示:
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => {
alert(this.data);
});
}
其值通常由函数执行上下文确定。
在全局范围中,这是指窗口对象的全局对象。
如果为任何函数启用了严格模式,则其值将未定义,因为在严格模式下,全局对象是指未定义的窗口对象。
站在点前面的对象就是这个关键字将绑定到的对象。
我们可以通过call、bind和apply显式地设置这个值
当new关键字用作构造函数时,它将绑定到正在创建的新对象。
箭头函数不绑定此项 — 相反,这是词汇上的约束,即基于原始上下文
正如大多数答案所建议的,我们可以使用Arrow函数、bind方法或Self-var
与f.bindthis相比,更喜欢使用箭头函数,尤其是
goog.bindf,这个。避免编写const self=this。箭头函数
对于有时意外通过的回调特别有用
附加参数
谷歌明确建议使用lambdas,而不是bind或const self=this
因此,最好的解决方案是使用lambdas,如下所示
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => {
alert(this.data);
});
}
参考资料:
这在JS中是:
JS中的值100%取决于函数的调用方式,而不是函数的定义方式。我们可以通过“点的左侧规则”相对容易地找到该值:
使用function关键字创建函数时,该关键字的值是调用函数点左侧的对象
如果点的左边没有对象,那么函数中的值通常是全局对象在节点、浏览器窗口中的全局对象。我不建议在这里使用this关键字,因为它比使用window之类的东西更显式!
存在某些构造,如箭头函数和使用Function.prototype.bind创建的函数。可以修复此函数的值。这些都是规则的例外情况,但确实有助于修复该规则的值。
nodeJS中的示例
输出:
让我逐一介绍输出,忽略从第二个日志开始的第一个日志:
这是obj2,因为点规则的左边,我们可以看到test1如何被称为obj2.test1;。obj2位于点的左侧,因此该值为。
即使obj2位于点的左侧,test2也通过bind方法绑定到obj1。所以这个值的最大值是obj1。
obj2位于名为:obj2.test3的函数的点的左侧。因此,obj2将是该值。
在这种情况下:obj2.test4 obj2位于点的左侧。但是,箭头函数没有自己的此绑定。因此,它将绑定到外部范围(即模块)的this值。导出一个在开始时记录的对象。
我们还可以使用call函数指定该值。在这里,我们可以将所需的this值作为参数传递,在本例中为obj2。
我遇到了Ngx折线图XaxistickFormat函数的问题,该函数是从HTML调用的,如下所示:[XaxistickFormat]=xFormat。我无法从声明的函数访问组件的变量。这个解决方案帮助我解决了这个问题,找到了正确的解决方法。希望这对Ngx折线图的用户有所帮助
不要像这样使用函数:
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
}
相关:相关:,主要也适用于JS。@Stratton:自答问题是UI中的一个选项,建议:。我为一次又一次出现的问题做了几次,以提供一个规范的答案。经典问答的问题在于,现有问题的措辞往往不够笼统,不能集中于核心问题。Felix,我以前读过这个答案,但从未回答过。我越来越担心人们用自我和自我来指代这一点。我有这种感觉,因为这是一个在不同上下文中使用的重载变量;而self通常对应于本地实例,它通常引用另一个对象。我知道你没有制定这条规则,正如我在许多其他地方看到的那样,但这也是我开始使用这条规则的原因,但我不确定其他人的感受,除了由此产生的非统一实践之外。@FelixKling是否可以安全地假设,使用此内部原型函数将始终具有预期的行为,而不管它们通常如何调用?在p中使用回调时
rototype函数,是否有绑定、self或其他替代方法?@FelixKling依赖Function.prototype.call和Function.prototype.apply有时会很有用。特别是使用apply,我跑了很多里程。我不太倾向于使用绑定,也许只是出于习惯,虽然我知道,但不能肯定的是,使用BIN比其他选项可能有轻微的开销优势。很好的答案,但是考虑添加一个额外的可选解决方案,只是为了不使用类、新或这一点。这是在范围内查找的,就像普通变量一样。完全为我点击,谢谢!=>点击这个按钮;如果您对答案有任何建设性的批评,我们将不胜感激!全局范围中的this关键字不一定指窗口对象。只有在浏览器中才是这样。@RandallFlagg我从浏览器的角度写下了这个答案。如果有必要,可以随意使用这个答案:这与任何现有答案有何不同?这正是我在Typescript:public methodName=params=>{body}中得到它的方式这个问题是关于使用函数/方法作为回调的。你的回答可能更适合我的工作。我发现你的第四点措辞含糊不清。考虑一下,右边的对象是站在点之前,但是上下文不是那个对象。绑定选项是惊人的,它只是通过这个对象的指针在另一个对象上做这个:谢谢!子上下文中的父上下文是一个误导性短语,因为这不是父/子关系的一部分。它通常指调用方法的对象,但可以是任何对象,也可以是严格模式下的任何值。指一个执行上下文,该上下文是许多无法引用的参数中的一个,因为ECMA-262禁止它。不同意此答案。“执行上下文”一词的存在并不禁止上下文的其他用途,正如它不禁止执行的其他用途一样。也许有一个更好的术语来描述这一点,但这里没有提供,而且可以说现在关闭上下文的大门已经太迟了。@Roamer-1888-谢谢您的编辑。你是对的,但我的论点并不依赖于执行上下文的存在,而排除了出于其他目的的执行上下文。相反,它是基于从技术和语义的角度来看上下文都不合适。我还认为,使用上下文而不是这一点正在消失。我看不出有任何理由为this或thisBinding找到一个替代术语,它只是混淆了,意味着在某个点上,你必须解释上下文实际上是this,而它无论如何都不在上下文中-当你已经承认这是执行上下文的一部分,执行只是形容词时,我认为你不能说这与上下文无关。@Roamer-1888-我不打算在这一点之后继续这个对话。是的,这是执行上下文的一部分。说它是上下文就像说一个团队的一个成员就是团队。
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => {
alert(this.data);
});
}
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);
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
}