Краткий обзор Scoping and Hoisting в JavaScript

1656542652 kratkij obzor scoping and hoisting v javascript

Тьяго Ромеро Гарсия

0PVN1ZvZ0uPX8QzwuwMpJ5C8UjzjD4wzPW8K
Подъемник является общим как для Mariners, так и для разработчиков Javascript.

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

Короче говоря, определение объема и подъема влияет на то, как код, который мы пишем, будет работать с нашими объявлениями (например, var, let, const и function).

Начнём наш итог с первого из них: var.

Работа с вар

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

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

Это определение Местный масштаб. Апротив Глобальный масштаб, когда переменные объявляются вне вашей функции. Они доступны каждому и всюду. Они вездесущи, как воздух, которым мы дышим, или как воздух window объект в обозревателе.

Следовательно, другие блоки кода как условия и циклы (например if, for, while, switch и try) не разграничивают область применения, в отличие от большинства других языков.

Тогда любой var Внутри этих блоков будет определена область родительской функции, содержащая этот блок.

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

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

Вот пример:

function stepSum() {  var total = 0;
  for (var i = 0; i < arguments.length; i++) {    var parameter = arguments[i];     total += parameter;    console.log(`${i}) adding ${parameter}`);  }
  // outside the loop, we can still access vars i and parameter  // even though the were declared within the for loop  console.log(`i=${i}, parameter=${parameter}`);  return total;}
console.log(`total=${stepSum(3, 2, 1)}`);

Выход:

0) adding 31) adding 22) adding 1i=3, parameter=1total=6

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

function stepSum() {  var total = 0;  var i;  var parameter;
  for (i = 0; i < arguments.length; i++) {    parameter = arguments[i];     total += parameter;    console.log(`${i}) adding ${parameter}`);  }
  console.log(`i=${i}, parameter=${parameter}`);  return total;}
console.log(`total=${stepSum(3, 2, 1)}`);

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

Это источник путаницы для программистов, работающих с языками с блоком (например, C или Java). Они обычно объявляют свои vars как правильные, когда собираются впервые использовать.

Проблемы с var

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

function stepSum() {  var total = 0;
  for (var i = 0; i < arguments.length; i++) {    var parameter = arguments[i];
    setTimeout(function() {      total += parameter;      console.log(`${i}) adding ${parameter}, total=${total}`);    }, i*1000);  }}
stepSum(3, 2, 1);

Выход:

3) adding 1, total=13) adding 1, total=23) adding 1, total=3

Почему это вышло? Он подытоживает только последний параметр 1 трижды. А также количество шагов всегда равно 3, где мы ожидаем увидеть 1, 2 и 3. Что здесь не так ли?

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

Учитывая, что мы используем setTimeout вызовов здесь, мы можем ожидать, что когда эта функция выполняется впервые (через секунду), то stepSum функция уже завершена. Поэтому parameter закончился со значением последнего присвоения, которое было получено из последней итерации цикла for, когда ему было установлено значение 1. То же самое из i заканчивая значением 3.

Вот почему эти значения подбираются, когда 3 setTimeout вызовы в конечном счете выполняются.

Как мы можем это исправить? Просто благодаря правильному использованию обзора и подъема. Мы можем предоставить новую область функции для защиты i и parameter от переназначения. Это создает локальное пространство только для них. Возможно, используя нечто иное, чем var, которое также может дать нам локальную область в блоке, как мы видим дальше.

Работа с let и const

Представлено ES2015 let и const которые являются переменными, уважающими область блока. Это означает, что их безопасно объявлять внутри блока и не вытекать наружу, как показано в следующем примере:

function stepSum() {  let total = 0;
  for (let i = 0; i < arguments.length; i++) {    const parameter = arguments[i];     total += parameter;    console.log(`${i}) adding ${parameter}`);  }
  // outside the loop, we can no longer access i and parameter  console.log(`i=${i}, parameter=${parameter}`);  return total;}
console.log(`total=${stepSum(3, 2, 1)}`);

Выход:

0) adding 31) adding 22) adding 1Uncaught ReferenceError: i is not defined  at stepSum (<anonymous>:10:20)  at <anonymous>:13:1

Ладно, теперь, когда мы узнали, как предотвратить утечку и защитить переменные в области блока, давайте попробуем их!

Вернуться к setTimeout выше, мы можем использовать let и const чтобы решить нашу проблему:

function stepSum() {  let total = 0;
  for (let i = 0; i < arguments.length; i++) {    const parameter = arguments[i];
    setTimeout(function() {      total += parameter;     console.log(`${i}) adding ${parameter}, total=${total}`);    }, i*1000);  }}
stepSum(3, 2, 1);

Вуаля, теперь мы ожидаем результата:

0) adding 3, total=31) adding 2, total=52) adding 1, total=6

Имейте в виду, что мы создали одну пару i и parameter переменные для каждой итерации цикла for. Сравните это с ранее, когда у нас был только один сингл i и parameter каждый раз переписывается. Это немного важно для потребления памяти.

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

Работа с функциями

Вот кое-что заслуживает внимания: объявление a function отличается от объявления a var и присвоить ему функцию.

Например, вот пример объявления a function после его использования понять, как работает подъемник. Это действительный JavaScript:

console.log(`total=${stepSum(3, 2, 1)}`);
function stepSum(...args) {  let total = 0;
  args.forEach((parameter, i) => {     total += parameter;    console.log(`${i}) adding ${parameter}`);  });
  return total;}

Выход:

0) adding 31) adding 22) adding 1total=6

Почему это сработало? Потому что функция stepSum был полностью приподнят перед использованием.

Однако, объявляя это как a var вызывает ошибку:

console.log(`total=${stepSum(3, 2, 1)}`);
var stepSum = function(...args) {  let total = 0;
  args.forEach((parameter, i) => {     total += parameter;    console.log(`${i}) adding ${parameter}`);  });
  return total;}

Выход:

Uncaught TypeError: stepSum is not a function  at <anonymous>:1:22

Почему он сломался?

Разница здесь заключается в том, что когда a function приподнят, его корпус также приподнят. По сравнению с тем, когда а var поднято, поднимается только его декларация, но не его назначение. Следовательно, приведенный выше код был бы подобен тому, где мы стараемся использовать stepSum до того как ему будет назначена функция.

var stepSum;console.log(`total=${stepSum(3, 2, 1)}`);
stepSum = function(...args) {  let total = 0;
  args.forEach((parameter, i) => {     total += parameter;    console.log(`${i}) adding ${parameter}`);  });
  return total;}

Готовы по вызову?

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

function stepSum(...args) {  let total = 0;
  args.forEach((parameter, i) => {     total += parameter;    console.log(`${i}) adding ${parameter}`);    return;    function total() {}  });
  return total;}
console.log(`total=${stepSum(3, 2, 1)}`);

Выход:

0) adding 31) adding 22) adding 1total=0

Почему 0?? Я приглашаю вас оставить свое объяснение в разделе комментариев ниже:)

Учите больше

Чтобы получить более интересные сценарии по определению объема и размещения, я предлагаю прочитать эту уточняющую статью:

Объем и подъем JavaScript
Этот метод на самом деле достаточно гибок и может использоваться везде, где вам нужна временная область, а не только в пределах блока.www.adequatelygood.com

После этого вы можете проверить свои знания с помощью нескольких вопросов на собеседовании:

Функция подъема и подъема Вопросы на собеседование
Это часть 2 моей предыдущей статьи о подъеме переменных под названием «Руководство по подъему переменных JavaScript? с…мedium.freecodecamp.org

Эта статья была первоначально опубликована (версия к ES2015) 4 февраля 2014 как Javascript Hoisting.

Добавить комментарий

Ваш адрес email не будет опубликован.