Изначально эта статья задумывалась как небольшой бенчмарк для собственного использования, да и вообще статьёй быть не планировалась, однако, в процессе проведения замеров всплыли некоторые интересные особенности в реализации архитектуры JavaScript, сильно влияющие на производительность конечного кода в отдельных случаях. Предлагаю, и вам, ознакомится с полученными результатами, попутно также разобрав некоторые смежные темы: циклы for, окружение (контекст выполнения) и блоки.

В конце своей статьи «Использование let объявлений переменных и особенности образуемых при этом замыканий в JavaScript» я вскользь затронул тему сравнения производительности let (LexicalDeclaration) и var (VarDeclaredNames) объявлений переменных в циклах. Для сравнения мы использовали время выполнения ручной (без помощи Array.prototype.sort()) сортировки массива, так как при его длине в 100 000 мы получили чуть более 5млрд. итераций за два цикла (внешнего и вложенного), и, данное количество должно было позволить произвести адекватную оценку в итоге.

Для var это была сортировка вида:

for (var i = 0, len = arr.length; i < len-1; i++) {
    var min = arr[i],
        mini = i,
        tmp;

    for (var j = i+1; j < len; j++) {
        if (min > arr[j]) {
            min = arr[j];
            mini = j;
        }
    }

    tmp = arr[i];
    arr[i] = min;
    arr[mini] = tmp;
}
// время выполнения Firefox: 9.082 сек.
// время выполнения Chrome: 10.783 сек.

И для let:

for (let i = 0, len = arr.length; i < len-1; i++) {
    let min = arr[i],
        mini = i,
        tmp;

    for (let j = i+1; j < len; j++) {
        if (min > arr[j]) {
            min = arr[j];
            mini = j;
        }
    }

    tmp = arr[i];
    arr[i] = min;
    arr[mini] = tmp;
}
// время выполнения Firefox: 5.261 сек.
// время выполнения Chrome: 5.391 сек.

Увидев эти цифры, казалось бы, можно однозначно утверждать, что let объявления полностью превосходят var в скорости работы. Но, помимо данного вывода, в воздухе остался подвешен вопрос, а что будет, если вынести объявления let за пределы циклов for?

Но, прежде, чем сделать это, необходимо глубже вникнуть в работу цикла for, руководствуясь текущей спецификацией ECMAScript 2019 (ECMA-262) :

13.7.4.7Runtime Semantics: LabelledEvaluation
With parameter labelSet.
IterationStatement:for(Expression;Expression;Expression)Statement
    1. If the first Expression is present, then
        a. Let exprRef be the result of evaluating the first Expression.
        b. Perform ? GetValue(exprRef).
    2. Return ? ForBodyEvaluation(the second Expression, the third Expression, Statement, « », labelSet). 
IterationStatement:for(varVariableDeclarationList;Expression;Expression)Statement
    1. Let varDcl be the result of evaluating VariableDeclarationList.
    2. ReturnIfAbrupt(varDcl).
    3. Return ? ForBodyEvaluation(the first Expression, the second Expression, Statement, « », labelSet). 
IterationStatement:for(LexicalDeclarationExpression;Expression)Statement
    1. Let oldEnv be the running execution context's LexicalEnvironment.
    2. Let loopEnv be NewDeclarativeEnvironment(oldEnv).
    3. Let loopEnvRec be loopEnv's EnvironmentRecord.
    4. Let isConst be the result of performing IsConstantDeclaration of LexicalDeclaration.
    5. Let boundNames be the BoundNames of LexicalDeclaration.
    6. For each element dn of boundNames, do
        a. If isConst is true, then
            i. Perform ! loopEnvRec.CreateImmutableBinding(dn, true).
        b. Else,
            i. Perform ! loopEnvRec.CreateMutableBinding(dn, false).
    7. Set the running execution context's LexicalEnvironment to loopEnv.
    8. Let forDcl be the result of evaluating LexicalDeclaration.
    9. If forDcl is an abrupt completion, then
        a. Set the running execution context's LexicalEnvironment to oldEnv.
        b. Return Completion(forDcl).
    10. If isConst is false, let perIterationLets be boundNames; otherwise let perIterationLets be « ».
    11. Let bodyResult be ForBodyEvaluation(the first Expression, the second Expression, Statement, perIterationLets, labelSet).
    12. Set the running execution context's LexicalEnvironment to oldEnv.
    13. Return Completion(bodyResult).

Здесь, как мы видим, присутствуют три варианта вызова и дальнейшей работы цикла for:
  • при for(Expression;Expression;Expression)Statement
    ForBodyEvaluation(the second Expression, the third Expression, Statement, « », labelSet).
  • при for(varVariableDeclarationList;Expression;Expression)Statement
    ForBodyEvaluation(the first Expression, the second Expression, Statement, « », labelSet).
  • при for(LexicalDeclarationExpression;Expression)Statement
    ForBodyEvaluation(the first Expression, the second Expression, Statement, perIterationLets, labelSet)

В последнем, третьем варианте, в отличие от первых двух, 4-ый параметр, не пустой — perIterationLets – это собственно и есть те самые let-объявления в первом, передаваемом циклу for, параметре. Они задаются в пункте 10:
— If isConst is false, let perIterationLets be boundNames; otherwise let perIterationLets be « ».
В случае, если в for была передана константа, а не переменная, параметр perIterationLets становится пустым.

Также, в третьем варианте, необходимо обратить внимание на пункт 2:
— Let loopEnv be NewDeclarativeEnvironment(oldEnv).

8.1.2.2NewDeclarativeEnvironment ( E )
When the abstract operation NewDeclarativeEnvironment is called with a Lexical Environment as argument E the following steps are performed:
1.	Let env be a new Lexical Environment.
2.	Let envRec be a new declarative Environment Record containing no bindings.
3.	Set env's EnvironmentRecord to envRec.
4.	Set the outer lexical environment reference of env to E.
5.	Return env. 

Здесь, в качестве параметра E принимается окружение из которого был вызван цикл for (глобальное, какая-либо функция и т. п.), и, создаётся новое окружение для выполнения цикла for со ссылкой на внешнее окружение, его создавшее (пункт 4). Нас интересует данный факт в связи с тем, что окружение это контекст выполнения.

А мы помним, что let и const объявления переменных контекстно привязаны к блоку в котором они объявлены.

13.2.14Runtime Semantics: BlockDeclarationInstantiation ( code, env )
Note
When a Block or CaseBlock is evaluated a new declarative Environment Record is created and bindings for each block scoped variable, constant, function, or class declared in the block are instantiated in the Environment Record.
BlockDeclarationInstantiation is performed as follows using arguments code and env. code is the Parse Node corresponding to the body of the block. env is the Lexical Environment in which bindings are to be created.
    1. Let envRec be env's EnvironmentRecord.
    2. Assert: envRec is a declarative Environment Record.
    3. Let declarations be the LexicallyScopedDeclarations of code.
    4. For each element d in declarations, do
        a. For each element dn of the BoundNames of d, do
            i. If IsConstantDeclaration of d is true, then
                1. Perform ! envRec.CreateImmutableBinding(dn, true).
            ii. Else,
                1. Perform ! envRec.CreateMutableBinding(dn, false).
        b. If d is a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then
            i. Let fn be the sole element of the BoundNames of d.
            ii. Let fo be the result of performing InstantiateFunctionObject for d with argument env.
            iii. Perform envRec.InitializeBinding(fn, fo).

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

Идём дальше, и, рассмотрим, что же собой представляет ForBodyEvaluation:

13.7.4.8Runtime Semantics: ForBodyEvaluation ( test, increment, stmt, perIterationBindings, labelSet )
The abstract operation ForBodyEvaluation with arguments test, increment, stmt, perIterationBindings, and labelSet is performed as follows:
    1. Let V be undefined.
    2. Perform ? CreatePerIterationEnvironment(perIterationBindings).
    3. Repeat,
        a. If test is not [empty], then
            i. Let testRef be the result of evaluating test.
            ii. Let testValue be ? GetValue(testRef).
            iii. If ToBoolean(testValue) is false, return NormalCompletion(V).
        b. Let result be the result of evaluating stmt.
        c. If LoopContinues(result, labelSet) is false, return Completion(UpdateEmpty(result, V)).
        d. If result.[[Value]] is not empty, set V to result.[[Value]].
        e. Perform ? CreatePerIterationEnvironment(perIterationBindings).
        f. If increment is not [empty], then
            i. Let incRef be the result of evaluating increment.
            ii. Perform ? GetValue(incRef).

На что надо в первую очередь обратить внимание:
  • описание входящих параметров:
    • test: выражение проверяемое на истинность перед выполнением очередной итерации тела цикла (например: i < len);
    • increment: выражение вычисляемое в начале каждой новой итерации (кроме первой) (например: i++);
    • stmt: тело цикла;
    • perIterationBindings: переменные объявленные с помощью let в первом параметре for (например: let i = 0 || let i || let i, j);
    • labelSet: метка цикла;
  • пункт 2: здесь, в случае, если передан непустой параметр perIterationBindings, для выполнения начального прохода цикла создаётся второе окружение;
  • пункт 3.a: проверка на заданное условие продолжения выполнения цикла;
  • пункт 3.b: выполнение тела цикла;
  • пункт 3.e: создание нового окружения.

Ну, и, непосредственно, алгоритм создания внутренних окружений цикла for:

13.7.4.9Runtime Semantics: CreatePerIterationEnvironment ( perIterationBindings )
1. The abstract operation CreatePerIterationEnvironment with argument perIterationBindings is performed as follows:
    1. If perIterationBindings has any elements, then
        a. Let lastIterationEnv be the running execution context's LexicalEnvironment.
        b. Let lastIterationEnvRec be lastIterationEnv's EnvironmentRecord.
        c. Let outer be lastIterationEnv's outer environment reference.
        d. Assert: outer is not null.
        e. Let thisIterationEnv be NewDeclarativeEnvironment(outer).
        f. Let thisIterationEnvRec be thisIterationEnv's EnvironmentRecord.
        g. For each element bn of perIterationBindings, do
            i. Perform ! thisIterationEnvRec.CreateMutableBinding(bn, false).
            ii. Let lastValue be ? lastIterationEnvRec.GetBindingValue(bn, true).
            iii. Perform thisIterationEnvRec.InitializeBinding(bn, lastValue).
        h. Set the running execution context's LexicalEnvironment to thisIterationEnv.
    2. Return undefined. 

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

В качестве примера рассмотрим подобное выражение:

let arr = [];

for (let i = 0; i < 3; i++) {
    arr.push(i);
}

console.log(arr); // Array(3) [ 0, 1, 2 ]

А вот как его можно разложить без использования for (с известной долей условности):

let arr = [];

// создаётся первый контекст
{
    let i = 0; // первый параметр передаваемый в for
}

// первый проход цикла, второй контекст
{
    let i = 0; // последнее значение переменной i из предыдущего окружения
    if (i < 3) arr.push(i);
}

// второй проход цикла
{
    let i = 0; // последнее значение переменной i из предыдущего окружения
    i++;
    if (i < 3) arr.push(i);
}

// третий проход цикла
{
    let i = 1; // последнее значение переменной i из предыдущего окружения
    i++;
    if (i < 3) arr.push(i);
}

// четвертый проход цикла
{
    let i = 2; // последнее значение переменной i из предыдущего окружения
    i++;
    if (i < 3) arr.push(i);
}

console.log(arr); // Array(3) [ 0, 1, 2 ]

Фактически мы приходим к тому, что на каждый контекст, а тут у нас их пять, мы делаем новые привязки для let переменных объявленных первым параметром в for (важно: это не распространяется на let объявления непосредственно в теле цикла).

Вот как, например, будет выглядеть данный цикл при использовании var, когда отсутствуют дополнительные привязки:

let arr2 = [];

var i = 0;
if (i < 3) arr.push(i);

i++;
if (i < 3) arr.push(i);

i++;
if (i < 3) arr.push(i);

i++;
if (i < 3) arr.push(i);

console.log(arr); // Array(3) [ 0, 1, 2 ]

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

Давайте попробуем сделать это, на примере всё той же сортировки массива на 100 000 элементов, и для красоты также вынесем перед for определение всех остальных переменных:

let i, j, min, mini, tmp, len = arr.length;

for (i = 0; i < len-1; i++) {
    min = arr[i];
    mini = i;

    for (j = i+1; j < len; j++) {
        if (min > arr[j]) {
            min = arr[j];
            mini = j;
        }
    }

    tmp = arr[i];
    arr[i] = min;
    arr[mini] = tmp;
}
// время выполнения Firefox: 34.246 сек.
// время выполнения Chrome: 10.803 сек.

Неожиданный результат… Прямо противоположный ожидаемому, если быть точным. Особенно поражает просадка Firefox в данном тесте.

Ок. Это не сработало, давайте вернём объявление i и j переменных обратно в параметры соответствующих циклов:

let min, mini, tmp, len = arr.length;

for (let i = 0; i < len-1; i++) {
    min = arr[i];
    mini = i;

    for (let j = i+1; j < len; j++) {
        if (min > arr[j]) {
            min = arr[j];
            mini = j;
        }
    }

    tmp = arr[i];
    arr[i] = min;
    arr[mini] = tmp;
}
// время выполнения Firefox: 6.575 сек.
// время выполнения Chrome: 6.749 сек.

Хм. Казалось бы, технически, единственное отличие последнего примера от примера в начале статьи — это вынесенные объявления переменных min, mini, tmp и len за пределы цикла for и хоть контекстно разница всё-таки присутствует, но она сейчас нас не особо интересует, и, кроме того, мы избавились от необходимости 99 999 раз объявлять эти переменные в теле цикла верхнего уровня, что опять же, в теории, скорее должно увеличить производительность, а не снизить его более чем на секунду.

То есть, получается, что каким-то образом, работа с переменными объявленными в параметре или теле цикла for происходит существенно быстрее, чем вне его.

Но, мы вроде не видели в спецификации к циклу for каких-либо «турбо» инструкций, которые могли бы навести нас на подобную мысль. Следовательно, дело не в специфике работы конкретно цикла for, а в чём-то другом… Например, в особенностях работы let объявлений: какая основная черта отличает let от var? Блочный контекст выполнения! А в двух наших последних примерах мы использовали объявления вне блока. Но, что если вместо переноса этих объявлений обратно в for мы просто выделим отдельный блок для них?

{
    let i, j, min, mini, tmp, len = arr.length;

    for (i = 0; i < len-1; i++) {
        min = arr[i];
        mini = i;

        for (j = i+1; j < len; j++) {
            if (min > arr[j]) {
                min = arr[j];
                mini = j;
            }
        }

        tmp = arr[i];
        arr[i] = min;
        arr[mini] = tmp;
    }
}
// время выполнения Firefox: 5.262 сек.
// время выполнения Chrome: 5.405 сек.

Вуаля! Выходит, загвоздка была в том, что let объявления происходили в глобальном контексте, и, стоило нам выделить под них отдельный блок, все проблемы тут же исчезли.

И тут было бы неплохо вспомнить про другой, немного незаслуженно, обруганный способ объявлений переменных – var.

В примере в начале статьи время сортировки с использованием var показало крайне плачевный результат, относительно let. Но, если присмотреться к этому примеру внимательней, можно обнаружить, что ввиду отсутствия у var переменных блочных привязок, фактический контекст переменных был глобальным. А мы, на примере let, уже обнаружили, как это может влиять на производительность (и, что характерно, при использовании let, просадка в скорости оказалась сильнее чем в случае с var, особенно в Firefox). Поэтому, справедливости ради, выполним пример с var создав для переменных новый контекст:

function test() {
    var i, j, min, mini, tmp, len = arr.length;

    for (i = 0; i < len-1; i++) {
        min = arr[i];
        mini = i;

        for (j = i+1; j < len; j++) {
            if (min > arr[j]) {
                min = arr[j];
                mini = j;
            }
        }

        tmp = arr[i];
        arr[i] = min;
        arr[mini] = tmp;
    }
}

test();
// время выполнения Firefox: 5.255 сек.
// время выполнения Chrome: 5.411 сек.

И, мы получили результат практически идентичный тому, что был при использовании let.

Напоследок, давайте проверим, происходит ли замедление работы от чтения глобальной переменной без изменения её значения.

let

let len = arr.length;

for (let i = 0; i < len-1; i++) {
    let min = arr[i],
        mini = i,
        tmp;

    for (let j = i+1; j < len; j++) {
        if (min > arr[j]) {
            min = arr[j];
            mini = j;
        }
    }

    tmp = arr[i];
    arr[i] = min;
    arr[mini] = tmp;
}
// время выполнения Firefox: 5.262 сек.
// время выполнения Chrome: 5.391 сек.

var

var len = arr.length;

function test() {
    var i, j, min, mini, tmp;

    for (i = 0; i < len-1; i++) {
        min = arr[i];
        mini = i;

        for (j = i+1; j < len; j++) {
            if (min > arr[j]) {
                min = arr[j];
                mini = j;
            }
        }

        tmp = arr[i];
        arr[i] = min;
        arr[mini] = tmp;
    }
}

test();
// время выполнения Firefox: 5.258 сек.
// время выполнения Chrome: 5.439 сек.

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

Подведём итоги


  1. Изменение глобальных переменных происходит значительно медленней, чем изменение локальных. Принимая это во внимание, можно оптимизировать код в соответствующих ситуациях, создав отдельный блок или функцию, в том числе, для объявления переменных, вместо выполнения части кода в глобальном контексте. Да, почти в любом учебнике можно встретить рекомендации делать как можно меньше глобальных привязок, но, обычно в качестве причины указывается, только засорение глобального пространства имён, и, ни слова про возможные проблемы с производительностью.
  2. Несмотря на то, что выполнение циклов с let объявлением в первом параметре for создаётся большое количество окружений, на производительности это практически не сказывается, в отличие от ситуаций, когда мы выносим подобные объявления за пределы блока. Тем не менее, не стоит исключать вероятность существования экзотических ситуаций, когда данный фактор скажется на производительности более существенно.
  3. Производительность var переменных, всё-таки не уступает производительности let переменных, однако и не превосходит их (опять же, в общем случае), что подводит нас к очередному выводу об отсутствии целесообразности использования var объявлений кроме как в целях совместимости. Однако, если необходимо манипулировать глобальными переменными с изменением их значений, вариант с var в плане производительности будет предпочтительней (по крайней мере на текущий момент, если, в частности, предполагается, что скрипт может также запускаться на движке Gecko).

Ссылки


ECMAScript 2019 (ECMA-262)
Использование let объявлений переменных и особенности образуемых при этом замыканий в JavaScript

Комментарии (16)


  1. ukbpyh Автор
    30.09.2019 16:19

    Я сильно извиняюсь перед теми, кто уже успел прочитать статью. Дело в том, что уже опубликовав я обратил внимание, на то, что при использовании let объявлений, просадка в скорости оказалась сильнее чем в случае с var, особенно в FireFox. Соответственно я внёс в статью соответствующую отсылку на данный факт и изменил 3-ий пункт выводов, так как, с учетом этого обнаруженного нюанса в случаях необходимости использования переменных с глобальным контекстом вариант с var видится предпочтительным. Спасибо!


  1. CoolCmd
    30.09.2019 17:58

    Напомню, что браузер называется Firefox, а не FireFox. Это название животного.


    И стандарт ECMA читать не обязательно. Он написан для разработчиков интерпретатора. Простым смертным подойдут более простые определения, хотя бы из MDN.


    Я бы не стал делать выводы по результатам одного простенького примера. JIT — это такая загадочная штука, что буквально смена строк местами может повернуть производительность с ног на голову. Причем в разных версиях одного браузера может быть по разному. В моем проекте замена var на let и const давала в разных браузерах противоположный результат.


    Конкретный пример, в котором у Firefox выполнение увеличилось до 30 сек, можно закинуть в Bugzill-у.


    1. ukbpyh Автор
      30.09.2019 18:23
      +1

      Спасибо, название браузера подправил.

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

      По поводу выводов относительно простенького примера. Исходя из нескольких прогонов в разных конфигурациях, которые приведены в статье, очевидна корреляция между количеством обращений к переменным в глобальной области видимости и соответствующим снижением производительности. Я проверял помимо этого еще на Watefox'е, и, во всех трёх браузерах была результаты согласовывались. И, да в Watefox'е тоже было больше 30 секунд, и, я понимаю, что он является форком Firefox.

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


  1. BigDflz
    30.09.2019 23:30

    хотелось бы услышать про такое:
    есть

    <div id='xxx' ></div>

    возможны 2 варианта:
    xxx.innerHTML='bla-bla';

    и
    var/let  d;
    d = document.querySelector('#xxx');
    d.innerHTML='bla-bla';


    который из них предпочтительнее и почему?
    и ведь возможно
    var xxx=333;

    я ни где не встречал предупреждения о том, что id и глобальные переменные не должны пересекаться


    1. ukbpyh Автор
      01.10.2019 00:36

      Дэвид Флэнаган — JavaScript. Подробное руководство (6-е издание) — 2012

      14.7. Элементы документа как свойства окна

      Если для именования элемента в HTML-документе используется атрибут id и
      если объект Window еще не имеет свойства, имя которого совпадает со значением
      этого атрибута, объект Window получает неперечислимое свойство с именем,
      соответствующим значению атрибута id, значением которого становится объект HTMLEle-
      ment, представляющий этот элемент документа.

      Как вы уже знаете, объект Window играет роль глобального объекта, находящегося
      на вершине цепочки областей видимости в клиентском JavaScript. Таким
      образом, вышесказанное означает, что атрибуты id в HTML-документах становятся
      глобальными переменными, доступными сценариям. Если, например, документ
      включает элемент />, на него можно сослаться с помощью
      глобальной переменной okay.

      Однако важно отметить, что этого не происходит, если объект Window уже имеет
      свойство с таким именем. Элементы с атрибутами id, имеющими значение
      «history», «location» или «navigator», например, не будут доступны через глобальные
      переменные, потому что эти имена уже используются. Аналогично, если HTML-
      документ включает элемент с атрибутом id, имеющим значение «х» и в сценарии
      объявляется и используется глобальная переменная х, явно объявленная переменная скроет неявную переменную, ссылающуюся на элемент. Если
      переменная объявляется в сценарии, который в документе находится выше
      именованного элемента, наличие переменной будет препятствовать появлению нового
      свойства окна. А если переменная объявляется в сценарии, который находится ниже
      именованного элемента, первая же инструкция присваивания значения этой
      переменной затрет значение неявно созданного свойства.


      var/let d;
      d = document.querySelector('#xxx');
      d.innerHTML='bla-bla';

      Этот вариант предпочтительней (безопасней), поскольку, если где-то в вашем коде будет объявление var переменной с таким же именем как значение id глобальная переменная xxx уже не будет ссылаться на html элемент.


      1. BigDflz
        01.10.2019 01:05

        Этот вариант предпочтительней (безопасней), поскольку, если где-то в вашем коде будет объявление var переменной с таким же именем как значение id глобальная переменная xxx уже не будет ссылаться на html элемент.

        с этим понятно. в общем — что и следовало ожидать.
        но если отвлечься от такой безопасности(будем считать что прогер за этим следит), получается, что уже есть глобальная переменная с именем = id, есть ли смысл заводить новую переменную? ведь если часто идёт обращение к элементу через document.querySelector('#xxx'), то рекомендуют использовать переменную. зачем? ведь уже есть она?
        остался вопрос о скорости (раз сравнивается скорость let и var).


        1. VolCh
          01.10.2019 08:31

          Сделайте выборку из dom один раз. Можете даже в глобальную переменную его записать. Это будет лучше чем использовать описанное поведение


          1. BigDflz
            01.10.2019 09:08

            но когда назначается id элементу это уже есть определение глобальной переменной. вопрос в том — зачем дублировать?


            1. ukbpyh Автор
              01.10.2019 10:54

              На счёт скорости: самое быстрое, как вышло из статьи — использовать локальные переменные, не важно let или var. А созданная ссылка xxx глобальная, то есть, если вопрос только в быстродействии — надо опять же использовать

              var/let  d;
              d = document.querySelector('#xxx');
              d.innerHTML='bla-bla';

              только в локальном контексте.

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


              1. BigDflz
                01.10.2019 11:18

                я пришёл к этому же мнению, хотелось подтверждения со стороны.
                тогда, уж если ловить блох в скорости, то
                в локальном контексте делать

                var/let  d;
                d = xxx;
                d.innerHTML='bla-bla';


                1. ukbpyh Автор
                  01.10.2019 11:31

                  Присваивание d = xxx не самое верное решение, лучше сделать выборку. Так как, при взгляде не код не понятно, что из себя представляет эта самая переменная xxx (что создаёт проблемы для сопровождения), кроме того, опять же проблемы с безопасностью.


  1. funca
    30.09.2019 23:41

    В современном вебе, с модулями и вебпаками, задефайнить что-либо в global ещё нужно постараться. Если запускали тесты в консоли devtools, то в них используется eval со своими особенностями.


    1. ukbpyh Автор
      01.10.2019 00:17

      Спасибо за комментарий.

      Возник один вопрос: что вы имеете в виду, под особенностями eval в devtools? Разве при открытии консоли браузер начинает как-то по другому обрабатывать скрипт?


  1. ukbpyh Автор
    01.10.2019 00:09

    Статья была ещё раз обновлена.

    Добавлена проверка на то, влияет ли только чтение (без записи) глобальных переменных на производительность. Оказалось, что нет. Выводы также соответствующим образом скорректированы.

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

    Спасибо!


  1. cheshirat
    01.10.2019 10:25
    +1

    Про опечатку в стандарте. LexicalDeclaration само по себе заканчивается на ;. Вот здесь: LexicalDeclaration.


    1. ukbpyh Автор
      01.10.2019 10:37

      Похоже вы правы. Я когда смотрел ориентировался на соседнюю инструкцию — обратил внимание на for с var

      for(varVariableDeclarationList;Expression;Expression)Statement

      — здесь есть точка с запятой, при этом если посмотреть на VariableDeclaration, там тоже есть точка с запятой, но я похоже ошибся, так как если взглянуть именно на VariableDeclarationList её там уже нет и значит всё верно. В общем, отталкиваясь от синтаксиса for при var я неверно сориентировался в пунктуации, если коротко.

      Спасибо за ценное замечание, статью поправил.