Расшифровать контекст выполнения JavaScript

JavaScript

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

Прежде всего, давайте разберемся, что такое стек контекста выполнения (Execution context stack).

堆,栈和队列
Приведенное выше изображение взято из mdn, показывающее стек, кучу и очередь соответственно, где стек — это то, что мы называем стеком контекста выполнения; куча — это сложный тип, используемый для хранения объектов, а ссылка на адрес объекта, который мы копируем, — это Адрес памяти кучи; очередь — это асинхронная очередь, используемая для выполнения цикла обработки событий.

Код JS анализируется и выполняется «сегментным» способом в движке, а не построчно. И этот «сегмент» исполняемого кода представляет собой не что иное, как три типа:Global code,Function Code,Eval code. Когда эти исполняемые коды выполняются, они создают контекст выполнения (контекст выполнения). Например, когда функция выполняется, движок JS выполнит некоторую «подготовительную работу», и эту «подготовительную работу» мы называем执行上下文.

Итак, по мере увеличения количества контекстов выполнения, как движок JS управляет этими контекстами выполнения? Затем идет стек контекста выполнения.

Здесь я использую пример по всему тексту, чтобы объяснить процесс выполнения стека контекста выполнения:

var scope = 'global scope';

function checkscope(s) {
  var scope = 'local scope';

  function f() {
    return scope;
  }
  return f();
}
checkscope('scope');

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

Здесь мы используем ECS для имитации стека контекста выполнения и globalContext для представления глобального контекста:

ESC = [
  globalContext, // 一开始只有全局上下文
]

Затем, когда код выполняет функцию checkscope, контекст выполнения функции checkscope создается и помещается в стек контекста выполнения:

ESC = [
  checkscopeContext, // checkscopeContext入栈
  globalContext,
]

Затем код выполняется дляreturn f()Когда создается контекст выполнения функции f:

ESC = [
  fContext, // fContext入栈
  checkscopeContext,
  globalContext,
]

После выполнения функции f контекст выполнения функции f извлекается из стека, а затем выполняется функция checkscope, а контекст выполнения функции checkscope извлекается из стека:

// fContext出栈
ESC = [
  // fContext出栈
  checkscopeContext,
  globalContext,
]

// checkscopeContext出栈
ESC = [
  // checkscopeContext出栈
  globalContext,
]

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

Каждый контекст выполнения имеет три важных свойства:

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

В этом разделе давайте сначала поговорим о переменном объекте (переменный объект, здесь называемый VO).

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

переменный объект в глобальном контексте

Переменный объект в глобальном контексте на самом деле является глобальным объектом. Мы можем получить доступ к глобальному объекту через это и в среде браузера,this === window; в среде узла,this === global.

this === window

this === global

Переменный объект в контексте функции

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

Перед выполнением функции создается контекст выполнения для текущей функции, а в это время создается переменный объект:

  • Инициализировать объект arguments в соответствии со свойством arguments функции;
  • Соответствующее свойство генерируется в соответствии с объявлением функции, и его значение является указателем на функцию в памяти. Перезаписать, если имя функции уже существует;
  • Соответствующее свойство генерируется в соответствии с объявлением переменной, и в настоящее время начальное значение не определено. Если имя переменной объявлено, игнорируйте объявление переменной;

Возьмите код только что в качестве примера:

var scope = 'global scope';

function checkscope(s) {
  var scope = 'local scope';

  function f() {
    return scope;
  }
  return f();
}
checkscope('scope');

Перед выполнением функции checkscope для нее будет создан контекст выполнения и инициализирован переменный объект.Переменный объект в это время:

VO = {
  arguments: {
    0: 'scope',
    length: 1,
  },
  s: 'scope', // 传入的参数
  f: pointer to function f(),
  scope: undefined, // 此时声明的变量为undefined
}

При выполнении функции checkscope активируется переменный объект, а свойства в замаскированном объекте меняются при выполнении кода:

VO = {
  arguments: {
    0: 'scope',
    length: 1,
  },
  s: 'scope', // 传入的参数
  f: pointer to function f(),
  scope: 'local scope', // 变量赋值
}

На самом деле это также можно объяснить другим понятием «подъем функции» и «подъем переменной»:

function checkscope(s) {
  function f() { // 函数提升
    return scope;
  }
  var scope; // 变量声明提升

  scope = 'local scope' // 变量对象的激活也相当于此时的变量赋值

  return f();
}

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

Каждый контекст выполнения имеет три важных свойства:

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

В этом разделе мы поговорим о цепочке областей действия.

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

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

Давайте используем наш пример, чтобы объяснить цепочку областей видимости:

var scope = 'global scope';

function checkscope(s) {
  var scope = 'local scope';

  function f() {
    return scope;
  }
  return f();
}
checkscope('scope');

Во-первых, при объявлении функции checkscope она привязывает[[scope]]Внутренние свойства:

checkscope.[[scope]] = [
  globalContext.VO
];

Затем перед выполнением функции checkscope создается контекст выполнения checkscopeContext, который помещается в стек контекста выполнения:

  • функция копирования[[scope]]цепочка области действия инициализации свойства;
  • создать переменный объект;
  • поместите переменный объект в начало цепочки областей видимости;
// -> 初始化作用域链;
checkscopeContext = {
  scope: checkscope.[[scope]],
}

// -> 创建变量对象
checkscopeContext = {
  scope: checkscope.[[scope]],
  VO = {
    arguments: {
      0: 'scope',
      length: 1,
    },
    s: 'scope', // 传入的参数
    f: pointer to function f(),
    scope: undefined, // 此时声明的变量为undefined
  },
}

// -> 将变量对象压入作用域链的最顶端
checkscopeContext = {
  scope: [VO, checkscope.[[scope]]],
  VO = {
    arguments: {
      0: 'scope',
      length: 1,
    },
    s: 'scope', // 传入的参数
    f: pointer to function f(),
    scope: undefined, // 此时声明的变量为undefined
  },
}

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

checkscopeContext = {
  scope: [VO, checkscope.[[scope]]],
  VO = {
    arguments: {
      0: 'scope',
      length: 1,
    },
    s: 'scope', // 传入的参数
    f: pointer to function f(),
    scope: 'local scope', // 变量赋值
  }
}

В то же время встречается объявление функции f, и функция f привязана[[scope]]Атрибуты:

checkscope.[[scope]] = [
  checkscopeContext.VO, // f函数的作用域还包括checkscope的变量对象
  globalContext.VO
];

Шаги функции f после этого такие же, как и для функции checkscope.

Еще один классический пример:

var data = [];

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

data[0]();
// ...

Это очень просто, независимо от того, к какому количеству данных осуществляется доступ, конечная консоль выводит 6, потому что до ES6 в JS не было концепции блочной области видимости, а код в цикле for находился в глобальной области видимости.

Перед выполнением функции данных переменный объект глобального контекста:

globalContext.VO = {
  data: [pointer to function ()],
  i: 6, // 注意:此时的i值为6
}

Цепочка контекста выполнения каждой анонимной функции данных примерно следующая:

data[n]Context = {
  scope: [VO, globalContext.VO],
  VO: {
    arguments: {
      length: 0,
    }
  }
}

Затем, когда функция выполнена, сначала перейдем к переменной объекту собственной анонимной функции, чтобы найти значение I. Если он найдет, он будет искать по цепочке области и найдет переменную объект глобального контекста выполнения. В это время будет найден переменная глобального контекста выполнения. Я в объекте 6, так что 6 напечатано каждый раз.

Лексическая область действия и динамическая область действия

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

Измените предыдущий пример:

var scope = 'global scope';

function f() {
  console.log(scope)
}

function checkscope() {
  var scope = 'local scope';

  f();
}
checkscope();

Поскольку JavaScript создает области на основе лексической области видимости, выводимый результатglobal scopeвместоlocal scope. Давайте проанализируем цепочку областей действия выше:

Объявление функции f встречается первым, и в это время оно привязано к ней.[[scope]]Атрибуты:

// 这里就是我们所说的“一个函数的作用域在函数声明的时候就已经确定了”
f.[[scope]] = [
  globalContext.VO, // 此时的全局上下文的变量对象中保存着scope = 'global scope';
];

Тогда мы сразу пропускаем процесс создания и выполнения контекста выполнения checkscope и переходим сразу к выполнению функции f. В этот момент контекст выполнения функции f инициализируется до того, как функция будет выполнена:

// 这里就是为什么会打印global scope
fContext = {
  scope: [VO, globalContext.VO], // 复制f.[[scope]],f.[[scope]]只有全局执行上下文的变量对象
  VO = {
    arguments: {
      length: 0,
    },
  },
}

Затем идет процесс выполнения функции f,console.log(scope), он будет искать переменную области видимости по цепочке областей видимости функции f. Сначала она будет искать в объекте переменной своего контекста выполнения, если не найдет, то будет искать в объекте переменной глобального исполнения контекст. В настоящее время значение области видимости равноglobal scope.

this

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

  • В глобальном контексте выполнения это относится к глобальному объекту. (В браузерах это относится к объекту Window).
  • В контексте выполнения функции значение this зависит от того, как была вызвана функция. Если он вызывается для ссылочного объекта, это будет установлено для этого объекта, в противном случае значение этого будет установлено для глобального объекта или неопределенного (в строгом режиме)

Подводя итог, кто бы ни звонил, это указывает на.

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

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

var scope = 'global scope';

function checkscope(s) {
  var scope = 'local scope';

  function f() {
    return scope;
  }
  return f();
}
checkscope('scope');

Сначала выполняется глобальный код, создается глобальный контекст выполнения, и глобальный контекст выполнения входит в стек контекста выполнения:

globalContext = {
  scope: [globalContext.VO],
  VO: global,
  this: globalContext.VO
}

ESC = [
  globalContext,
]

Затем при выполнении кода он доходит до этапа объявления функции checkscope, на котором происходит привязка[[scope]]Атрибуты:

checkscope.[[scope]] = [
  globalContext.VO,
]

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

// 创建执行上下文
checkscopeContext = {
  scope: [VO, globalContext.VO], // 复制[[scope]]属性,然后VO推入作用域链顶端
  VO = {
    arguments: {
      0: 'scope',
      length: 1,
    },
    s: 'scope', // 传入的参数
    f: pointer to function f(),
    scope: undefined,
  },
  this: globalContext.VO,
}

// 进入执行上下文栈
ESC = [
  checkscopeContext,
  globalContext,
]

Функция checkscope выполняется, обновляя объект переменной:

// 创建执行上下文
checkscopeContext = {
  scope: [VO, globalContext.VO], // 复制[[scope]]属性,然后VO推入作用域链顶端
  VO = {
    arguments: {
      0: 'scope',
      length: 1,
    },
    s: 'scope', // 传入的参数
    f: pointer to function f(),
    scope: 'local scope', // 更新变量
  },
  this: globalContext.VO,
}

f объявление функции, привязка[[scope]]Атрибуты:

f.[[scope]] = [
  checkscopeContext.VO,
  globalContext.VO,
]

Функция f выполняется, создает контекст выполнения и помещает его в стек контекста выполнения:

// 创建执行上下文
fContext = {
  scope: [VO, checkscopeContext.VO, globalContext.VO], // 复制[[scope]]属性,然后VO推入作用域链顶端
  VO = {
    arguments: {
      length: 0,
    },
  },
  this: globalContext.VO,
}

// 入栈
ESC = [
  fContext,
  checkscopeContext,
  globalContext,
]

Выполнение функции f завершено, контекст выполнения функции f извлекается, выполнение функции checkscope завершается, и функция checkscope извлекается из стека:

ESC = [
  // fContext出栈
  checkscopeContext,
  globalContext,
]

ESC = [
  // checkscopeContext出栈,
  globalContext,
]

На этом этапе анализируется поток всего контекста выполнения.