在这里创建闭包的替代方法是什么?

What are the alternatives to creating a closure here?

假设我们有一个简单对象列表:

var things = [
  { id: 1, name: 'one' },
  { id: 2, name: 'two' },
  { id: 3, name: 'three' }
];

我们需要遍历这些对象并将它们注册为稍后事件的参数。朴素的方法在所有回调之间共享相同的对象引用,因此每个回调都会触发 last 项:

for (var i = 0; i < things.length; i++) {
  var o = things[i];
  setTimeout(function() { doSomethingWith(o); }, i * 1000);
}

一个典型的解决方案是创建一个闭包,限制我们对象引用的范围:

for (var i = 0; i < things.length; i++) {
  (function() {
    var o = things[i];
    setTimeout(function() { doSomethingWith(o); }, i * 1000);
  })();
}

如果我们不针对 IE<9,我们可以依赖 .forEach():

things.forEach(function(o, i) {
  var o = things[i];
  setTimeout(function() { doSomethingWith(o); }, i * 1000);
});

但是,无论如何,我们最终在这种情况下创建了一种闭包,将匿名函数传递给 forEach()

有没有办法在没有闭包或函数引用的情况下完成此操作?

根本问题有三方面:

  1. 不太重要的是:传递给setTimeout()(或任何它可能是什么)的函数引用让你(我)感觉 就像你正在创建一个闭包。所以,我倾向于忘记外部封闭。
  2. 更重要的是: 额外的 function/closure 声明鼓励 "arrow code." 对于复杂操作,随着代码从屏幕上迁移,复杂操作的代码可读性会迅速恶化...这可能由 IE>9 中的 .forEach() 解决,除非应用程序或组织风格指南规定了换行符和闭包缩进。
  3. 重要的是: 我很确定有一种简单的方法可以处理这个问题。我觉得现在想不起来很傻。

也许更好的提问方式是:在我们开始强制创建闭包之前,我们到底做了什么?

我不认为在这里使用闭包有什么问题。它们是 javascript 中的自然工具,并且对于具有本地状态的异步回调来说是相当必要的——因为我们想避免全局状态。

如果您非常在意缩进,可以将提供作用域的 IEFE 与循环放在同一行:

for (var i = 0; i < things.length; i++) (function() {
  var o = things[i];
  setTimeout(function() { doSomethingWith(o); }, i * 1000);
}());

否则,您已经使用 forEach 非常好。请注意,您无需在代码中关心 IE<=8,因为如果您想支持 forEach,它是微不足道的。

当然,ES6 会给我们 let 语句来解决这个 very common problem 用新的语法 - 你需要使用 6to5-transpiler:

for (let i = 0; i < things.length; i++) {
//   ^^^
  var o = things[i];
  setTimeout(function() { doSomethingWith(o); }, i * 1000);
}

如果你想要一个非常清晰的代码组织,让闭包显式:

function makeDoer(thing) {
    return function() { doSomethingWith(thing); };
}
for (var i = 0; i < things.length; i++) {
    setTimeout(makeDoer(things[i]), i*1000);
}

What the devil did we all do before we all started compulsively creating closures?

我们使用了全局状态,并以不同的方式解决了我们的问题。例如,您的案例将由半递归函数更好地解决:

var i = 0;
function next() {
    if (i < things.length) {
        doSomethingWith(things[i++]);
        setTimeout(next, 1000);
    }
}
next();

我弄清楚了,有两种不同的方法。 第一个,您 将参数 绑定到此方法的调用。 它在函数中克隆参数 things[i] 并将其用作参数。

for (var i = 0; i < things.length; i++) {
  var o = things[i];
  setTimeout(doSomethingWith.bind(null, things[i]), i * 1000);
} 

还有第二种方式,

setTimeout 在参数时间之后,以毫秒为单位,接受你将要调用的函数的参数,它也会复制定义时的值,所以变量值可以在之后改变,setTimeout 将保证正确值将作为参数传递。

for (var i = 0; i < things.length; i++) {
 var o = things[i];
 setTimeout(function(param) { doSomethingWith(param); }, i * 1000, o);
}

希望对您有所帮助!