2015-07-08 2 views
22

Этот код журналы 6, 6 раз:Почему let и var bindings ведут себя по-другому, используя функцию setTimeout?

(function timer() { 
    for (var i=0; i<=5; i++) { 
    setTimeout(function clog() {console.log(i)}, i*1000); 
    } 
})(); 

Но этот код ...

(function timer() { 
    for (let i=0; i<=5; i++) { 
    setTimeout(function clog() {console.log(i)}, i*1000); 
    } 
})(); 

... входит следующий результат:

0 
1 
2 
3 
4 
5 

Почему?

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

+3

См. Здесь http://stackoverflow.com/questions/762011/javascript-let-keyword-vs-var-keyword – eithed

+0

Стоит отметить, что существует разница между старой версией Mozilla 'let' и новой ES2015 версия. Однако, для специфики этого вопроса, обманщик отвечает на это просто отлично. –

+3

На мой взгляд, это не дубликат. Каждый раз, когда кто-то спрашивает о let или var, мы не можем указать на них очень общий ответ. Это специально задает вопрос о setTimeout(), который создает «замыкание в цикле» - общий сценарий проблемы с подъемом var - ответ и пример ниже не указаны в связанном дублированном принятом ответе – Ryan

ответ

18

С var у вас есть область видимости функции, и только один общий обязательный для всех ваших итераций цикла - то есть i в каждом SetTimeout обратного вызова означает то же переменная, которая наконец равно 6 после цикла итерация заканчивается.

С let у вас есть блок сферы и при использовании в цикле for вы получите новый обязательный для каждой итерации - т.е. i в каждом SetTimeout обратного вызова означает другую переменную, каждый из которых имеет различное значение : первый равен 0, следующий 1 и т.д.

Так что это:

(function timer() { 
    for (let i = 0; i <= 5; i++) { 
    setTimeout(function clog() { console.log(i); }, i * 1000); 
    } 
})(); 

эквивалентно этому, используя только вар:

(function timer() { 
    for (var j = 0; j <= 5; j++) { 
    (function() { 
     var i = j; 
     setTimeout(function clog() { console.log(i); }, i * 1000); 
    }()); 
    } 
})(); 

Использование непосредственно вызываемого выражения функции для использования функции scope аналогично тому, как область видимости блока работает в примере с let.

Это может быть записано короче без использования имени j, но, возможно, не будет столь же ясно:

(function timer() { 
    for (var i = 0; i <= 5; i++) { 
    (function (i) { 
     setTimeout(function clog() { console.log(i); }, i * 1000); 
    }(i)); 
    } 
})(); 

И еще короче со стрелками функции:

(() => { 
    for (var i = 0; i <= 5; i++) { 
    (i => setTimeout(() => console.log(i), i * 1000))(i); 
    } 
})(); 

(Но если вы можете используйте функции стрелок, нет оснований для использования var.)

Вот как Babel.js переводит ваш пример с помощью let работать в средах, где let не доступен:

"use strict"; 

(function timer() { 
    var _loop = function (i) { 
    setTimeout(function clog() { 
     console.log(i); 
    }, i * 1000); 
    }; 

    for (var i = 0; i <= 5; i++) { 
    _loop(i); 
    } 
})(); 

Благодаря Michael Geary за размещение ссылки на Babel.js в комментариях. См. Ссылку в комментарии для демонстрации в реальном времени, где вы можете изменить что-либо в коде и посмотреть, что перевод происходит сразу. Интересно посмотреть, как можно перевести другие функции ES6.

+2

Просто чтобы добавить к вашему отличному объяснению, здесь является [перевод кода ES6 на ES5, предоставленный babeljs.io] (http://babeljs.io/repl/#?experimental=true&evaluate=true&loose=false&spec=false&code= (функция% 20timer()% 20% 7B% 0D% 0A% 20% 20for% 20 (пусть% 20i% 20% 3D% 200% 3B% 20i% 20% 3C% 3D% 205% 3B% 20i% 2B% 2B)% 20% 7B% 0D% 0A% 20 % 20% 20% 20setTimeout (функция% 20clog()% 20% 7B% 20console.log (я)% 3B% 20% 7D% 2C% 20i% 20% * 201000)% 3B% 0D% 0A% 20% 20% 7D% 0D% 0A% 7D)()% 3B). –

+0

@MichaelGeary Спасибо за ссылку. Я добавил перевод Вавилона на мой ответ. Благодарю. – rsp

+0

Здесь не применимо область охвата, это зависит от новой привязки, созданной на каждой итерации – seriousdev

2

Технически это объясняет @rsp в его превосходном ответе. Вот как мне нравится понимать, что все работает под капотом.Для первого блока кода с помощью var

(function timer() { 
    for (var i=0; i<=5; i++) { 
    setTimeout(function clog() {console.log(i)}, i*1000); 
    } 
})(); 

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

setTimeout(function clog() {console.log(i)}, i*1000); // first iteration, remember to call clog with value i after 1 sec 
setTimeout(function clog() {console.log(i)}, i*1000); // second iteration, remember to call clog with value i after 2 sec 
setTimeout(function clog() {console.log(i)}, i*1000); // third iteration, remember to call clog with value i after 3 sec 

и так далее

так i объявляется с использованием var, когда clog , компилятор находит переменную i в ближайшем функциональном блоке, который равен timer, и поскольку мы уже достигли конца for, i имеет значение 6 и выполняет clog. Это объясняет, что 6 регистрируются шесть раз.

 Смежные вопросы

  • Нет связанных вопросов^_^