Pull to refresh

Comments 51

Третий вариант:


const arr = [10, 12, 15, 21];
arr.forEach(function (item, i) {
    setTimeout(function () {
        console.log('Index: ' + i + ', element: ' + item);
    });
});

PS если уж и оставляли ссылку на SO — можно было бы и на русскоязычное объяснение сослаться. Например, на вот это: https://ru.stackoverflow.com/a/433888/178779

ещё покороче
setTimeout(function(item, i) {
    console.log('Index: ' + i + ', element: ' + item);
  }.bind(this, arr[i], i), 3000);

Вы цикл забыли, вот у вас и вышло "покороче". И, раз уж вы решили так делать — проще пойти через дополнительные параметры setTimeout:


const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
  setTimeout(function(i) {
    console.log('Index: ' + i + ', element: ' + arr[i]);
  }, 3000, i);
}
я его не забыл, а просто не стал писать, для наглядности — где я что поменял
извиняюсь, что-то я затупил и не увидел что у вас forEach, а не просто две обертки в цикле)
Из задания: в цикле for вызывается функция setTimeout, которой передаётся анонимная функция и значение задержки таймера. Когда заканчивает выполнятся функция setTimeout, анонимная функция остаётся жить и её можно запускать в другом месте кода (?) (т.е. замыкает на себя переменные всех внешних функций, но выполняется последней, — к тому моменту в [[scope]] i = 4).
mayorovp, можете пояснить что происходит здесь, для тех кто не пишет на JavaScript (не знаком с асинхронными функциями)?
Разобрался
Из задания: анонимная функция замыкает на себя переменные всех внешних функций. В цикле for с задержкой по таймеру вызывается функция setTimeout, поэтому анонимная функция вызывается когда цикл for уже завершён.

Из вашего примера: в функцию setTimeout добавили параметр i, который сохраняет изменённое значение для передачи анонимной функции (хоть setTimeout выполняется с задержкой по таймеру).
Неверно понял логику (По-русски описать нужно что происходит. Задание дали, а решение описали размыто и ткнули на справочник; тем кто пишет на JavaScript, — и так всё понятно). Похоже, все функции с задержкой асинхронные.

Более современный вариант:


const arr = [10, 12, 15, 21];
for (const [i, item] of arr.entries()) {
    setTimeout(function () {
        console.log(`Index: ${i}, element: ${item}`);
    });
}

Если уж использовать for-of и деструктуризацию, то и стрелочные функции тоже использовать можно :-)

На самом деле, даже стрелочные не нужны.


const arr = [10, 12, 15, 21];
for (const [i, item] of arr.entries()) {
    setTimeout(console.log, 0, `Index: ${i}, element: ${item}`);
}
Доки:
var timerId = setTimeout(func / code, delay[, arg1, arg2...])


Правда не сработает на <IE9

И касаемо 1 варианта — почему автор не сделал так? Есть какие то ограничения?

const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
(function(i){
  setTimeout(function() {
    console.log('Index: ' + i + ', element: ' + arr[i]);
  }, 3000);
})(i)
}
Это же то же самое, вид сбоку.
Нет, это совсем не то же самое. Тут в коллбэке setTimeout будет замыкаться не «i», объявленный в цикле for, а «i», являющийся параметром функции-обёртки. В итоге выведутся индексы от 0 до 3 и соответствующие им элементы.
И касаемо 1 варианта — почему автор не сделал так? Есть какие то ограничения?

Напомню, первый вариант — это то где setTimeout(function(i_local) { ... }(i), 3000)

А, ну тогда понятно о чём речь. Я почему-то подумал что первый вариант это тот который до ката. Каюсь, был невнимателен.
UFO just landed and posted this here
Да потому что пример специально написан с ошибкой, чтобы выяснить, понимает ли человек, как работают замыкания, или нет.
Вопрос то мой в другом — зачем в результате выполнения функция, а не выполнение функции в результате :)
UFO just landed and posted this here
UFO just landed and posted this here
«Никогда не было, и вот опять» (с)
Если авторам блога вчера исполнилось 14 или менее — то они могли что-то пропустить.
Вы таки не поверите, но до сих пор многие «сеньеры-помидоры», разглагольствующие про graphql, на этом вопросе сыпятся
UFO just landed and posted this here
В Java подобный код вообще не скомпилируется — потребует явно копировать в локальную переменную, по моему это правильно.
Вопрос был бы чуть более хитрым если бы в setTimeout таймаут было не 3000, а 0 (значение по умолчанию). Хитрость ведь не только в области видимости и замыканиях, а еще в понимании того что JS однопоточный и event loop блокировать очень нежелательно.
Я совсем не программирую на js, но мои познания в других языках позволили мне правильно ответить про замыкание (все вопросы про замыкания, сводятся к подобной формулировке).
А вот ваше уточнение про таймаут 0 и однопоточность не очень понятно, что мы получим?

Да тоже самое получаем, timeout работает по принципу "когда нибудь, но только не сейчас", т.е. если вы даже напишите -500, он сработает не раньше чем через один тик (зависит от браузера, но если не изменяет память минимальный таймаут 5-10мс).

так ничего же с 0 не изменится. А перенос времени вычисления вычислительно сложной задачи в рамках этого потока ничего не даст, всё равно луп залочится. Можно порезать задачку на куски и через performance.now отъедать не больше например 10 ms на итерацию, но это изврат. А вообще воркеры же есть. Но на самом деле не все задачи подходят.
Да ничего не измениться по сути, просто не все понимают как происходит планировка подобных «отложенных» задач в JS, а там тоже есть что обсудить. И вот как раз воркеры были бы к месту в обсуждении.
а мне вот такое решение кажется интересным.
const arr = [10, 12, 15, 21];
for (let i = 0; i < arr.length; i++) {
  setTimeout(function() {
    console.log('Index: ' + i + ', element: ' + arr[i]);
  }, 3000);
}

Вообще это не хитрый а самый что ни есть базовый вопрос по js на любую позицию кроме может совсем зелёного джуна. Если человек утверждает что знает JS и работает на нём профессионально, то незнание этих вещей означает полную профнепригодность в принципе. Скоупы, что их создаёт и как работает замыкание являются базовыми знаниями для языка который на этом построен чуть менее чем полностью.
Зашел написать этот же комментарий.
Еще можно спрашивать, чему равно 2+3*4, уровень сложности примерно такой же.
Только при чем тут гугль?
Что-то я разочарована, вполне рядовой вопрос на собесах в московских компаниях в течение уже нескольких лет. Сталкивалась и с такой формулировкой: «Как можно исправить данный пример? Напишите все способы, какие знаете»

К тому же кандидат мог почитать статьи о часто задаваемых вопросах на собеседованиях и тупо выучить как правильно ответить, все же не мешало бы просто отдельно спросить стандартные вопросы:
«Какие типы функций вы знаете и какие особенности у каждого?
Что такое замыкания и область видимости переменной?
Что такое setTimeout/setInterval, чем отличаются?»
… и тд.

Хмм. Следует ли из вышесказанного что в циклах, в которых мы не хотим создавать такие замыкания, var i будет работать чуть быстрее, чем let i, ведь интерпретатору не надо создавать новую переменную i на каждой итерации?


Производительность JS это, конечно, мутно, но я не настоящий сварщик.

Возьмем код


const arr = [10, 12, 15, 21];
for (let i = 0; i < arr.length; i++) {
  setTimeout(function() {
    console.log('The index of this number is: ' + i);
  }, 3000);
}

for (let i = 0; i < arr.length; i++) {
  console.log(i);
}

Вставим сюда и будет результат:


'use strict';

var arr = [10, 12, 15, 21];

var _loop = function _loop(i) {
  setTimeout(function () {
    console.log('The index of this number is: ' + i);
  }, 3000);
};

for (var i = 0; i < arr.length; i++) {
  _loop(i);
}

for (var i = 0; i < arr.length; i++) {
  console.log(i);
}

Видно, что когда лишние телодвижения не нужны, babel транслирует в код 1-в-1. В случае если нужно делать замыкание на внутреннюю переменную (как в задаче в примере) — ее создание неизбежно, поэтому как не крутите, память будет выделена. Думаю, движок хрома делает еще более оптимальный байткод, так что на производительность влиять не будет.

Ради интереса запустил цикл 1,000,000,000 раз в хроме, то с let работает быстрее.
console.time('loop');
for (let i = 0; i < 1000000000; i++) {}
console.timeEnd('loop');
// loop: 685.50390625ms

console.time('loop');
for (var i = 0; i < 1000000000; i++) {}
console.timeEnd('loop');
// loop: 2530.260986328125ms

Хитрый вопрос? Хитрее только "чему равен typeof null".
Вот вам еще хитрый вопрос для написания статьи на знание основ js:


function foo() {
    'use strict';
     console.log(bar());
     function bar() { return 'bar'};
}

Будет undefined, reference error или 'bar'?


Как вариант решения задачки из топика — даешь больше es6:


const arr = [10, 12, 15, 21];
arr.forEach((item, i) => setTimeout(function() {
    console.log('Index: ' + i + ', element: ' + item);
  }, 3000));

А вообще


Rx.Observable.from([10, 12, 15, 21]).delay(3000).do(console.log);
const arr = [10, 12, 15, 21];
arr.forEach((item, i) => setTimeout(_ => console.log('Index: ' + i + ', element: ' + item), 3000));
Я только изучаю JavaScript, ещё не дошёл до асинхронности, но уже подзабыл синтаксис for'а.
Вот такой код нормальный результат выдаёт:
const arr = [10, 12, 15, 21];
const iter = (i) => {
  if (i >= arr.length) {return ;}
  setTimeout(function() {
    console.log('Index: ' + i + ', element: ' + arr[i]);
  }, 3000);
  return iter(i + 1);
};
iter(0);
UFO just landed and posted this here
(к первому листингу и пояснению к нему)
Во-первых, почему последнее значение i — 4, а не 3?
Во-вторых, де-факто в консоли выводится иное…

В консоли выводится то же самое, просто некоторые консоли умеют отслеживать дублирующиеся сообщения и оставлять только одно.


А 4 выводится потому что после окончания цикла переменная i принимает именно это значение. На значении 3 цикл закончиться не может, потому что 3 < arr.length. Цикл заканчивается когда нарушается его условие — а оно нарушается когда i >= arr.length.

Да, сорри, какой-то не тот код выполнил. Своими глазами видел элементы массива в выводе.
Сам удивился. Спросоня. Эх, жаль карму…
Там var на let заменили, в комментарии выше. Не заметил :-) Ну про 4 тупанул, еще раз сорри :-) Чувствую себя первоклашкой :-(

У меня на собеседовании по Питону похожее спрашивали. В цикле создается список лямбд, каждая из которых ссылается на счетчик. Функции выполнятся уже когда счетчик будет максимальным, из-за лексической видимости будет напечатано последнее значение.

Был уверен что напечатается 4 раза последний элемент массива. Но после того как увидел правильный ответ, первая мысль — точно, это же for! Не знаю теперь кто я с точки зрения Amazon или Microsoft… for ведь такой же как в большинстве Си-подобных языков. Это не знание основ javascript или же не знание основ Си?)
А я вот сходу ошибся, потому что привык к Lua, а там замыкание на счетчики циклов иначе работает.

Вот такой код:
local cb = {}
local arr = {1,2,3,4}
local counter = 1

for i = 1, 4 do
  table.insert(cb, function ()
    print('index: ' .. i .. ', element: ' .. arr[i] .. ', counter:' .. counter)
  end)
  counter = counter + 1
end

for i = 1, #cb do
  cb[i]()
end


Даст вот такой результат:
index: 1, element: 1, counter:5
index: 2, element: 2, counter:5
index: 3, element: 3, counter:5
index: 4, element: 4, counter:5

Вопрос конечно хороший, но я один думаю, что асинхронные функции в цикле на практике лучше его не использовать из за неочевидного поведения?
Sign up to leave a comment.