Каковы альтернативы созданию закрытия здесь?

Предположим, у нас есть список простых объектов:

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

И нам нужно перебрать эти объекты и зарегистрировать их как параметры для какого-то более позднего события. Наивный подход использует одну и ту же ссылку на объект для всех обратных вызовов, поэтому каждый срабатывает для последнего элемента:

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. Что еще более важно: дополнительные объявления функций/замыканий поощряют "стрелочный код". Для сложных операций читабельность кода для сложных операций довольно быстро ухудшается, поскольку код мигрирует за пределы экрана ... Это потенциально решается с помощью .forEach() в IE> 9, если приложение или руководство по организационному стилю не диктует новую строку + отступ для закрытия.
  3. Самое главное: я был уверен, что есть простой способ справиться с этим. И я чувствую себя глупо из-за того, что не могу думать об этом прямо сейчас.

Может быть, лучше спросить так: Что, черт возьми, мы все делали до того, как начали навязчиво создавать замыкания?


person svidgen    schedule 24.02.2015    source источник


Ответы (2)


Я не думаю, что здесь есть что-то плохое в использовании замыканий. Они являются естественным инструментом в 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 утверждения, которые решают эту очень распространенную проблему с новым синтаксисом - вам нужно будет используйте транспилятор 6to5:

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();
person Bergi    schedule 24.02.2015
comment
Мы использовали глобальное состояние... Я думаю, это то, что я ищу. Я не мог уложить в голове, что мы использовали для управления такими вещами и почему я остановился. Но, я почти уверен, что это оно. Мы возились с множеством глобальных объектов. - person svidgen; 24.02.2015

Я так понимаю, это 2 разных способа. В первом случае вы привязываете параметр к вызову этого метода. Он клонирует параметр 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);
}

Надеюсь, поможет!

person mariodiniz    schedule 24.02.2015
comment
+1, но обратите внимание, что связанные функции также могут считаться замыканиями, а дополнительные параметры setTimeout поддерживаются не во всех браузерах. - person Bergi; 24.02.2015