Переменные JavaScript объявляют вне или внутри цикла?

213

В AS3 я считаю, что вы должны инициализировать все переменные вне циклов для повышения производительности. Это касается и JavaScript? Что лучше / быстрее / лучшие практики?

var value = 0;

for (var i = 0; i < 100; i++)
{
    value = somearray[i];
}

или

for (var i = 0 ; i < 100; i++)
{
    var value = somearray[i];
}
davivid
источник
7
За пределами! всегда снаружи.
BGerrissen
37
Хм, разве объявления переменных в любом случае не помещаются в область видимости функции как в Javascript, так и в AS3? Если я прав, то это действительно не имеет значения.
спонсор
3
@ Энди - вы пытались назначить, прежде чем объявить в теле функции? Возможно, ваши предубеждения вводят вас в заблуждение. Производительность WRT, с областью действия push-up, если JS интерпретируется, то он будет жевать дополнительные циклы внутри блока цикла. Если скомпилировано (что большинство движков делают сегодня), это не будет иметь значения.
спонсор
2
Отличный вопрос! Спасибо. Прочитав все ответы, я считаю, что если это небольшой цикл или только временная переменная, я буду хранить их там, где они нужны, и это не повлияет на производительность. Если переменная используется внутри функции более одного раза, почему бы не обратиться к ней внутри функции, и, наконец, глобальные переменные могут быть размещены вне функции fn ()
Дин Михан
3
Я удивлен, что никто не пытался измерить производительность. Я создал JSperf . Кажется, немного быстрее, когда объявлено внутри цикла для Safari и Firefox, противоположность для Chrome…
Buzut

Ответы:

281

Нет абсолютно никакой разницы в значении или производительности, в JavaScript или ActionScript.

varэто директива для парсера, а не команда, выполняемая во время выполнения. Если конкретный идентификатор был объявлен varодин или несколько раз где-нибудь в теле функции (*), тогда все использование этого идентификатора в блоке будет ссылаться на локальную переменную. Не имеет значения, valueобъявлен ли онvar внутри цикла, вне цикла или в обоих.

Следовательно, вы должны написать то, что вы считаете наиболее читабельным. Я не согласен с Крокфордом в том, что ставить все переменные на вершину функции всегда лучше. Для случая, когда переменная временно используется в разделе кода, лучше объявить varв этом разделе, поэтому этот раздел стоит отдельно и может быть вставлен в копию. В противном случае скопируйте и вставьте несколько строк кода в новую функцию во время рефакторинга, не выделяя и не перемещая связанные элементы по отдельности var, и вы получите случайную глобальную переменную.

В частности:

for (var i; i<100; i++)
    do something;

for (var i; i<100; i++)
    do something else;

Крокфорд порекомендует вам убрать второе var(или удалить оба varс и var i;выше), и jslint за это вам захочет. Но ИМО более приемлемо, чтобы сохранить обаvar , сохраняя весь связанный код вместе, вместо того, чтобы иметь дополнительный, легко забываемый фрагмент кода в верхней части функции.

Лично я склонен объявлять как varпервое присвоение переменной в независимом разделе кода, независимо от того, есть ли другое отдельное использование того же имени переменной в некоторой другой части той же функции. Для меня необходимость объявления varвообще является нежелательной бородавкой JS (было бы лучше иметь переменные по умолчанию локальные); Я не считаю своим долгом дублировать ограничения [старой редакции] ANSI C в JavaScript.

(*: кроме как во вложенных функциональных телах)

bobince
источник
4
Я до сих пор не могу решить, я с Крокфордом или нет в этом. Объявление переменных внутри блоков похоже на использование операторов условных функций (что непослушно) ... Однако я также согласен с вашими замечаниями :) #confused
Daniel Vassallo
20
+1 Я не согласен с доводами Крокфорда (цитируемыми в ответе Дэниела), так как разработчикам JavaScript мы не должны писать код, чтобы другие программисты из семейства C могли его понять. Я часто использую var внутри if блоков и циклов, потому что это имеет больше смысла для меня.
Энди Э
4
-1 OP спрашивает, нужно ли объявлять переменные в теле цикла перед началом цикла. Значение индекса цикла является особым случаем (и поднимается) и не помогает ОП вообще.
mkoistinen
21
Петлевые индексы не являются особым случаем, они обрабатываются и поднимаются точно так же, как и обычные присваивания.
2010 года
31
+1 Крокфорд ошибается насчет этого (и других, но я отвлекся). Требование, varкоторое используется только в верхней части функции - это просто запрос на случайное создание глобальной переменной. И иметь массу несвязанных переменных, объявленных в одном месте, семантически бессмысленно, особенно когда некоторые из этих переменных могут в итоге никогда не использоваться.
MooGoo
64

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

Я не уверен насчет аргумента производительности, но Дуглас Крокфорд по- прежнему рекомендует, чтобы эти varоператоры были первыми в теле функции. Цитирование из условных обозначений кода для языка программирования JavaScript :

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

Я думаю, что у него есть точка, как вы можете видеть в следующем примере. Объявление переменных в верхней части функции не должно вводить читателей в заблуждение о том, что переменная i содержится в области forблока цикла:

function myFunction() {
  var i;    // the scope of the variables is very clear

  for (i = 0; i < 10; i++) {
    // ...
  }
}
Даниэль Вассалло
источник
8
+1 за то, что рассказал ОП правду о масштабах JS. Мне интересно, стоит ли занижать ответы, которые говорят иначе!
спонсор
1
@Kieranmaine: я не говорил, что это не влияет на производительность. Я только что привел аргумент в пользу того, чтобы поместить их вне циклов, не относящихся к производительности ... У меня нет ссылок на аргументы производительности, но вы также не привели ни одного в своем ответе :)
Даниэль Вассалло,
1
@Kieranmaine: у вас есть источник для этого?
Энди Э
5
@Kieranmaine: AFAIK, даже если вы объявите переменные внутри цикла, ecma- / javascriptувеличит их во время выполнения. Это называется «Подъем». Так что не должно быть никакой разницы.
Джэнди
1
Как ES6 letвлияет на этот ответ?
jbyrd
58

ECMA-/JavascriptЯзык hoistsлюбой переменной , которая объявлена в любом месте в верхней части функции. Это происходит потому , что этот язык действительно есть function scopeи не не имеют , block scopeкак и многие другие C-подобных языков.
Это также известно как lexical scope.

Если вы объявите что-то вроде

var foo = function(){
    for(var i = 0; i < 10; i++){
    }
};

Это получается hoisted:

var foo = function(){
    var i;
    for(i = 0; i < 10; i++){
    }
}

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

JANDY
источник
Тот же базовый ответ, что и принятый, но, IMO, более читаемый и примерно такой же информативный. Хорошая работа.
Энн Ганн
5
Как ES6 letвлияет на этот ответ?
Jbyrd
13

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

Кроме того, никогда не оптимизируйте для производительности, если вам не нужно. Держите переменные рядом с тем местом, где они вам нужны в первый раз, чтобы ваш код был чистым. С другой стороны, люди, привыкшие к языкам с блочными областями, могут быть сбиты с толку.

Аарон Дигулла
источник
6

Еще одно соображение, которое мы имеем letи constв ES2015, заключается в том, что теперь вы можете привязывать переменные конкретно к блоку цикла. Поэтому, если вам не понадобится одна и та же переменная вне цикла (или если каждая итерация зависит от операции, выполненной с этой переменной на предыдущей итерации), вероятно, предпочтительнее сделать это:

for (let i = 0; i < 100; i++) {
    let value = somearray[i];
    //do something with `value`
}
Мэтт Браун
источник
4

Я только что сделал простой тест в Chrome. Попробуйте скрипку в вашем браузере и посмотрите результаты

  var count = 100000000;
    var a = 0;
    console.log(new Date());

    for (var i=0; i<count; i++) {
      a = a + 1
    }

    console.log(new Date());

    var j;
    for (j=0; j<count; j++) {
      a = a + 1;
    }

    console.log(new Date());

    var j;
    for (j=0; j<count; j++) {
        var x;
        x = x + 1;
    }

    console.log(new Date());

В результате последний тест занимает ~ 8 секунд, а предыдущие 2 - всего ~ 2 секунды. Очень многократно и независимо от порядка.

Итак, это доказывает мне, что всегда следует объявлять переменные вне цикла. Любопытный случай для меня - это первый случай, когда я объявляю iв операторе for (). Похоже, что этот тест выполняется так же быстро, как и второй тест, в котором я предварительно объявляю индекс.

mkoistinen
источник
14
@KP: результаты подтверждаются только в том случае, если вы тестируете их самостоятельно или если их проверяет большое количество людей. @mkoistinen: я построил более справедливый тест, jsfiddle.net/GM8nk . После запуска сценария несколько раз в Chrome 5 я мог видеть, что не было последовательного победителя. Все три варианта показали лучшие результаты после нескольких обновлений. -1 от меня боюсь. Обратите внимание, вы можете запустить его в других браузерах. IE и Fx не понравились 100 миллионов итераций.
Энди Э
1
@AndyE. Вау, так что, исходя из этого простого теста, IE сосет в 100 раз больше? =)
mkoistinen
2
Результаты повсюду для меня, нет явного кросс-браузерного выигрыша, хотя иногда бывают значительные различия в скорости. Weird. Я думаю, что скрипка Энди - лучший тест, если поместить каждого кандидата в его собственную функцию ... конечно, если оригинальный скрипт запускается вне функции, он не должен ничего проверять, так как varобъявляет как глобальную переменную, которая будет быть глобальным в любом случае.
2010 года
4
Через год после свершившегося
sdleihssirhc
2
Это не мое, но я подумал, что некоторые из вас заинтересуются: jsperf.com/var-in-for-loop
m1.
1

JavaScript - это язык, написанный внизу на C или C ++, я не очень уверен, какой это. И одной из его целей является сохранение большого объема работы с внутренней памятью. Даже в C или C ++ вам не придется беспокоиться о том, будет ли он потреблять много ресурсов, когда переменные объявляются внутри цикла. Почему вы должны беспокоиться об этом в JavaScript?

Ян Ян
источник
1
C или C ++ могут быть в нижней части JavaScript. Но мы не должны забывать, что браузер конвертирует JavaScript в базовый язык (C, C ++). Так что производительность зависит от браузера
Kira
3
@Kira На самом деле он не конвертируется в C / C ++. Javascript компилируется в набор инструкций, которые выполняются средой выполнения JS (то есть виртуальной машиной, запущенной в браузере). Тот же принцип применим к другим динамическим языкам, таким как Python и Ruby.
Энтони Э
@AnthonyE, спасибо за информацию. Я не был уверен насчет конвертации JS в C или C ++. Так что я использовал, может быть, в моем комментарии
Кира
0

Ну, это зависит от того, чего вы пытаетесь достичь ... если valueпредположить, что в блоке цикла есть только временная переменная, тогда гораздо понятнее использовать вторую форму. Это также более логично и многословно.

Crozin
источник
Я обнаружил, что продвижение объявления всех переменных наверх - включая временные переменные - может на самом деле привести к путанице, так как оно просто становится «шумным».
Даниэль Соколовский
0

Не имеет значения, если вы объявляете переменные внутри или снаружи цикла for. Ниже приведен пример кода для тестирования.

function a() {
   console.log('Function a() starts');
   console.log(new Date());
    var j;
    for (j=0; j<100000000; j++) {
        var x;
        x = x + 1;
    }
    console.log(new Date());
    console.log('Function a() Ends');
}
a()
function b() {
console.log('Function B() starts');
   console.log(new Date());
    var a;
    var j;
    for (j=0; j<100000000; j++) {
      a = a + 1;
    }
    console.log(new Date());
    console.log('Function B() Ends');
}
b()

Результаты показали в моем случае

Function a() starts
VM121:3 Thu Apr 12 2018 15:20:26 GMT+0530 (India Standard Time)
VM121:9 Thu Apr 12 2018 15:20:26 GMT+0530 (India Standard Time)
VM121:10 Function a() Ends
VM121:14 Function B() starts
VM121:15 Thu Apr 12 2018 15:20:26 GMT+0530 (India Standard Time)
VM121:21 Thu Apr 12 2018 15:20:26 GMT+0530 (India Standard Time)
VM121:22 Function B() Ends

Спасибо - MyFavs.in

myfavs.in
источник
в обоих случаях вы объявляете j вне цикла! X_x
Джон Ктехик
Я попробовал это в Chromium 81 с letвместо varи, a()как правило, немного медленнее (например, 120 против 115 мс = ~ 6% = IMO незначительно)
mikiqex
-1

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

var a = 30;
var a = 50;
var a = 60;

Как вы думаете, это правильно? Нет ... потому что вы не хотите объявлять переменную столько раз. Когда вы объявляете переменную внутри цикла, разве она не объявляется столько раз, сколько выполняется цикл? Очевидно, это даст вам пощечину, когда вы находитесь в режиме «использования строгого режима». Люди не согласны с Крокфордом, не задумываясь о первоначальном вопросе.

Так что всегда хорошо объявлять переменные сверху - 1. Для удобства чтения, 2. Создание хороших привычек.

Вивек Поре
источник
1
«Когда вы объявляете переменную внутри цикла, разве она не объявляется столько раз, сколько выполняется цикл?» <- Нет, это не правильно. Объявление переменной поднимается, поэтому все, что вам остается, это присваивание.
Матиас
-2

Что касается производительности после запуска тестирования в Chrome, Firefox и jsperf в ОС Linux, то существует разница в производительности между объявлением переменных в цикле и вне цикла. Это небольшая разница, но она также осложняется количеством итераций и количеством объявлений переменных.

Поэтому для лучшей производительности я должен был бы предложить объявить переменные вне цикла. Или еще лучше объявить ваши переменные в строке. Смотрите пример.

// inline
for (var ai = 0, al = 100000000, av; ai < al; ai++) {
    av = av + 1;
}

// outside
var bv;
var bl = 100000000;
for (var bi = 0; bi < bl; bi++) {
    bv = bv + 1;
}

Обратите внимание, как переменные 'al' и 'av' находятся в строке объявления цикла for. Эта встроенная декларация обеспечила мне неизменно лучшую производительность. Даже за объявлением переменных вне цикла. Опять же разница в производительности очень мала.

https://jsperf.com/outside-inline-for-loop-ase/1

AlexanderElias
источник
Для меня ваш тест дал внутри цикла. И как бы то ни было, разница слишком мала, чтобы заключить, и принятый ответ ясно объясняет, что разницы нет
Ulysse BN
Поскольку объявления переменных поднимаются, разницы действительно нет.
Trincot