理解关于闭包的匿名 JavaScript 函数定义

Understanding anonymous JavaScript function definition with regard to closures

我在概念上很好地处理了闭包,但是出现了一个我不太理解的问题。

当创建函数以将某些值传递给内部函数而不将其绑定到最外层函数的最终值时 returns,这似乎是不允许的:

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
            function (x) {  // this anonymous function definition...
                var item = 'item' + list[x];
                result.push(
                    return function () {
                        console.log(item + ' ' + list[x]);
                    };
                );
            }(i);           // and simultaneous invocation...
    }
    return result;
}

而如果我将闭包完全移动到对 .push() 的调用中,则一切正常:

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
            result.push(
                function (x) {  // wrapper now inside the call to .push()
                    var item = 'item' + list[x];
                    return function () {
                        console.log(item + ' ' + list[x]);
                    };
                }(i)           // and called here...
            );
    }
    return result;
}

我想知道的是:当我定义将闭包立即包装在 for 循环内而不是在对 .push() 的调用内的匿名函数时,我违反了什么规则?

在第一种情况下,您推 return 函数声明而不是调用,这实际上是

result.push(return function(){...})

在第二种情况下,您正在推动 IIFE 函数执行的 return,return 是您的原始函数,因此它有效

result.push(function(){...})

显然,第二是你想要的。你可以先改成

result.push(
    function () {
        console.log(item + ' ' + list[x]);
    };
);

让它发挥作用。推动时没有return

根据 "not allowed",我假设解释器正在抱怨语法错误。在第一种情况下你有什么:

result.push(
    return function () {
        console.log(item + ' ' + list[x]);
    };
);

在语法上无效。

但即使你删除 return:

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
            function (x) {  // this anonymous function definition...
                var item = 'item' + list[x];
                result.push(
                    function () {
                        console.log(item + ' ' + list[x]);
                    }
                );
            }(i);           // and simultaneous invocation...
    }
}

您仍然会收到错误消息。这是因为您没有为 IIFE 指定括号,这意味着 function (x) { ... }() 被视为 declaration/statement 而不管尾随 ()。但是如果你是声明一个函数,你需要指定一个名称,这就是为什么 function 之后的 ( 是意想不到的。如果你想把它当作一个表达式,你必须把它包装在 (...) 中,因此使用 (function (x) { ... })().

在第二种情况下,result.push(...)的参数只能是一个表达式,所以function (x) { ... }()的解释方式没有歧义;它永远不能是声明,因此它必须是表达式(函数文字或 IIFE)。

作为类比,将 function() { ... } 想象成字符串 "hello"。您永远不能孤立地使用 "hello";以下代码在语法上无效:

var x = "foo";
"hello";

这基本上就是您使用匿名函数所做的事情:

var x = "foo";
function () {
}

该函数应该做什么?它没有分配给任何东西,就像前面示例中的 "hello" 没有分配给任何东西一样。但是我们知道可以调用函数,所以我们用 (function() { ... } ()) 做的就是说 "take this function I have defined here and then call it right now"。它类似于在字符串文字上调用方法:

"abcd".substring(0, 2); // returns "ab"

事实上,你可以用一个函数做一些类似的事情,我认为它更好地展示了 IIFE 正在发生的事情:

// logs "hello"
(function() { 
    console.log("hello");
}).call();

括号是一种消除歧义并告诉解释器您希望将函数视为 expression/literal 而不是声明的方法。在上面的例子中,如果你删除了周围的括号,你会得到关于意外 (.

的相同语法错误

包装器 (IIFE) 函数存在语法错误,并且在第一种情况下错误地放置了 return 语句。这是一个固定的(并稍作修改的)片段。

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
            (function (x) {  // this anonymous function declaration...
                var item = 'item' + list[x];
                result.push(
                    function () {
                        console.log(item + ' ' + list[x]);
                        return item + ' ' + list[x];
                    }
                );
            })(i);           // and simultaneous invocation...
    }
    return result;
}

buildList([1,2,3]).forEach(function(func) { 
  
  document.body.innerHTML += func() + '<br>';

});