Как стать автором
Обновить

Комментарии 12

НЛО прилетело и опубликовало эту надпись здесь

Интересно... Но я не увидел никакого практического смысла. Дано голое описание хитрых скоупов. Плюс очень смутили 2 пункта про "всплытие" var и let/const. Во-первых этого нет в спецификации ecma. Во-вторых механизм поведения, который странно обозначают "всплытие" описан в 14.3 ecma. Там описано, как хост среда готовит код к выполнению. Описаны этапы обработки кода в 2 этапа static semantics и runtime semantics. И никакого всплытия.

По моему, практический смысл очевиден. Многие разработчики хотя бы раз слышали на интервью вопрос: "что такое область видимости?", однако, очень часто, интервьюер сам не может дать исчерпывающий ответ на этот вопрос. Как правило, область видимости считают ограниченной операторными скобками, что порождает ряд логичных вопросов.

Например:

- почему тогда for (let i, ...) оказывается замкнута в скоупе


- какого тип переменная "e" в конструкции catch (e)

- есть ли разница между

switch (a) {
case "":
const b = "b";
break;
}

и

switch (a) {
case "": {
const b = "b";
break;
}
}

- и т.д.

Кстати, вообще на вопрос "Что такое область видимости?" тогда вообще лучше сначала уточнить, а что имеется ввиду? Если подразумевается общепрограмистский термин, относящийся к любому языку, то это одно. Тут можно начать рассказывать про фунарг проблему и что жаргонный термин "Области видимости" просо помогает объяснить человеку, знакому с программированием, как эта проблема разрешается в конкретном языке. А если вопрос к js - то в специикации EcmaScript нет такого понятия, как область видимости. Что уже немало может удивать собеседующего:)

В js то, что подразумевается под "областью видимости", организовано просто и элегантно. В спецификации называется Execution context (глава 9.4).

Суть в том, что когда создаётся функция (причём весь файл скрипта тоже функция), то создаётся вокруг неё Environment Record (окружение это функции), где как раз записываются все идентификаторы, объявленные внутри это функции (var, let, const и functional declarations). Если внутри функции объявляется другая функция, то вокруг неё также создаётся Environment Record, содержащая все идентификаторы этой функции. А также, создаётся поле [[OuterEnv]], которое ссылается на родительский Environment Record. И так далее, как и наследование прототипов в js.

И когда внутри функции мы пытаемся обратиться и любому идентификатору, то сначала он ищется в своём Энвайронменте. Если его нет, то делается запрос этого идентификатора у родителя (через поле [[OuterEnv]]. И так далее по цепочке. Глобальное окружение в опле [[OuterEnv]] имеет null. Если идентификатор не был наден и в глобальном окружении, то происходит Refference Error - идентификатор не найден.

Вот и всё в глобальном смысле Области видимости. Это дерево Environment-ов, связанных друг с другом как дети и родители. В этом плане понятно, что самый глубоки ребёнок может добраться до самого верхнего идентификатора. Но не наоборот.

И таким образом фунарг проблема в js разрешается так: функция вернёт значение того идентификатора, которы был объявлен в её Environment на момент декларации функции, а не её вызова.

switch ("a") {
  case "a":
    const a = "a";
    break;
  case: "b":
    const a = "b";  // Syntax error - идентификатор <a> уже определён
    break;
}

и

switch ("a") {
  case "a": {
    const a = "a";
    break;
  }
  case: "b": {
    const a = "b";  // нет ошибки на стадии Static - идентификаторы из разных блоков
    break;
  }
}

Этот пример объясняется очень просто спекой EcmaScript - в случае использования let\const внутри одного блока на стадии подготовки кода Static Semantics будут выполнены все проверки, связанные с let\const. Это ещё до выполнения кода. И из этого следует, что неправильное испльзование let\const может сильно просадить производительность на ровном месте. С этой точки зрения var гораздо безопаснее. В примере выше, если const заменить на var, то ошибки не будет. И перформанс будет лучше.

Собсвенно, мой главный вопрос к статье - не хватает объяснения для чего это используется и что означает по своей сути. Дана только длинная выжимка спеки v8. И из её прочтения я ничего не понял, как это использовать даже, не говоря о том, на какой вопрос собеса эта статья помогает отвтетить. Спека EcmaScript в этом плане, кажется, гораздо проще и понятнее объясняет механизм работы js "под капотом".

Кому что проще понимать - вопрос сугубо индивидуальный. Я лишь попытался объяснить работы конкретно V8, а не абстрактного документа. Именно под V8 каждый день исполняется 65-75% всего JS. На мой взгляд, логично рассматривать работу JS именно на движке. Аналогично, если у вас ломается двигатель автомобиля, вы ищете проблему конкретно в вашем автомобиле, а не в ГОСТе по двигателям внутреннего сгорания (ГОСТ 10150-2014, кстати). Однако, еще раз, каждому свое, если спецификация вам удобнее/роднее для восприятия, это имеет место быть.

А вот тезис, что var был бы безопаснее ("гораздо"?) - извините, не выдерживает критики. Var, в данном случае, "всплывет" в глобальный scope (да, я в курсе, что в спецификации нет понятия "всплытие", но тем не менее), что дает возможность её последующей мутации за пределами switch. Более того, мутация произойдет прямо в вашем примере в блоке case "b", что может оказать явный или неявный сайд-эффект в блоке case "a" или в другом, пока неопределенном месте. Не просто так, с релизом ES6, var не рекомендуется к использованию вообще.

работы конкретно V8, а не абстрактного документа

V8 не может работать поперёк спецификации языка, который он запускает :) Поэтому алгоритмы поведения, описаные в спеке EcmaScript, так или иначе реализованы в v8.

тезис, что var был бы безопаснее ("гораздо"?) - извините, не выдерживает критики.

Я специально отметил, что "с точки зрения производительности". Если у вас var безконтрольно мутирует, то это возможно проблема архитектуры. Модуль слишком большой, или имена сущностей не достаточно проработаны, чтобы случайно затираться другими именами. Про пример switch-case и мутацию - ну да, это просто пример сферического коня в вакууме. Просто иллюстрация, в чём может быть разница между этими записями.

Не просто так, с релизом ES6, var не рекомендуется к использованию вообще.

Простите, а где именно написано, что `var` не рекомендуется? В спецификации ES2023 такой рекомендации нет (как и не было в предыдущих). Есть 14.3.2 Variable Statement про var, но ни одного упоминания, что использование var хоть как-то устарело. Более того во всех примерах есть описания разницы применения как let\const так и var.

Я не говорил, что это прописано в спеке. Это скорее вопрос последующих дискуссий. Он лежит даже за пределами JS. Сам proposal был сделан на основе аналогий с другими ЯП (конкретно, за пример брался С++), со всеми вытекающими тредами по этой теме.

Формальную мотивацию можно посмотреть в ESLint https://eslint.org/docs/latest/rules/no-var#examples

Тогда есть некоторое противоречие. ESlint поясняет это правило возможными ошибками и тем, что ввели let\const с блочной областью видимости, как в других языках. Но другие языки, это не скриптовые языки (в основной массе. Плюсы в частности) И то, что работает у них, в js, без понимания логики работы, может приводить к просадке производительности на ровном месте.

Понятно, что let\const оберегают от неряшливого использования идентификаторов. Но никто не говорит, чего это стоит. А стоит это производительности. Потому что мало того, что на каждый let\const будут идти стопка проверок. Так ещё можно палки в колёса самому V8 вставлять, чтобы он не мог хорошо оптимизировать наш код. Я как раз не так хорошо знаком с реализацией V8. Знаю только что там есть 3 части - Ignition, Sparkplug и TurboFan. Турбофан как раз про оптимизации. И что если мы используем внешний const внутри вложенного блока и вложенность больше 1, то оптимизации уже никогда до использования этого const внутри вложенного блока не доберутся.

Используя let\const зставляет движок в начале выполнения любого контекста (функции, модуля) начать с того, что произвести сбор всех идентификаторов, объявленных как let\const и проверить, а нет ли дублирования, а не используются ли они до своего определения. Что означает доп. операции движка и доп затраты времени. Если производительность вообще никак не важна, то да, можно использовать let\const как угодно.

- почему тогда for (let i, ...) оказывается замкнута в скоупе

Спека EcmaScript (14.7.4.2) на это очень понятно отвечает:

Если в конструкции for на 1м месте происходит  LexicalDeclaration Expression, т.е. декларация идентификаторов с помощью let или const, то движок должен выполнить такой набор команд

  1. Зафиксировать текущий контекст исполнений Executive Context (old Environment)

  2. Создать новый Environment Record на основе старого и поместить внутрь него ссылку на внешний стары Env

  3. Выполнить проверки всех идентификаторов в этом окружении на стадии Static Semantics. Сюда как раз и попадёт let i. И ровно поэтому i будет в скоупе (окружении по спеке EcmaScript) и не будет видна извне.

  4. Поместить в Executive Context Stack новый Env цикла for

  5. и т.д. начать его выполнять

Никакой магии, и всё можно на пальцах объяснить и на паре картинок наглядно показать. И тогда статья может иметь не сложный, но понятный вровень :) Я вообще за простоту. Так то понятно, что спека - это официальный и очень общий документ, который написан специфическим языком. Но любую спеку можно объяснить на пальцах, чтобы было понятно что откуда берётся, и главное - зачем

Извините, за многословность :) Просто недавно засел плотно за переосмысление js

Стоит обратить внимание, что, не смотря на то, что у стрелочных функций нет своего контекста,

Тут хорошо бы отметить одно существенное различие между стелочной и регулярной функцией. Именно в перформансе и затратах памяти. Если вы не планируете использовать функцию как конструктор или ссылаться на this, то имеет смысл исользовать стрелочную функцию. Т.к. регулярная функция имеет довольно много накладных расходов на своё обслуживание. Установку this при каждом её использовании в дот-нотации, например. Или обслуживание проперти prototype для возможности использовать её в виде конструктора. Стрелочная функция от всего этого очищена. И её перформанс будет выше, если эти доп. опции от функции не требуются.

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории