在这里创建一个闭包有什么替代方案?

时间:2015-02-24 16:37:58

标签: javascript closures code-readability

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

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&lt; 9,我们可以依赖.forEach()

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

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

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

潜在的问题是三方面的:

  1. 不太重要:传递给setTimeout()的函数引用(或其可能的任何内容)会让您(我)感觉就像您正在创建关闭。所以,我倾向于忘记外封闭。
  2. 更重要的是:附加功能/闭包声明鼓励&#34;箭头代码。&#34;对于复杂的操作,复杂操作的代码可读性会随着代码从屏幕迁移而迅速恶化......这可能由IE中的.forEach()&gt; 9解决,除非应用程序或组织样式指南指示换行符+缩进闭合。
  3. 大多数非常重要:我很确定有一种简单的方法可以解决这个问题。我现在感觉不舒服。
  4. 也许更好的方式就是这样:在我们开始强制创建闭包之前,我们都做了什么恶魔?

2 个答案:

答案 0 :(得分:1)

我认为在这里使用闭包没有任何问题。它们是javascript中的自然工具,对于具有本地状态的异步回调非常必要 - 因为我们希望避免全局状态。

如果您非常关心缩进,可以将提供范围的IEFE放在与循环相同的行上:

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

否则,您已经完全使用forEach了。请注意,您不需要关心代码中的IE&lt; = 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);
}
  

在我们开始强制创造封闭之前,我们都做了什么恶魔?

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

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

答案 1 :(得分:1)

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

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

以第二种方式,

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

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

希望它有所帮助!