Ошибка JSlint "Не выполняйте функции внутри цикла". приводит к вопросу о самом Javascript
У меня есть код, который вызывает анонимные функции в цикле, что-то вроде этого псевдо-примера:
for (i = 0; i < numCards; i = i + 1) {
card = $('<div>').bind('isPopulated', function (ev) {
var card = $(ev.currentTarget);
....
JSLint сообщает об ошибке "Не выполняйте функции в цикле". Мне нравится держать мой код JSLint чистым. Я знаю, что могу переместить анонимную функцию из цикла и вызывать ее как именованную функцию. Что в стороне, вот мой вопрос:
Будет ли интерпретатор Javascript действительно создавать экземпляр функции для каждой итерации? Или действительно ли только один экземпляр функции "скомпилирован", и один и тот же код выполняется повторно? То есть, "предложение" JSLint для перемещения функции из цикла фактически влияет на эффективность кода?
Ответы
Ответ 1
Будет ли интерпретатор Javascript действительно создавать экземпляр функции для каждой итерации?
Это связано с тем, что он не знает, будет ли объект функции изменен в другом месте. Помните, что функции являются стандартными объектами JavaScript, поэтому они могут иметь свойства, подобные любому другому объекту. Когда вы это сделаете:
card = $('<div>').bind('isPopulated', function (ev) { ... })
для всего, что вам известно, bind
может изменить объект, например:
function bind(str, fn) {
fn.foo = str;
}
Очевидно, что это приведет к неправильному поведению, если объект функции был разделен на все итерации.
Ответ 2
Частично это зависит от того, используете ли вы выражение функции или объявление функции. Они разные, они происходят в разное время, и они оказывают иное влияние на окружающий объем. Поэтому начнем с различия.
Выражение функции - это function
production, где вы используете результат как правое значение — например, вы присваиваете результат переменной или свойству или передаете его в функцию как параметр и т.д. Это все выражения функций:
setTimeout(function() { ... }, 1000);
var f = function() { ... };
var named = function bar() { ... };
(Не используйте этот последний — который называется именованным функциональным выражением — реализация имеет ошибки, особенно IE.)
Напротив, это объявление функции:
function bar() { ... }
Это автономно, вы не используете результат как правое значение.
Два основных отличия между ними:
-
Выражения функций оцениваются там, где они встречаются в потоке программы. Объявления обрабатываются, когда управление входит в область содержимого (например, содержащая функция или глобальная область).
-
Имя функции (если она есть) определяется в области содержимого для объявления функции. Это не выражение функции (запрет ошибок браузера).
Ваши анонимные функции - это выражения функций, и поэтому запрет интерпретаторам, выполняющим оптимизацию (что он позволяет делать), они будут воссозданы в каждом цикле. Таким образом, ваше использование прекрасное, если вы думаете, что реализация будет оптимизирована, но разбивка на именованную функцию имеет другие преимущества и — главное — не стоит вам ничего. Кроме того, см. ответ casablanca для заметки о том, почему интерпретатор не может оптимизировать воссоздание функции на каждой итерации в зависимости от того, насколько глубоко она проверяет ваш код.
Большая проблема будет заключаться в том, что вы использовали объявление функции в цикле, тело условного и т.д.:
function foo() {
for (i = 0; i < limit; ++i) {
function bar() { ... } // <== Don't do this
bar();
}
}
Технически, тесное чтение спецификации грамматики показывает, что это недействительно, хотя практически никакой реализации на самом деле это не делает. То, что делает выполнение, варьируется, и лучше избегать его.
Для моих денег лучше всего использовать объявление одной функции, например:
function foo() {
for (i = 0; i < limit; ++i) {
bar();
}
function bar() {
/* ...do something, possibly using 'i'... */
}
}
Вы получаете тот же результат, нет никакой возможности, что реализация создаст новую функцию в каждом цикле, вы получите преимущество функции, имеющей имя, и вы ничего не теряете.
Ответ 3
Интерпретатор может фактически создать новый объект функции с каждой итерацией, хотя бы потому, что эта функция может быть замыканием, которое должно захватывать текущее значение любой переменной в ее внешней области.
Вот почему JSLint
хочет отпугнуть вас от создания многих анонимных функций в узком цикле.
Ответ 4
Boo для JSLint. Это как тупой инструмент на голове. Новый объект функции создается каждый раз, когда встречается function
(это выражение/выражение, а не объявление - edit: это белая ложь. См. Ответы T.J. Crowders). Обычно это делается в цикле для закрытия и т.д. Большая проблема заключается в создании ложных замыканий.
Например:
for (var i = 0; i < 10; i++) {
setTimeout(function () {
alert(i)
}, 10)
}
приведет к "нечетному" поведению. Это не проблема с "созданием функции в цикле, так как не понимает правила, используемые JS для областей видимости переменных и закрытий (переменные не привязаны к закрытию, области действия - контексты выполнения - есть).
Однако вы можете создать закрытие функции. Рассмотрим этот менее удивительный код:
for (var i = 0; i < 10; i++) {
setTimeout((function (_i) {
return function () {
alert(_i)
}
})(i), 10)
}
О нет! Я все еще создал функцию!