【问题标题】:Why do you have to wrap a callback with an anonymous function?为什么必须用匿名函数包装回调?
【发布时间】:2015-05-03 21:13:20
【问题描述】:

我的 html 包含两个相互重叠的表单,一个用作添加表单,一个用作编辑表单。我使用 jQuery 通过以下代码显示和隐藏它们:

var editForm = $("#edit-form");
var addForm = $("#add-form");

var showEditForm = function() {
    editForm.fadeIn(function() {
        addForm.fadeOut();
    });
};
var showAddForm = function() {
    editForm.fadeOut(function() {
        addForm.fadeIn();
    });
};

我想让代码更紧凑,所以我直接在 fadeOut() 回调上设置了 fadeOut() 调用,方法如下:

var showEditForm = function() {
    editForm.fadeIn(addForm.fadeOut);
};
var showAddForm = function() {
    editForm.fadeOut(addForm.fadeIn);
};

但这会产生以下错误Uncaught TypeError: Failed to execute 'animate' on 'Element': Valid arities are: [1], but 4 arguments provided.,但为什么不起作用?

【问题讨论】:

  • 你必须提供一个函数,这样高阶函数fadeout才能触发它。这个答案的范围可以从单子到语法

标签: javascript jquery callback fadein fadeout


【解决方案1】:

这是因为将函数作为对象的属性调用是一种特殊的语法,它以对象为上下文调用函数。

当你调用这样的函数时:

obj.func();

那么this 将是函数内部对obj 的引用。

如果你得到函数的引用,然后调用它:

var f = obj.func;
f();

那么this 将是对全局上下文的引用,即window 对象。

通过使用editForm.fadeIn(addForm.fadeOut);,您可以获得对addForm.fadeOut 的引用并发送到fadeIn 方法。它不再与对象关联,因此将使用全局上下文而不是对象作为上下文来调用它。

您可以使用proxy 方法将函数与对象关联起来,以便使用正确的上下文调用它:

var showEditForm = function() {
  editForm.fadeIn($.proxy(addForm.fadeOut, addForm));
};
var showAddForm = function() {
  editForm.fadeOut($.proxy(addForm.fadeIn, addForm));
};

【讨论】:

    【解决方案2】:

    我怀疑问题在于 addForm.fadeOut 在传递给 fadeIn 函数时使用错误的参数组合调用(反之亦然)。

    这个陷阱的经典例子似乎是:

    ["0", "1", "2", "3"].map(function(i) {return parseInt(i);})
    

    这按预期工作,结果是[1,2,3,4]。你可能希望你可以缩短这个,就像你在上面所做的那样,然后写

    ["0", "1", "2", "3"].map(parseInt);
    

    不幸的是;这评估为[0, NaN, NaN, NaN]。问题在于,.map 调用任何提供了三个参数的函数:值、索引和数组本身,而parseInt 最多需要两个参数:值,还有要解析的基数/基数. (e.g. radix 2 to parse a string as binary) 所以实际上发生的事情本质上是:

    [
        parseInt("0", 0), //0, radix is ignored
        parseInt("1", 1), //NaN, what is base 1?
        parseInt("2", 2), //NaN, 2 isn't valid binary
        parseInt("3", 3)  //NaN, 3 isn't valid ternary/base-3
    ]
    

    根据错误消息,我怀疑这里发生了同样的事情。函数的“arity”是传递给它的参数的数量,所以这里的错误消息说提供了 4 个参数,而预期只有一个。

    通常,对于带有可选参数的函数,在将它们直接传递给其他函数之前需要小心,否则您无法控制将使用哪些参数调用它。

    【讨论】:

    • 这确实产生了错误。使用addForm.fadeOut(function() { editForm.fadeIn.apply(this, arguments); }); 抛出完全相同的错误
    【解决方案3】:

    小提琴:http://jsfiddle.net/jmj8tLfm/

    addForm.fadeInaddForm.fadeOut 被调用,但未指定调用 addForm.fadeIn() 时通常会传递的 this 上下文。尝试.bind()-ing 这个变量,如下所示:

    var showEditForm = function() {
        editForm.fadeIn(addForm.fadeOut.bind(addForm));
    };
    var showAddForm = function() {
        editForm.fadeOut(addForm.fadeIn.bind(addForm));
    };
    

    【讨论】:

    【解决方案4】:

    如果你使用 vanilla js 编写。为什么需要在匿名函数中传递回调函数的原因与函数调用有关。

    看看这个例子:

    const firstFunc = (callback) => {
     setTimeout(function() {
      console.log('yes');
      console.log(callback())
     }, 3000);
    }
    
    const secondFunc = () => console.log('great');
    
    firstFunc(function(){
     secondFunc();
    });
    
    // prints 'yes' 'great' after 3 seconds
    
    > yes
      great
    

    当调用函数时,如果你传递不带括号的回调参数,即firstFunc(secondFunc);,回调函数将在第一个函数完成后调用(就像上面一样)在第一个函数中提供的回调被调用的地方是调用那个功能。即callback()() 是重要部分。

    尝试像这样在第一个函数callback 中省略括号,然后将第二个函数作为回调传递,不带括号firstFunction(secondFunction) 请注意您是如何传递回调函数的,但它从未被调用。你的 console.log() 应该是这样的。

    > yes
      () => console.log('great')
    

    那么为什么这很重要......

    如果您使用第一个代码 sn-p 中的设置将函数调用作为 firstFunc(secondFunc()) 传递。您会注意到第二个函数首先打印,然后 3 秒后第一个函数被调用。

    > great
      yes
    

    鉴于 Javascript 是事件驱动的,第二个函数的调用可以在 const secondFunc = () => console.log('great'); 找到,它会立即调用该函数,而不是等待第一个函数的响应。这就是您在 firstFunc 中调用 secondFunc() 时 () 所做的。

    通过传递一个匿名函数,该函数在到达 callback() 调用部分之前永远不会被调用。再次使用第一个代码 sn-p 中的设置,尝试。

    firstFunc(function(){
     secondFunc();
    });
    
    firstFunc(function(){
     secondFunc();
    }())
    

    查看第二个调用如何立即调用 secondFunc。正在发生的事情是匿名函数是一个包装器,不会立即调用您的函数。

    为什么这很有用? 如果 secondFunc 接受回调,则在第二个函数完成执行之前不会调用该回调函数。您需要在第二个函数中调用该回调函数。

    firstFunc(function(){
     secondFunc(function(){
      thirdFunc();
     });
    });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-07-16
      • 2012-12-31
      • 1970-01-01
      • 2013-05-16
      • 1970-01-01
      • 2016-08-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多