Ошибка 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)
}

О нет! Я все еще создал функцию!