为什么你必须用匿名函数包装回调?

Why do you have to wrap a callback with an anonymous function?

我的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.,但为什么不起作用?

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

当您调用这样的函数时:

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));
};

Fiddle: http://jsfiddle.net/jmj8tLfm/

调用

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

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

我怀疑问题是当 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 最多接受两个参数:值,还有 radix/base 进行解析。(例如,基数 2 将字符串解析为二进制)所以实际发生的本质上是:

[
    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 个参数,而预期只有一个。

一般来说,对于带有可选参数的函数,在将它们直接传递给其他函数之前需要小心,否则你无法控制调用它的参数。

如果你正在用 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); 回调函数将在第一个函数完成后调用(就像上面一样)在回调获取的第一个函数内提供called 正在调用该函数。即 callback()() 是重要的部分。

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

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

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

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

> great
  yes

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

中调用 secondFunc() 时 () 所做的

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

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

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

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

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

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