Ошибка JSlint «Не создавайте функции в цикле». приводит к вопросу о самом Javascript - PullRequest
28 голосов
/ 13 октября 2010

У меня есть код, который вызывает анонимные функции внутри цикла, что-то вроде этого псевдо-примера:

for (i = 0; i < numCards; i = i + 1) {
    card = $('<div>').bind('isPopulated', function (ev) {
        var card = $(ev.currentTarget);
        ....

JSLint сообщает об ошибке «Не выполняйте функции в цикле». Мне нравится держать мой код JSLint в чистоте. Я знаю, что могу вывести анонимную функцию из цикла и вызвать ее как именованную функцию. Кроме того, вот мой вопрос:

Будет ли интерпретатор Javascript действительно создавать экземпляр функции за одну итерацию? Или действительно только один экземпляр функции «скомпилирован» и один и тот же код выполняется неоднократно? То есть действительно ли «предложение» JSLint по перемещению функции из цикла фактически влияет на эффективность кода?

Ответы [ 4 ]

42 голосов
/ 13 октября 2010

Частично это зависит от того, используете ли вы выражение функции или объявление функции .Это разные вещи, они случаются в разное время и по-разному влияют на окружающую сферу.Итак, давайте начнем с различия.

Функция выражение - это function производство, где вы используете результат в качестве значения правой руки - например, вы присваиваете результатв переменную или свойство, или передачу его в функцию в качестве параметра и т. д. Это все функции выражения :

setTimeout(function() { ... }, 1000);

var f = function() {  ... };

var named = function bar() { ... };

(не используйте этот последний - которыйназывается выражением именованной функции - в реализациях есть ошибки, в частности IE .)

Напротив, это объявление функции :

function bar() { ... }

Это автономно, вы не используете результат в качестве значения правой руки.

Два основных различия между ними:

  1. Выражения функций оцениваются там, где они встречаются в потоке программы.Объявления оцениваются, когда элемент управления входит в содержащую область (например, содержащую функцию или глобальную область).

  2. Имя функции (если она есть) определяется в содержащейобласть действия для функции объявление .Это не для функции выражение (исключая ошибки браузера).

Ваши анонимные функции - выражения функций , и, таким образом, запрет интерпретатору делаетОптимизация (что бесплатно), они будут воссозданы в каждом цикле.Поэтому вы можете использовать его хорошо, если думаете, что реализации оптимизируют, но разбиение его на именованную функцию имеет другие преимущества и, что немаловажно, ничего не стоит.Кроме того, см. ответ Касабланки , где приведено примечание о том, почему интерпретатор не может в состоянии оптимизировать воссоздание функции на каждой итерации в зависимости от того, насколько глубоко он проверяет ваш код.

Большая проблема была бы, если бы вы использовали функцию объявление в цикле, тело условия и т. Д.:

function foo() {
    for (i = 0; i < limit; ++i) {
        function bar() { ... } // <== Don't do this
        bar();
    }
}

Технически, подробное чтение спецификацииграмматика показывает, что это недопустимо для этого, хотя практически ни одна реализация на самом деле не обеспечивает это.Чем отличаются реализации do , и лучше держаться от них подальше.

За мои деньги лучше всего использовать объявление одной функции, например:

function foo() {
    for (i = 0; i < limit; ++i) {
        bar();
    }

    function bar() {
        /* ...do something, possibly using 'i'... */
    }
}

Вы получаете тот же результат, нет никакой возможности, что реализация создаст новую функцию в каждом цикле, вы получите преимущество функции, имеющей имя , и вы ничего не потеряете.

24 голосов
/ 13 октября 2010

Будет ли интерпретатор Javascript действительно создавать экземпляр функции за одну итерацию?

Это необходимо, поскольку он не знает, будет ли объект функции изменен в другом месте.Помните, что функции являются стандартными объектами JavaScript, поэтому они могут иметь свойства, как и любой другой объект.Когда вы делаете это:

card = $('<div>').bind('isPopulated', function (ev) { ... })

для всех, что вы знаете, bind может изменить объект, например:

function bind(str, fn) {
  fn.foo = str;
}

Очевидно, это приведет к неправильному поведению, если объект функциибыл распространен на всех итерациях.

2 голосов
/ 13 октября 2010

Boo to JSLint.Это как тупой инструмент на голове.Новый объект функции создается каждый раз, когда встречается function (это утверждение / выражение, а не объявление - edit: это ложь белого цвета. См. Ответы TJ 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)
}

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

2 голосов
/ 13 октября 2010

Интерпретатор может фактически создавать новый объект функции с каждой итерацией, хотя бы потому, что эта функция может быть замыканием, которое должно захватывать значение current любой переменной во внешней области.

Вот почему JSLint хочет отпугнуть вас от создания множества анонимных функций в тесном цикле.

...