Глубокое понимание области видимости JavaScript и цепочки областей видимости

JavaScript

предисловие

В JavaScript есть функция, называемая областью действия. Хотя многим начинающим разработчикам концепция области действия не очень проста для понимания, в этой статье я постараюсь объяснить область действия и цепочку областей действия самым простым способом, надеюсь, вы что-то поняли!

Чтобы прочитать больше качественных статей, нажмитеБлог GitHub

Объем

1. Что такое объем

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

function outFun2() {
    var inVariable = "内层变量2";
}
outFun2();//要先执行这个函数,否则根本不知道里面是啥
console.log(inVariable); // Uncaught ReferenceError: inVariable is not defined

Из приведенного выше примера вы можете понять концепцию области видимости: переменная inVariable не объявлена ​​в глобальной области видимости, поэтому при принятии значения в глобальной области действия будет сообщено об ошибке. Мы можем понять это так:Область действия — это независимый сайт, поэтому переменные не будут раскрыты и не будут раскрыты.. то естьОбласть действия чаще всего используется для изоляции переменных. Переменные с одинаковыми именами в разных областях видимости не будут конфликтовать.

До ES6 в JavaScript не было блочной области, только глобальная область действия и область действия функции.. Появление ES6 предоставляет нам «область действия на уровне блоков», что можно отразить добавлением команд let и const.

2. Глобальный объем и объем функций

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

  • Самая внешняя функция и переменные, определенные за пределами самой внешней функции, имеют глобальную область видимости.
var outVariable = "我是最外层变量"; //最外层变量
function outFun() { //最外层函数
    var inVariable = "内层变量";
    function innerFun() { //内层函数
        console.log(inVariable);
    }
    innerFun();
}
console.log(outVariable); //我是最外层变量
outFun(); //内层变量
console.log(inVariable); //inVariable is not defined
innerFun(); //innerFun is not defined
  • Все переменные, которые не определены и назначены напрямую, автоматически объявляются имеющими глобальную область действия.
function outFun2() {
    variable = "未定义直接赋值的变量";
    var inVariable2 = "内层变量2";
}
outFun2();//要先执行这个函数,否则根本不知道里面是啥
console.log(variable); //未定义直接赋值的变量
console.log(inVariable2); //inVariable2 is not defined
  • Все свойства объекта окна имеют глобальную область видимости.

Как правило, встроенные свойства объекта окна имеют глобальную область действия, например, window.name, window.location, window.top и т. д.

У глобальной области видимости есть недостаток: если мы пишем много строк JS-кода, а определения переменных не включены в функции, то они все находятся в глобальной области видимости. Это загрязнит глобальное пространство имен и легко вызовет конфликты имен.

// 张三写的代码中
var data = {a: 100}

// 李四写的代码中
var data = {x: true}

Вот почему исходный код таких библиотек, как jQuery, Zepto и т. д., весь код будет помещен в(function(){....})()середина. Потому что все переменные, размещенные в нем, не будут просочены и раскрыты, не будут загрязнены наружу и не повлияют на другие библиотеки или JS-скрипты. Это проявление объема функции.

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

function doSomething(){
    var blogName="浪里行舟";
    function innerSay(){
        alert(blogName);
    }
    innerSay();
}
alert(blogName); //脚本错误
innerSay(); //脚本错误

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

Конечный результат: 2, 4, 12.

  • Пузырь 1 является глобальной областью действия и имеет идентификатор foo;
  • Пузырь 2 — это область действия foo с идентификаторами a, bar, b;
  • Пузырь 3 представляет собой бар с ограниченной областью действия и имеет только идентификатор c.

Стоит отметить, что:Блочные операторы (операторы между фигурными скобками "{}"), такие как if и switch условные операторы или циклы for и while, в отличие от функций, они не создают новую область. Переменные, определенные в операторе блока, останутся в той области, в которой они уже существуют.

if (true) {
    // 'if' 条件语句块不会创建一个新的作用域
    var name = 'Hammad'; // name 依然在全局作用域中
}
console.log(name); // logs 'Hammad'

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

3. Область действия блока

Область на уровне блока может быть объявлена ​​с помощью новых команд let и const, а к объявленным переменным нельзя получить доступ за пределами области действия указанного блока. Блочные области создаются, когда:

  1. внутри функции
  2. внутри блока кода (окруженного парой фигурных скобок)

Синтаксис объявления let такой же, как у var. В основном вы можете использовать let вместо var для объявлений переменных, но это ограничит область действия переменной текущим блоком кода. Область действия на уровне блоков имеет следующие характеристики:

  • Объявление переменных не поднимается в начало блока кода

Объявления let/const не поднимаются вверх текущего блока, поэтому вам нужно вручную разместить объявления let/const вверху, чтобы сделать переменную доступной во всем блоке.

function getValue(condition) {
if (condition) {
let value = "blue";
return value;
} else {
// value 在此处不可用
return null;
}
// value 在此处不可用
}
  • Не повторяйте декларации

Если идентификатор уже определен внутри блока кода, объявление let с тем же идентификатором внутри блока приведет к возникновению ошибки. Например:

var count = 30;
let count = 40; // Uncaught SyntaxError: Identifier 'count' has already been declared

В этом примере переменная count объявлена ​​дважды: один раз с помощью var и один раз с помощью let. Поскольку let не может дублировать существующий идентификатор в той же области, объявление let здесь выдает ошибку. Но если новая переменная с тем же именем объявлена ​​с помощью let внутри вложенной области, ошибка не будет выдана.

var count = 30;
// 不会抛出错误
if (condition) {
let count = 40;
// 其他代码
}
  • Волшебное использование Binding Block Scope в циклах

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

for (let i = 0; i < 10; i++) {
  // ...
}
console.log(i);
// ReferenceError: i is not defined

В приведенном выше коде счетчик i действителен только в теле цикла for, и за пределами цикла будет сообщено об ошибке.

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10

В приведенном выше коде переменная i объявлена ​​командой var и действительна в глобальной области видимости, поэтому глобально существует только одна переменная i. Значение переменной i будет меняться каждый раз, когда цикл зацикливается, а console.log(i) внутри функции, назначенной массиву a в цикле, i в нем указывает на глобальный i. Другими словами, i во всех элементах массива a указывает на одно и то же i, что приводит к тому, что на выходе среды выполнения будет значение i в последнем раунде, равное 10.

Если используется let, объявленная переменная действительна только в области блока, а последний вывод равен 6.

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6

В приведенном выше коде переменная i объявлена ​​с помощью let, и текущая i действительна только в этом цикле, поэтому i каждого цикла на самом деле является новой переменной, поэтому окончательный вывод равен 6. Вы можете спросить, если переменная i повторно объявляется в каждом цикле, как она узнает значение предыдущего цикла для вычисления значения текущего цикла? Это связано с тем, что движок JavaScript внутренне запоминает значение предыдущего цикла, и при инициализации переменной i в этом цикле расчет выполняется на основе предыдущего цикла.

Кроме того, у цикла for есть особенность, то есть часть, задающая переменную цикла, является родительской областью видимости, а внутренняя часть тела цикла — отдельной дочерней областью.

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc

Приведенный выше код работает правильно и выводит abc 3 раза. Это указывает на то, что переменная i внутри функции и переменная цикла i не находятся в одной и той же области видимости, а имеют разные области видимости.

цепочка прицелов

1. Что такое свободная переменная

Во-первых, давайте узнаем, что такоесвободная переменная. В следующем кодеconsole.log(a)Чтобы получить переменную, но a не определена в текущей области видимости (сравните b). Переменные, не определенные в текущей области видимости, становятся свободными переменными. Как получить значение свободной переменной — ищем родительскую область видимости (примечание: это утверждение не является строгим, и оно будет объяснено ниже).

var a = 100
function fn() {
    var b = 200
    console.log(a) // 这里的a在这里就是一个自由变量
    console.log(b)
}
fn()

2. Что такое цепочка областей действия

А если у родителя его нет? Ищите слой за слоем, пока не найдете глобальную область видимости и все еще не найдете ее, а затем сдайтесь. Эта послойная связь представляет собой цепочку областей видимости.

var a = 100
function F1() {
    var b = 200
    function F2() {
        var c = 300
        console.log(a) // 自由变量,顺作用域链向父作用域找
        console.log(b) // 自由变量,顺作用域链向父作用域找
        console.log(c) // 本作用域的变量
    }
    F2()
}
F1()

3. О значении свободных переменных

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

var x = 10
function fn() {
  console.log(x)
}
function show(f) {
  var x = 20
  (function() {
    f() //10,而不是20
  })()
}
show(fn)

В функции fn при получении значения свободной переменной x из какой области его следует брать? - Чтобы перейти к области, где была создана функция fn,везде, где будет вызываться функция fn.

Итак, прекратите использовать приведенное выше утверждение. Напротив, было бы более уместно описать это в этом предложении:в поле, где была создана функция». Значение в области действия, ударение здесь "создать", а не "вызвать", не забудьте запомнить - на самом деле это так называемый "статический прицел"

var a = 10
function fn() {
  var b = 20
  function bar() {
    console.log(a + b) //30
  }
  return bar
}
var x = fn(),
  b = 200
x() //bar()

fn() возвращает функцию bar, которая назначена x. Выполнить x(), то есть выполнить код функции штриха. Принимая значение b, берите его непосредственно в области действия fn. При взятии значения a я пытался взять его в области действия fn, но не смог его получить, поэтому я мог только обратиться к области, в которой была создана fn, чтобы найти его, и результат был найден, поэтому окончательный результат был 30

область действия и контекст выполнения

Многие разработчики часто путают понятия области действия и контекста выполнения, ошибочно полагая, что это одно и то же понятие, но это не так.

Мы знаем, что JavaScript — это интерпретируемый язык.Выполнение JavaScript делится на две фазы: интерпретация и выполнение.Эти две фазы делают разные вещи:

Стадия интерпретации:

  • лексический анализ
  • Разбор
  • определение правила области видимости

Этап выполнения:

  • Создайте контекст выполнения
  • выполнить код функции
  • вывоз мусора

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

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

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

Порекомендуйте полезный инструмент мониторинга ошибок для всехFundebug, добро пожаловать, чтобы попробовать это бесплатно!

Добро пожаловать в публичный аккаунт:Мастер по фронтенду, мы будем свидетелями вашего роста вместе! Если вы чувствуете, что что-то приобрели, пожалуйста, дайте мне вознаграждение, чтобы мотивировать меня выпускать больше высококачественного контента с открытым исходным кодом.

Справочные статьи и книги