【问题标题】:jQuery, Javascript, Object Notation - Explanation of Differences in Callback BehaviorjQuery, Javascript, Object Notation - 回调行为差异解释
【发布时间】:2011-08-16 22:02:32
【问题描述】:

考虑以下使用对象字面量定义的 jQuery 实现...

$(function() {

    var myObject = {

        methodOne: function()
        {

            $('#element').animate(
                {'marginLeft': '50px'},
                'slow',
                function() {
                    myObject.methodTwo();
                }
            );

        },

        methodTwo: function()
        {

            $('#element').animate(
                {'marginLeft': '-50px'},
                'slow',
                function() {
                    myObject.methodOne();
                }
            );

        }

    } // End myObject

    myObject.methodOne(); // Execute

});

作为记录,上面的代码按预期工作。我不明白的是为什么像下面这样一个微妙且看似无害的变化......

    methodOne: function()
    {

        $('#element').animate(
            {'marginLeft': '50px'},
            'slow',
            myObject.methodTwo() // No more anonymous function
        );

    },

... 到 methodOne 和 methodTwo 会导致浏览器错误声明 too much recursion。我声明回调的方式有什么区别?另外,如果我带回匿名函数声明,但将对象引用修改为如下所示...

    methodOne: function()
    {

        $('#element').animate(
            {'marginLeft': '50px'},
            'slow',
            function() {
                this.methodTwo(); // assuming 'this' refers to 'myObject'
            }
        );

    },

...我通过methodOne 获得了一次很好的通过,并且在回调时我的浏览器因为找不到methodTwo 而崩溃。我的猜测是我在某个地方超出了范围,但我无法正确决定在哪里。非常感谢您的洞察力!

【问题讨论】:

    标签: javascript jquery oop


    【解决方案1】:

    让我们分解一下

            $('#element').animate(
                {'marginLeft': '50px'},
                'slow',
                function() {
                    myObject.methodTwo();
                }
            );
    

    在此示例中,您将函数对象作为回调传递。动画完成时调用该函数对象。它具有通过闭包共享的myObject 对象,因此它可以轻松找到它并在其上调用方法。太棒了!

        $('#element').animate(
            {'marginLeft': '50px'},
            'slow',
            myObject.methodTwo() // No more anonymous function
        );
    

    这里发生了一些不同的事情。作为这里的回调,您实际上传递的是myObject.methodTwo() 的返回值,而不是实际的函数对象。因此,由于methodTwo() 不返回任何内容,因此undefined 实际上作为您的回调传递。这意味着animate() 函数认为没有回调。

    所以也许你想试试这个!

        $('#element').animate(
            {'marginLeft': '50px'},
            'slow',
            myObject.methodTwo // No more anonymous function or invocation
        );
    

    好吧,这仍然行不通。现在您正在为回调传递一个函数对象,是的,但它会丢失上下文 (this)。事实证明,当您自己调用函数对象时,this 是全局对象。看看这个:

    var obj = {
      foo: function() { console.log(this) }
    };
    
    obj.foo() // logs obj
    
    var func = obj.foo;
    func() // logs window (the global object in a browser)
    

    因此,您不能将函数对象直接传递给回调,该回调应该像以对象作为接收者的方法一样被调用。在animate() 方法的内部,它会为您执行callback(),这是一个根本不会为您保留this 值的调用。

    那为什么这个没用呢?

        $('#element').animate(
            {'marginLeft': '50px'},
            'slow',
            function() {
                this.methodTwo(); // assuming 'this' refers to 'myObject'
            }
        );
    

    当调用匿名函数作为回调时,就像中断方法一样,this 默认为窗口对象。所以这段代码实际上调用了window.methodTwo(),不存在的,就爆炸了。

    因此,公认的标准 JS 方式是您的第一种方式。

    someFunc(arg1, arg2, function() {
      someObj.someMethod()
    });
    

    这应该总是有效的,即使它看起来很浪费,因为你调用 2 个函数来做一件事。但正如您发现的那样,它是最不容易出错的。

    了解this 在 JS 中的工作方式是一种痛苦的经历,但是当您了解它时,您会发现规则非常简单且易于操作。


    如果你仍然不喜欢那样,你可以做一些棘手的 js 魔术。喜欢underscore.js bind() method。

    someFunc(arg1, arg2, _.bind(someObj.someMethod, someObj));
    

    这将返回一个始终以someObj 作为this 运行的函数,它可以安全地用作回调。

    或者,如果您想试用 CoffeeScript,它有一个粗箭头来保留上下文,它可以编译为类似于 underscore.js bind() 方法的 JS。

    someObj =
      foo: ->
        someFunc arg1, arg2, =>
          this.bar()
    
      bar: ->
        alert 'got the callback!'
    
    someObj.foo()
    

    在这种情况下,=> 风格的函数声明保留了它出现的范围的上下文,因此可以安全地使用它。

    【讨论】:

    • +1 并解决了您关于范围和对象处理的非常透彻的课程。当谈到 Javascript 时,魔鬼确实在细节中,但我仍然认为它是一种非常雄辩的语言(相对而言,就是这样)。
    【解决方案2】:

    当您为 jquery 提供要执行的函数时,您实际上是在传递一个函数指针,而不是您想要执行的内容。它基本上对您传递的函数引用执行 exec,因此当您将其更改为 obj.method( ) 它不再理解它了..

    myObject.methodTwo() 的问题是 myObject.methodTwo() 在作为函数传递之前被评估。在这种情况下,Javascript 和扩展的 jQuery 需要一个函数指针。 IE。 setTimeout 函数。

    在下面的用法中,创建了一个匿名函数(只是一个语句块)并注册为回调函数。注意'function(){'的使用。匿名函数只做一件事:调用 myObject.methodOne()

    function() {
      myObject.methodOne();
    }
    

    关于回调函数中的第二个问题,this 指的是不同的对象,而不是您的原始对象,这就是失败的原因。如果您想将 this 引用传递给回调函数,请阅读:http://thomasdavis.github.com/tutorial/anonymous-functions.html

    【讨论】:

      【解决方案3】:

      当您将回调设置为myObject.methodTwo() 时,您实际上是在调用 methodTwo。然后在methodTwo 内部,它通过调用methodOne 来做同样的事情......它调用methodTwo,它调用methodOne,等等。所以你有一个无限递归。

      当您将同一行代码包装在匿名函数中时,它不会立即调用该函数。您现在传递的是函数本身,而不是返回值。然后使用回调的函数确定何时(如果有的话)实际调用该函数。

      【讨论】:

        【解决方案4】:

        正如 zzzz 和 Paul 所说,您是在调用函数并传递其返回值,而不是传递回调。但是您不需要将其包装在匿名函数中以使其工作 - 只需在方法后删除 () 即可。

        【讨论】:

          【解决方案5】:

          这里的this.methodTwo是寻找本地引用,This属于当前作用域,所以方法二定义在当前作用域之外,你可以使用。我只是快速查看代码并找到以下建议。

           methodOne: function()
            {
           var that = this;
              $('#element').animate(
                  {'marginLeft': '50px'},
                  'slow',
                  function() {
                      that.methodTwo(); // assuming 'this' refers to 'myObject'
                  }
              );
          
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-11-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-02-28
            • 1970-01-01
            相关资源
            最近更新 更多