JavaScript | let в цикле for — efim360.ru

JavaScript | let в цикле for

У цикла for существует 3 производства:

Три производства цикла for по стандарту ECMAScript
Три производства цикла for по стандарту ECMAScript

Мы видим, что внутри круглых скобок существует 3 зоны, которые отделены точкой с запятой ";".

Каждая зона имеет свой смысл. Но нас интересует первая зона.

В ней можно разместить:

  1. не let и квадратная скобка [
  2. var
  3. LexicalDeclaration и Expression

 

Рассмотрим третий вариант производства, который может включать "LexicalDeclaration" и "Expression".

Let and Const Declarations по стандарту ECMAScript
Let and Const Declarations по стандарту ECMAScript

Оно в свою очередь состоит из "LetOrConst BindingList".

Из стандарта:

Объявления let и const определяют переменные, область действия которых ограничена LexicalEnvironment текущего контекста выполнения. Переменные создаются при создании экземпляра содержащей их записи среды, но к ним нельзя получить доступ каким-либо образом, пока не будет оценена LexicalBinding переменной. Переменной, определенной LexicalBinding с Initializer, присваивается значение AssignmentExpression её Initializer при оценке LexicalBinding, а не при создании переменной. Если LexicalBinding в объявлении let не имеет Initializer, переменной присваивается значение undefined при оценке LexicalBinding.

 

Примеры использования

Будем писать функции. Начнём с простого примера, чтобы понять "боль" и "печаль":

function f1(){for(a=0; a<10; a++){console.log(a)}}

Вывод в консоли браузера:

Цикл for без let или const в JavaScript - переменная a прописалась свойством у глобального объекта window в браузере
Цикл for без let или const в JavaScript - переменная a прописалась свойством у глобального объекта window в браузере

Что здесь не так?

Мы объявили функцию. Мы её вызвали. Цикл успешно сделал 10 выводов. И вроде всё логично складывается. Инкремент тоже отрабатывает нормально. Но.

Но проблема в том, что переменная "a" записанная в параметрах цикла, прописалась в качестве ключа у глобального объекта window среды выполнения кода (в браузере). Это значит, что она видна отовсюду в нашей программе и её значение может изменить в любой момент какая-угодно функция. Рассчитывать на эту переменную больше нельзя так как в неё может попасть что угодно.

Где же прописалась наша переменная? Давайте смотреть на скрин:

Новый ключ a со значением 10 у глобального объекта window - JavaScript
Новый ключ a со значением 10 у глобального объекта window - JavaScript

 

А теперь давайте напишем ещё одну функцию с циклом, где также будем использовать нашу "a" как инкрементируемое значение, только в этот раз также запишем переменную "b".

function f2(){for(b=0; b<3; a++, b++){console.log(a+b)}}

Смотрим снова в консоль

Вторая функция увидела глобальный ключ a у объекта window и применила его значение в работе своего цикла в JavaScript
Вторая функция увидела глобальный ключ a у объекта window и применила его значение в работе своего цикла в JavaScript

И вот наша вторая функция уже вовсю эксплуатирует значение ключа "a". И она не просто его получает, а ещё и меняет.

То есть эти две функции не работают изолированно друг от друга. Циклы в их телах не создают ограничения области действия для идентификаторов. Такой подход может однажды "выстрелить в ногу". Подсчитывать важные данные такими функция просто невозможно.

 

Как изолировать параметры цикла от других участков программы?

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

Давайте создадим функцию, похожую на первую, но в этот раз объявим переменную "a" прям в параметре цикла.

function f3(){for(let a=0; a<10; a++){console.log(a)}}

Вызываем и смотрим:

Изолировали параметр цикла for от внешнего кода и глобального объекта window - JavaScript
Изолировали параметр цикла for от внешнего кода и глобального объекта window - JavaScript

И вот сейчас всё работает как нужно. Третья функция не изменяет ключ "a" у глобального объекта window. Его значение по-прежнему 13. В свою очередь глобальный объект ничего не знает о локальной переменной цикла, которая была объявлена через ключевое слово let.

Мы добились изоляции, а значит можем быть более уверенны в работе наших программ.

 

Несколько операторов let в блоке параметров цикла

Возникает резонный вопрос "А что если у нас несколько переменных должны участвовать в цикле? Как их сделать изолированными?"

Первое, что хочется придумать - это записать несколько операторов let через запятую до первого символа ";" из блока параметров цикла.

Давайте попробуем:

function f4(){for(let a=0, let b=0; a<10; a++){console.log(a)}}
Ошибка. В блоке параметров цикла for может быть прописан только один let - JavaScript
Ошибка. В блоке параметров цикла for может быть прописан только один let - JavaScript

И у нас ничего не получается.

Текст ошибки:

Uncaught SyntaxError: let is disallowed as a lexically bound name

Перевод текста ошибки:

Неперехваченная синтаксическая ошибка: let запрещено как лексически связанное имя

 

Говоря простым языком, мы должны отделять команды при помощи символа точки с запятой - ";". Нельзя через одну запятую написать слово (идентификатор привязки), которое зарезервировано языком JavaScript.

В нашем случае синтаксический анализатор сначала смотрит на первое ключевое слово let в блоке параметров. Затем, когда он видит первую запятую, он ожидает получить после неё второе имя идентификатора привязки, а вместо этого видит зарезервированное ключевое слово let, что противоречит перечисляемым идентификаторам привязки.

Понять это можно на примере. Абстрагируемся от цикла. В общем коде программы возможны 2 формы записи:

// ПЕРВАЯ форма записи let

let a;
let b;
let c;
Способ объявления переменных через let в несколько строк - JavaScript
Способ объявления переменных через let в несколько строк - JavaScript

Мы объявили 3 переменные (a, b и c) и не присвоили им никаких значений. Переменные доступны, но отвечают они undefined

Переменные (fj, gh, lk) небыли объявлены и поэтому мы получаем ошибку ссылки на них.

 

// ВТОРАЯ форма записи let

let a1, b1, c1;
Способ объявления переменных через let в одну строку через запятую - JavaScript
Способ объявления переменных через let в одну строку через запятую - JavaScript

Переменные (a1, b1, c1) теперь есть в среде выполнения кода, а переменных (q1, q2, q3) нет.

 

Теперь вернёмся к нашему циклу. У него всё тоже самое. Когда мы напишем ключевое слово let, тогда он будет ожидать перечисления через запятую новых идентификаторов привязки, которые будут работать только внутри цикла. Это очень похоже на вторую форму записи объявлений переменных через let.

function f5(){for(let a=0, b=101, c=2002; a<10; a++, b++, c++){console.log(a, b, c)}}

Смотрим результат выполнения

Объявление трёх параметров цикла for через одно ключевое слово let - все они изолированы - JavaScript
Объявление трёх параметров цикла for через одно ключевое слово let - все они изолированы - JavaScript

Мы записали одно слово let и после него через запятую перечислили имена идентификаторов с их выражениями присваивания до первой точки с запятой ";"

Это говорит о том, что в нашем цикле сейчас существуют 3 независимые изолированные переменные, которые объявлены через let. КАЖДЫЙ ОБЪЯВЛЕН ЧЕРЕЗ LET, хоть это и не заметно на первый взгляд.

Мы можем обратиться к нашим старым глобальным переменным "a" и "b" и убедиться, что их значения никак не изменились.

А самое важное то, что запись "b=101" НЕ ПЕРЕТЁРЛА значение у глобальной переменной "b". Цикловая "b" стала равной 110, а глобальная "b" по-прежнему 3. Просто отсутствие let может первое время путать и вводить в заблуждение.

 

Информационные ссылки

Стандарт ECMAScript - https://tc39.es/ecma262/multipage/