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

JavaScript
Вы действительно понимаете контекст выполнения?

контекст выполнения

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

Существует три типа контекстов выполнения:

  • Глобальный контекст выполнения: есть только один, созданный при первом запуске программы, она создает глобальный объект в браузере (windowобъект), так чтоthisуказывает на этот глобальный объект
  • Контекст выполнения функции: создается при вызове функции, каждый вызов создает новый контекст выполнения функции.
  • Контекст выполнения функции Eval: запуститьevalКонтекст выполнения, созданный, когда код находится в функции, используется редко и не рекомендуется.

стек контекста выполнения

Стек контекста выполнения (ECS), также называемый стеком вызовов функций (call stack), является собственностьюLIFOСтек структур данных (последний вошел, первый вышел), используемый для хранения контекста выполнения, созданного при выполнении кода.

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

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

Пример кода 🌰

var color = 'blue';

function changeColor() {
  var anotherColor = 'red';
  
  function swapColors() {
    var tempColor = anotherColor;
    anotherColor = color;
    color = tempColor;
  }
  
  swapColors();
}

changeColor();

console.log(color); // red

Выполнение может быть осуществлено вdevToolизcall stackвидел вanonyomusэто глобальный стек контекста; остальные стеки контекста функции

Диаграмма:

Процесс реализации:

  1. первый созданный全局执行上下文, помещается в стек выполнения, и исполняемый код в нем начинает выполняться.
  2. тогда позвониchangeColorфункция, JS-движок прекращает выполнение глобального контекста выполнения, активирует функциюchangeColorСоздает собственный контекст выполнения и помещает контекст функции поверх стека контекста выполнения, где начинается выполнение исполняемого кода.
  3. changeColorназываетсяswapColorsфункция, приостановленная в это времяchangeColorконтекст выполнения, созданныйswapColorsНовый контекст выполнения функции и контекст выполнения функции помещаются в верхнюю часть стека контекстов выполнения.
  4. когдаswapColorsПосле выполнения функции контекст ее выполнения извлекается из вершины стека и возвращается вchangeColorВыполнение продолжается в контексте выполнения.
  5. changeColorИсполняемого кода нет, и другого контекста выполнения не обнаружено.全局执行上下文продолжать выполнять.
  6. Как только весь код будет выполнен, движок JS будет удален из текущего стека.全局执行上下文.
注意:函数中,遇到return能直接终止可执行代码的执行,因此会直接将当前上下文弹出栈。

использоватьECStackЧтобы имитировать стек вызовов:

ECStack=[]

Первый глобальный код JS столкнется при выполнении стека контекста выполнения кода, вдавленного в глобальный контекст, мы используемglobalContextозначает это, только когда все приложение заканчивается,ECStackбудет очищен, так чтоECStackвсегда внизуglobalContext:

ECStack.push(globalContext)

Используйте псевдокод для имитации приведенного выше поведения кода:

ECStack.push(<changeColor> functionContext);
ECStack.push(<swapColors> functionContext);

// swapColors出栈
ECStack.pop();
// changeColor出栈
ECStack.pop();

Чтобы закрепить понимание контекста выполнения, давайте нарисуем эволюцию примера, который является простым примером замыкания.

function f1() {
  var n = 999;
  function f2() {
    console.log(n);
  }
  return f2;
}
f1()() // 999

Используйте псевдокод для имитации приведенного выше поведения кода:

ECStack.push(<f1> functionContext);
// f1出栈
ECStack.pop();

ECStack.push(<f2> functionContext);
// f2出栈
ECStack.pop();

Поскольку функция f2 в f1 не вызывается и не выполняется в исполняемом коде f1, при выполнении f1 f2 не создаст новый контекст, а новый контекст не будет создан до тех пор, пока не будет выполнена f2. Конкретный эволюционный процесс выглядит следующим образом.

версия es3

В контексте выполнения версии es3 есть три важных свойства:

  • Переменный объект VO (переменный объект)
  • цепочка прицелов
  • this

Каждый контекст выполнения может быть абстрагирован в объект.

Пример кода композиции контекста выполнения:

executionContextObj = {
  scopeChain: { /* 变量对象(variableObject)+ 所有父执行上下文的变量对象*/ },
  [variableObject | activationObject]: {
    /*函数 arguments/参数,内部变量和函数声明 */
    arguments,
    ...
  },
  this: {}
}

переменный объект

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

Объекты-переменные в разных контекстах исполнения тоже разные:

  • глобальный контекстОбъект переменной в — это глобальный объект, а в браузере — это объект окна. В коде JavaScript верхнего уровня на глобальный объект можно ссылаться с помощью ключевого слова this. Все глобальные переменные и функции существуют как свойства и методы окна.
    console.log(this) //window
    var a=1 //挂到window上的属性
    console.log(window.a) //1
    console.log(this.a) //1
  • контекст выполнения функциимы используемАО (объект активации)Чтобы представить объект переменной, поскольку объект переменной является спецификацией или реализацией движка, к нему нельзя получить прямой доступ в среде JavaScript, только когда вызывается функция и объект переменной активируется как активный объект, мы можем получить к нему доступ для Его свойства и методы.
  活动对象就是变量对象,只不过处于不同的状态和阶段而已。

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

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

О масштабе и цепочке масштабов вы можете обратиться к другой моей статье:Освоение JavaScript Интервью: что такое замыкания?

Тогда у некоторых студентов возникают вопросы, каков объем и контекст выполнения?разницаЧто 🎉:

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

var a = 10;
function fn(x) {
  var a = 20;
  console.log(arguments)
  console.log(x)
}
fn(20)
fn(10) // 不同的调用可能有不同的参数

JavaScript использует лексическую область видимости, а область действия, созданная функцией FN, определяется при определении функции;

Связанный 🎉:

Скоуп это просто "сайт" в котором нет переменных.Значение переменной должно быть получено через контекст выполнения соответствующий скоупу, поэтому скоуп статический, а контекст выполнения динамический. То есть область используется только для разделения эффективной области переменных, которые вы определяете в этой области, и она недействительна за пределами этой области.

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

Жизненный цикл

Жизненный цикл контекста выполнения состоит из трех фаз, а именно:

  • создать сцену
    • генерировать переменный объект
      • создавать аргументы
      • Декларация функции сканирования
      • Сканировать объявления переменных
    • Создайте цепочку областей видимости
    • Определите точку этого
  • этап выполнения
    • присвоение переменной
    • ссылка на функцию
    • выполнить другой код
  • фаза разрушения

создать сцену

Создавайте переменные объекты 🎉

  1. Создать аргументы: если это контекст функции, он будет создан первымargumentsобъект, добавьте имена параметров и значения в переменный объект.
  2. Объявления функций сканирования: для найденных объявлений функций сохраните имя функции и ссылку на функцию (указатель) вVO, еслиVOВ нем уже есть функция с таким именем, то перезапишите (перепишите указатель ссылки).
  3. Поиск объявлений переменных: для каждого найденного объявления переменной сохраните имя переменной вVO, и инициализируйте значение переменной дляundefined. Если имя переменной уже существует в объекте переменной, ничего не делайте и продолжайте сканирование.

Для иллюстрации возьмем каштан 🌰:

function person(age) {
  console.log(typeof name); // function
  console.log(typeof getName); // undefined
  var name = 'abby';
  var hobby = 'game';
  var getName = function getName() {
    return 'Lucky';
  };
  function name() {
    return 'Abby';
  }
  function getAge() {
    return age;
  }
  console.log(typeof name); // string
  console.log(typeof getName); // function
  name = function () {};
  console.log(typeof name); // function
}
person(20);

Когда вызывается person(20), но код еще не выполнен, создается следующее состояние:

personContext = {
    scopeChain: { ... },
    activationObject: {
        arguments: {
            0: 20,
            length: 1
        },
        age: 20,
        name: pointer, // reference to function name(),
        getAge: pointer, // reference to function getAge(),
        hobby: undefined,
        getName : undefined,
    },
    this: { ... }
}

Прежде чем функция будет выполнена, она сначала создаст контекст выполнения функции, сначала укажет ссылку на функцию, а затем определит переменные по порядку, инициализированные какundefinedдепозит вVOСреди них после сканирования переменнойnameнайти вVOВ нем есть свойство (переменная объявления функции) с таким же именем, поэтому оно игнорируется.

全局执行上下文的创建没有创建 arguments 这一步

Постройте цепочку масштабов 🎉

На этапе создания контекста времени выполнения цепочка областей действия создается после объекта переменной. Сама цепочка областей видимости содержит переменные объекты.

  1. При написании фрагмента кода функции создается лексическая область видимости, которая является атрибутом внутри функции.[[scope]]означает, что он содержит объект родительской переменной, поэтому[[scope]]Это иерархическая цепочка.
person.[[scope]] = [
     globalContext.variableObject
]
  1. Когда функция вызывается, это означает, что функция активирована.В это время контекст функции создается и помещается в стек выполнения, а затем атрибут функции [[scope]] копируется для создания цепочки областей видимости:
personContext = {
     scopeChain:person.[[scope]]
}
  1. Создайте активный объект (предыдущий шаг создания объекта переменной), затем поместите активный объект (АО) в начало цепочки областей видимости.
personContext = {
    activationObject: {
        arguments: {
            0: 20,
            length: 1
        },
        age: 20,
        name: pointer, // reference to function name(),
        getAge: pointer, // reference to function getAge(),
        hobby: undefined,
        getName : undefined,
    },
    scopeChain:[activationObject,[[scope]]]
}

Убедитесь, что это указывает на 🎉

Если текущая функция вызывается как метод объекта или используетсяbind,call,applyЖдатьAPIКогда делается делегированный вызов, информация о вызывающей стороне (это значение) текущего блока кода сохраняется в текущем контексте выполнения, в противном случае по умолчанию используется вызов глобального объекта.

Что касается реализации этого, пожалуйста, обратитесь к моей другой статье:Разблокируйте новые позы этого, применяйте, звоните, связывайте 🍊

этап выполнения

этап выполнения, поток выполнения входит в функцию и запускает/интерпретирует код в контексте, JS-движок начинает присваивать значения определенным переменным, начинает обращаться к переменным по цепочке областей видимости, и если внутри есть вызов функции, создается новый контекст выполнения создается и помещается в стек выполнения и передает управление

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

  1. первое исполнениеconsole.log; В настоящее времяnameсуществуетVOэто функция.getNameЗначение не указано вVOЗначение вundefined.
  2. Выполните код назначения,getNameприсваивается как функциональное выражение,nameназначается какabby
  3. второе исполнениеconsole.log; В настоящее времяnameПоскольку функция переопределяется присваиванием строки, онаstringТипыgetNameдаfunctionТипы.
  4. третья казньconsole.log; В настоящее времяnameПоскольку он снова перезаписывается, этоfunctionТипы

Таким образом, после того, как после понимания контекста реализации хорошего объяснения переменных подъема (подъема): на самом деле переменные и декларации функций в коде не будут изменяться, но при компиляции двигателя JavaScript в память

Это объясняет, почему мы можемnameполучить к нему доступ до объявления, почему послеnameЗначение типа изменилось, почемуgetNameПри печати в первый разundefinedДождитесь вопроса.

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

Контекст выполнения функции при окончательном выполнении консоли:

personContext = {
    scopeChain: { ... },
    activationObject: {
        arguments: {
            0: 20,
            length: 1
        },
        age: 20,
        name: pointer, // reference to function name(),
        getAge: pointer, // reference to function getAge(),
        hobby: 'game',
        getName:pointer, pointer to function getName(),
    },
    this: { ... }
}

фаза разрушения

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

Полный пример

Пример 1🌰:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();

1. Выполните глобальный код, сгенерируйте глобальный контекст и поместите его в стек выполнения.

ECStack=[
     globalContext
]

2. Инициализация глобального контекста

globalContext={
     variableObject:[global,scope,checkscope],
     this:globalContext.variableObject,
     scopeChain:[globalContext.variableObject]
}

3. Внутреннее свойство [[scope]] генерируется при создании функции checkscope, и в нем хранится цепочка контекста глобального контекста

checkscope.[[scope]] = [
     globalContext.variableObject
]

4. Вызовите функцию checkscope, создайте контекст функции и поместите стек

ECStack=[
     globalContext,
     checkscopeContext
]

5. В это время функция checkscope еще не выполнена и входит в контекст выполнения

  • Скопируйте атрибут [[scope]] функции, чтобы создать цепочку областей действия.
  • Создайте активный объект со свойством arguments
  • Инициализировать переменные объекты, добавить объявления переменных, объявления функций, формальные параметры
  • Активный объект перемещается в начало цепочки областей видимости.
    checkscopeContext = {
        activationObject: {
            arguments: {
                length: 0
            },
            scope: undefined,
            f: pointer, // reference to function f(),
        },
        scopeChain: [activationObject, globalContext.variableObject],
        this: undefined
    }

6. Выполняется функция checkscope и устанавливается область действия переменной

    checkscopeContext = {
        activationObject: {
            arguments: {
                length: 0
            },
            scope: 'local scope',
            f: pointer, // reference to function f(),
        },
        scopeChain: [activationObject, globalContext.variableObject],
        this: undefined
    }

Функция f создается для генерации атрибута [[scope]] и содержит цепочку областей действия родительской области.

f.[[scope]]=[
     checkscopeContext.activationObject,
     globalContext.variableObject
]

7. Вызывается функция f, генерируется контекст функции f и помещается в стек.

ECStack=[
     globalContext,
     checkscopeContext,
     fContext
] 

8. В это время функция f еще не выполнена, а контекст выполнения инициализирован

  • Скопируйте атрибут [[scope]] функции, чтобы создать цепочку областей действия.
  • Создайте активный объект со свойством arguments
  • Инициализировать переменные объекты, добавить объявления переменных, объявления функций, формальные параметры
  • Активный объект перемещается в начало цепочки областей видимости.
fContext = {
     activationObject: {
            arguments: {
                length: 0
            },
        },
        scopeChain: [fContext.activationObject, checkscopeContext.activationObject, globalContext.variableObject],
        this: undefined
    }

9. Выполняется функция f, ищет значение области действия в цепочке областей видимости и возвращает значение области видимости.

10. После выполнения функции f контекст функции f извлекается из стека контекста выполнения.

ECStack=[
     globalContext,
     checkscopeContext
]

11. После выполнения функции контрольной области контекст выполнения контрольной области извлекается из стека контекста выполнения.

ECStack=[
     globalContext
]

Пример 2🌰:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();
  1. Выполнять глобальный код, генерировать глобальный контекст и помещать его в стек выполнения.
  2. Инициализация глобального контекста
  3. Создайтеcheckscopeфункция при генерации внутренних свойств[[scope]], и сохраните в нем цепочку областей видимости глобального контекста
  4. передачаcheckscopeфункция, создать контекст функции, поместить стек
  5. В настоящее времяcheckscopeФункция еще не выполнена, вход в контекст выполнения
    • Функция копирования[[scope]]Свойства создают цепочку областей действия
    • использоватьargumentsСвойства создать активный объект
    • Инициализировать переменные объекты, добавить объявления переменных, объявления функций, формальные параметры
    • Активный объект перемещается в начало цепочки областей видимости.
  6. checkscopeвыполнение функции, по переменнойscopeустановить значение,fфункция создана[[scope]]свойство и содержит цепочку областей родительской области
  7. Вернитесь к функции f. В это время выполнение функции checkscope завершено, и стек выталкивается.
  8. fвызов функции, генерацияfконтекст функции, поместить стек
  9. В настоящее времяfФункция еще не выполнена, инициализируйте контекст выполнения
    • Функция копирования[[scope]]Свойства создают цепочку областей действия
    • использоватьargumentsСвойства создать активный объект
    • Инициализировать переменные объекты, добавить объявления переменных, объявления функций, формальные параметры
    • Активный объект перемещается в начало цепочки областей видимости.
  10. fВыполнение функции, просмотр цепочки областей видимостиscopeзначение, возвратscopeстоимость
  11. fПосле выполнения функцииfКонтекст функции извлекается из стека контекста выполнения.

🚀 можно увидеть спереди, и только разницаcheckScopeПосле выполнения функция извлекается из стека и затем выполняется.fФункция, шаги такие же, как в примере 1

fContext = {
    scopeChain: [activationObject, checkscopeContext.activationObject, globalContext.variableObject],
}

✨ здесьcheckscopeContextПосле того, как функция выполнена и уничтожена,fФункция все еще может читатьcheckscopeContext.AOценность, то естьcheckscopeContext.AOДо сих пор жив в памяти,fфункция все еще может пройтиfцепочка областей видимости функции, чтобы найти ее. и почемуcheckscopeContext.AOне уничтожается именно потому, чтоfссылка на функциюcheckscopeContext.AOИменно потому, что JS понимает, что когда дочерний контекст ссылается на переменные родительского контекста, достигается эффект неразрушения этих переменных.ЗакрытиеКонцепция!

ES5 версия

ES3 Спецификация ES5 в дополнение к активному объекту и переменному объекту к лексическим компонентам среды (компонент LexicalEnvironment) и переменным компонентам среды (компонент VariableEnvironment) вместо этого.

Жизненный цикл

Жизненный цикл контекста выполнения es5 также включает три этапа: этап создания → этап выполнения → этап повторного использования.

создать сцену

Фаза создания делает три вещи:

  1. Определите значение this, также известное как This Binding.

  2. Компонент LexicalEnvironment создан

  3. Компонент VariableEnvironment создан

Псевдокод примерно такой:

ExecutionContext = {  
  ThisBinding = <this value>,     // 确定this 
  LexicalEnvironment = { ... },   // 词法环境
  VariableEnvironment = { ... },  // 变量环境
}
This Binding

ThisBindingОн привязан к контексту выполнения, то есть каждый контекст выполнения имеетthis,а такжеes3изthisнет никакой разницы,thisЗначение может быть подтверждено только во время выполнения и не может быть подтверждено во время определения

Создайте лексическую среду

Лексическое окружение устроено следующим образом:

GlobalExectionContext = {  // 全局执行上下文
  LexicalEnvironment: {       // 词法环境
    EnvironmentRecord: {     // 环境记录
      Type: "Object",           // 全局环境
      // 标识符绑定在这里 
      outer: <null>           // 对外部环境的引用
  }  
}
 
FunctionExectionContext = { // 函数执行上下文
  LexicalEnvironment: {     // 词法环境
    EnvironmentRecord: {    // 环境记录
      Type: "Declarative",      // 函数环境
      // 标识符绑定在这里      // 对外部环境的引用
      outer: <Global or outer function environment reference>  
  }  
}

Вы можете видеть, что существует два типа лексического окружения 👇:

  • глобальная среда: это лексическая среда без внешней среды, внешняя среда которой упоминается какnull. иметь глобальный объект (объект окна) и связанные с ним методы и свойства (например, методы массива) и любые определяемые пользователем глобальные переменные,thisЗначение указывает на этот глобальный объект.
  • функциональная среда: переменные, определенные пользователем в функции, сохраняются в записи среды, включаяargumentsобъект. Ссылка на внешнюю среду может быть глобальной средой или внешней функциональной средой, содержащей внутреннюю функцию.

Лексическое окружение состоит из двух компонентов 👇:

  • Регистратор окружающей среды: Фактическое место, где хранятся объявления переменных и функций.
  • ссылка на внешнюю среду: Он указывает на следующий объект в цепочке объема и может получить доступ к своей родительской лексической среде (область объема), аналогично цепочке объема ES3

Есть также два типа регистраторов среды 👇:

  • Использование в контексте функцииДекларативный регистратор среды, используемый для хранения переменных, функций и параметров.
  • Использование в глобальной средеРекордер объектной среды, используемый для определения связи между переменными и функциями, которые появляются в глобальном контексте.

🎉 Поэтому:

  • Лексическое окружение, используемое для создания глобального контекстаРекордер объектной среды ,outerзначениеnull;
  • Используется при создании лексического окружения контекста функцииДекларативный регистратор среды ,outerЗначением является глобальный объект или родительская лексическая среда (область действия).
Создать переменную среду

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

В ES6 разница между лексической средой и средой переменных заключается в том, что первая используется для хранения объявлений функций и переменных (letа такжеconstключевое слово), в то время как последний используется только для хранения переменных (var) связывание, поэтому переменная среда реализует область действия на уровне функции, а лексическая среда реализует область действия на уровне блока на основе области действия функции.

🚨 использоватьlet / constОбъявленная глобальная переменная будет привязана кScriptобъект вместоWindowобъект, не может бытьWindow.xxиспользовать в форме; использоватьvarОбъявленные глобальные переменные привязаны кWindowобъект; использованиеvar / let / constВсе объявленные локальные переменные обязаныLocalобъект. Примечание:Scriptобъект,Windowобъект,LocalТри объекта находятся в параллельном сопоставлении.

箭头函数没有自己的上下文,没有arguments,也不存在变量提升

Знакомство с примерами

let a = 20;  
const b = 30;  
var c;

function multiply(e, f) {  
 var g = 20;  
 return e * f * g;  
}

c = multiply(20, 30);

встреча с вызывающей функциейmultiply, создается контекст выполнения функции:

GlobalExectionContext = {

  ThisBinding: <Global Object>,

  LexicalEnvironment: {  
    EnvironmentRecord: {  
      Type: "Object",  
      // 标识符绑定在这里  
      a: < uninitialized >,  
      b: < uninitialized >,  
      multiply: < func >  
    }  
    outer: <null>  
  },

  VariableEnvironment: {  
    EnvironmentRecord: {  
      Type: "Object",  
      // 标识符绑定在这里  
      c: undefined,  
    }  
    outer: <null>  
  }  
}

FunctionExectionContext = {  
   
  ThisBinding: <Global Object>,

  LexicalEnvironment: {  
    EnvironmentRecord: {  
      Type: "Declarative",  
      // 标识符绑定在这里  
      Arguments: {0: 20, 1: 30, length: 2},  
    },  
    outer: <GlobalLexicalEnvironment>  
  },

  VariableEnvironment: {  
    EnvironmentRecord: {  
      Type: "Declarative",  
      // 标识符绑定在这里  
      g: undefined  
    },  
    outer: <GlobalLexicalEnvironment>  
  }  
}

Причина поднятия переменной: на этапе создания объявление функции сохраняется в среде, а для переменной устанавливается значениеundefined(в случае var ) или оставить его неинициализированнымuninitialized(в случае let и const). Вот почему можно получить доступ к переменным, определенным с помощью var до объявления (хотя это и undefined ), но доступ к переменным, определенным с помощью let и const, до объявления приводит к ошибке ссылки. Это называется переменным подъемом.

Графическое продвижение переменных:

var myname = "极客时间"
function showName(){
  console.log(myname);
  if(0){
   var myname = "极客邦"
  }
  console.log(myname);
}
showName()

image.png

При поиске myname внутри showName сначала будет использована переменная myname в текущем контексте выполнения функции, потому что变量提升, текущий контекст выполнения содержит переменную myname, и ее значение не определено, поэтому полученное значение myname не определено.

этап выполнения

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

этап переработки

Контекст выполнения извлекается из стека и ожидает, пока виртуальная машина перезапустит контекст выполнения.

Краткое описание процесса

  1. создать сценуСначала создайте лексическое окружение глобального контекста: сначала создайте对象环境记录器, а затем создайте его ссылку на внешнюю средуouter, значение равно нулю
  2. Создайте переменную среду для глобального контекста: тот же процесс, что и выше.
  3. Убедитесь, что значение this является глобальным объектом (в качестве примера возьмем браузер, это window )
  4. Функция вызывается, создавая лексическое окружение контекста функции: сначала создайте声明式环境记录器, а затем создайте его ссылку на внешнюю средуouter, значение равно null, значение является глобальным объектом или родительским лексическим окружением
  5. Создайте переменную среду контекста функции: тот же процесс, что и выше.
  6. определить это значение
  7. в контекст выполнения функцииэтап выполнения
  8. Введите после завершения выполненияэтап переработки

Пример объяснения

Лексическая средаouterИзвлеченная структура контекста выполнения выглядит следующим образом:image.png

Ниже мы используем следующий пример для анализа процесса создания и выполнения контекста выполнения:

function foo(){
  var a = 1
  let b = 2
  {
    let b = 3
    var c = 4
    let d = 5
    console.log(a)
    console.log(b)
  }
  console.log(b) 
  console.log(c)
  console.log(d)
}   
foo()

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

1.png

Шаг 2:Продолжить выполнение кода. Когда блок кода выполняется, значение a в переменной среды было установлено на 1, а значение b в лексической среде было установлено на 2. В это время контекст выполнения функция показана на рисунке:

2.png

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

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

третий шаг:Когда код выполняется в блоке с областью действияconsole.log(a)Когда значение переменной a необходимо искать в лексическом окружении и переменном окружении, конкретный метод поиска: запрос вниз по вершине стека лексического окружения, если оно найдено в блоке в лексическом окружении, оно будет вернитесь непосредственно к движку JavaScript, если он не найдет его, продолжайте искать в среде переменных.

Такой процесс поиска переменных завершен, вы можете обратиться к следующему рисунку:

image.png

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

3.png

пятый шаг:Когда функция foo завершает выполнение, стек выполнения извлекает контекст выполнения функции foo.

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

внешняя ссылка

outerэто внешняя ссылка, указывающая на внешний контекст выполнения, который определяется лексической областью

function bar() {
  console.log(myName)
}
function foo() {
  var myName = " 极客邦 "
  bar()
}
var myName = " 极客时间 "
foo()

Когда фрагмент кода использует переменную, движок JavaScript сначала ищет переменную в «текущем контексте выполнения», Например, когда приведенный выше код ищет переменную myName, если она не найдена в текущей среде переменных, механизм JavaScript продолжит поиск в контексте выполнения, на который указывает external. Для интуитивного понимания можно посмотреть на следующую картинку:

image.png

Как видно из рисунка, внешний вид функции bar и функции foo указывают на глобальный контекст, а это означает, что если внешняя переменная используется в функции bar или foo, движок JavaScript будет искать в глобальный контекст выполнения. Мы называем эту цепочку поисков цепочкой областей видимости. Теперь вы знаете, что переменные просматриваются через цепочку областей видимости, но все еще остается нерешенный вопрос, функция bar вызывается функцией foo, то почему внешняя ссылка функции bar является глобальным контекстом выполнения, а не контекст выполнения функции foo?

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

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

function bar() {
  var myName = " 极客世界 "
  let test1 = 100
  if (1) {
    let myName = "Chrome 浏览器 "
    console.log(test)
  }
}
function foo() {
  var myName = " 极客邦 "
  let test = 2
  {
    let test = 3
    bar()
  }
}
var myName = " 极客时间 "
let myAge = 10
let test = 1
foo()

Для вышеуказанного кода при выполнении блока оператора IF внутри функции штрих-бара выполняется, его стек вызовов, как показано на следующем рисунке:

image.png

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

Ссылаться на