JavaScript 回调范围 [duplicate]

JavaScript Callback Scope [duplicate]

提问人:Chris MacDonald 提问时间:10/8/2008 更新时间:12/23/2011 访问量:54543

问:

我在使用普通的旧 JavaScript(没有框架)在回调函数中引用我的对象时遇到了一些问题。

function foo(id) {
    this.dom = document.getElementById(id);
    this.bar = 5;
    var self = this;
    this.dom.addEventListener("click", self.onclick, false);
}

foo.prototype = {
    onclick : function() {
        this.bar = 7;
    }
};

现在,当我创建一个新对象时(在DOM加载之后,使用span#test)

var x = new foo('test');

onclick 函数中的 'this' 指向 span#test,而不是 foo 对象。

如何在 onclick 函数中获取对我的 foo 对象的引用?

JavaScript 事件 绑定 范围 回调

评论

0赞 vsync 8/10/2020
保存参考:this.cb = this.onclick.bind(this); addEventListener("click", this.cb);

答:

15赞 Sergey Ilinsky 10/8/2008 #1
this.dom.addEventListener("click", function(event) {
    self.onclick(event)
}, false);

评论

1赞 Chris MacDonald 10/8/2008
谢谢,但是您能解释一下为什么我需要在那里创建一个匿名函数吗?为什么这会改变绑定?
0赞 Tom 10/8/2008
每当触发事件时,“this”都是指调用该事件的对象。
2赞 Adam Bellaire 10/8/2008
当您在问题中使用表单时,self.onclick 只是一个匿名函数,在处理时,它就像您将其直接附加到 span 一样工作。当你把它包装在一个闭包中时,self.onclick(event)真的会做你想做的事,例如,在定义闭包的上下文中使用'self'。
0赞 w00t 1/27/2012
这仅适用于正在创建的一个对象;考虑function () {var i; for (i=0; i<5; i++) { setTimeout( function() { console.log(i); }, 1000); } }; f();
0赞 w00t 2/10/2012
我收回了这一点 - 每次调用都会创建一个新存在,并且与我之前评论中的示例不同,它不会改变。(里面也有错别字,应该以var selffunction f())
-3赞 Javier 10/8/2008 #2

这是 JS 最令人困惑的点之一:“this”变量表示最局部的对象......但函数也是对象,所以“this”指向那里。还有其他微妙的点,但我不记得它们了。

我通常避免使用“this”,只需定义一个局部的“me”变量并改用它。

评论

2赞 Kenaniah 2/3/2010
你的陈述是完全不准确的。函数的 apply() 和 call() 方法都允许您使用“this”引用您在调用中传递的变量来执行函数。
1赞 Javier 2/3/2010
@Kenaniah:....因此,更令人困惑的是,代码的确切含义取决于它的调用方式!
0赞 Arman 6/4/2013
函数在 JavaScript 中非常灵活,不使用几乎不是解决问题的方法。 是 JS 中唯一的动态范围功能,它为语言增加了很多功能。是的,函数是 JS 中的第一类对象这一事实暗示了“执行上下文指针”的动态性质。如果使用得当,这是一个很棒的功能。thisthis
81赞 hishadow 10/11/2008 #3

(提取了一些隐藏在其他答案评论中的解释)

问题出在以下行:

this.dom.addEventListener("click", self.onclick, false);

在这里,你传递一个函数对象作为回调。当事件触发时,该函数被调用,但现在它与任何对象 (this) 都没有关联。

可以通过将函数(及其对象引用)包装在闭包中来解决此问题,如下所示:

this.dom.addEventListener(
  "click",
  function(event) {self.onclick(event)},
  false);

由于变量 self 是在创建闭包时分配的,因此闭包函数将在稍后调用 self 变量时记住该变量的值。

解决此问题的另一种方法是创建一个效用函数(并避免使用变量来绑定):

function bind(scope, fn) {
    return function () {
        fn.apply(scope, arguments);
    };
}

更新后的代码将如下所示:

this.dom.addEventListener("click", bind(this, this.onclick), false);

Function.prototype.bind 是 ECMAScript 5 的一部分,提供相同的功能。因此,您可以执行以下操作:

this.dom.addEventListener("click", this.onclick.bind(this), false);

对于尚不支持 ES5 的浏览器,MDN 提供了以下填充码

if (!Function.prototype.bind) {  
  Function.prototype.bind = function (oThis) {  
    if (typeof this !== "function") {  
      // closest thing possible to the ECMAScript 5 internal IsCallable function  
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");  
    }  

    var aArgs = Array.prototype.slice.call(arguments, 1),   
        fToBind = this,   
        fNOP = function () {},  
        fBound = function () {  
          return fToBind.apply(this instanceof fNOP  
                                 ? this  
                                 : oThis || window,  
                               aArgs.concat(Array.prototype.slice.call(arguments)));  
        };  

    fNOP.prototype = this.prototype;  
    fBound.prototype = new fNOP();  

    return fBound;  
  };  
} 

评论

0赞 hishadow 8/17/2009
不过,closure() 可能是一个更好的名称。
0赞 w00t 1/27/2012
当您使用多个对象时,闭包将不起作用;您将始终引用创建的最后一个对象。实用程序函数之所以有效,是因为每次都新存储参数。
2赞 Vincent Robert 10/11/2008 #4

解释是,这并不意味着你认为它在 JavaScript 中的含义。它实际上意味着对象原型中的函数(不以任何方式引用自身)。self.onclickonclickselfself

JavaScript 只有函数,没有像 C# 那样的委托,因此不可能传递方法和它应该作为回调应用到的对象。

在回调中调用方法的唯一方法是在回调函数中自行调用它。由于 JavaScript 函数是闭包,因此它们能够访问在创建它们的范围内声明的变量。

var obj = ...;
function callback(){ return obj.method() };
something.bind(callback);

评论

0赞 Chris MacDonald 10/11/2008
我将原型函数称为“onclick”以轻松关联它。我知道它不会被实际的 DOM onclick 事件自动调用,这就是为什么我尝试让事件侦听器将我的对象的 onclick 与 DOM 的 onclick 函数绑定。
0赞 Vincent Robert 10/12/2008
那不是我的重点,我理解你的 onclick 函数。我的观点是 self.onclick 和 foo.prototype.onclick 在 JavaScript 中没有区别。JavaScript 中没有办法说“这个方法绑定到这个对象”。
0赞 dolmen 8/23/2010
唯一的方法是使用闭包。
1赞 alberto 4/17/2009 #5

我写了这个插件...

我认为这将是有用的

jquery.callback

2赞 tpk 11/28/2009 #6

这里提供了对问题的一个很好的解释(我在理解到目前为止描述的解决方案时遇到了问题)。

5赞 Seldaek 1/14/2011 #7

对于寻求此问题解决方案的jQuery用户,您应该使用jQuery.proxy