JS核心知识-执行上下文

如果你要成为一名出色的前端开发,第一关必须看懂代码。看懂代码的第一步就是掌握执行上下文。它能帮助你理解JS的其他概念。例如变量提升、作用域链、闭包等。

什么是执行上下文

执行上下文是一个为即将执行的代码块做好一切准备而创建的环境。此环境包含了代码运行时所需要的变量、函数、参数以及作用域链和this指向等信息。

执行上下文的类型

目前JS主要的执行上下文的类型分为四种:

  • 全局执行上下文 它默认的、最外层的上下文。浏览器环境为window Node.js环境global。可以统一使用globalThis

  • 函数执行上下文 每次调用函数时,都会为该函数创建一个全新的执行上下文。函数可以访问外部执行上下文的信息。但外部的不能访问函数内部的信息。

  • Eval函数执行上下文 Eval内部的代码会获取自己的上下文。由于Eval的使用带来了安全、性能等问题。强烈推荐不使用。因此在此不讨论。

  • 模块执行上下文 模块执行上下文是ES6为模块系统新增的一种执行上下文。模块中声明的顶级变量归模块自身所有。不会成为全局环境的属性。自动地启用严格模式。通过<script type="module">.mjs方式创建。

执行上下文生成过程

执行上下文生成过程主要分为两个阶段:创建阶段执行阶段

创建阶段

在此阶段,执行上下文刚刚创建,但是代码并没有运行。主要完成如下三件事情:

1.创建变量对象(Variable Object VO)

在函数上下文中 VO也称为活动对象(Activation Object AO)。创建AO的过程经历以下事情:

  1. 建立一个空的AO对象,在此对象中添加一个名为arguments的类数组对象。arguments存放参数值。
  2. 将形参属性名添加到arguments中,变量名作为属性添加AO对象中,其值为undefined
  3. 将实参值赋值到arguments类数组中
  4. 找到函数中的函数声明,以函数名作为属性、函数体为值,添加到AO中去,如果属性重名,则覆盖之前的属性值。

全局执行上下文创建的变量对象为全局对象(Global Object GO)。过程与函数类似。只是没有参数arguments

2. 建立作用域链

作用域链是一个当前执行上下文和外层执行上下文组成的一个链表的数据结构。它确保了变量和函数的有序访问。例如当查找一个变量时,会按照作用域从当前执行上下文开始查找。一直向外层执行上下文查找,如果找到立刻停止。如果找到全局上下文也没有。则抛出异常。

3. 确定this的指向

this的值在执行上下文创建时确定。全局上下文this指向全局对象。函数上下文的this指向取决函数如何被调用的。

执行阶段

在执行阶段,开始代码逐行执行。

  • 变量赋值
  • 函数引用:执行到函数时,将创建好的执行上下文压入调用栈的顶部,如果在函数内部有遇到函数的执行,将重复上述创建和执行的过程。

代码示例

通过一两个小案例讲解一下执行上下生成的过程。

示例1

js 复制代码
function fn(a) {
  console.log(a);
  var a = 123;
  console.log(a);
  function a() {}
  console.log(a);
  var b = function () {};
  console.log(b);
  function d() {}
}
fn(1);

在将要执行fn(1)时,开始进入fn执行上下文的创建阶段

  1. 创建一个fn的执行上下文AO
text 复制代码
AO = {
    argument: { length: 0 }
    /* this和作用域链忽略 */
}
  1. 将形参和变量声明放入argumnets和AO中
text 复制代码
AO = {
    arguments: { 0: undefined, length: 1 },
    b: undefined
}
  1. 实参赋值到arguments中
text 复制代码
AO = {
    arguments: { 0: 1, length: 1},
    b: undefined,
}
  1. 找到函数内的函数声明,以函数名为属性,函数体为值,添加到AO中去.
text 复制代码
AO = {
    arguments: { 0: [Function a] },
    b: undefined,
    d: [Function d]
}

到此创建阶段结束。准备开始执行阶段

  1. 将创建的函数执行上下文压入调用栈的顶部
  2. 开始逐行执行代码
  3. 执行第一个console。此时在执行上下文中可以看到a是一个函数。因此浏览器打印为 f a() {}
  4. 执行到var a = 123时,将AO中的参数变量改为了123
text 复制代码
AO = {
    arguments: { 0: 123 },
    b: undefined,
    d: [Function d]
}
  1. 执行到第二console。打印123
  2. 执行到第三个console。也打印123
  3. 执行到var b = funciton () {},将AO的属性b的值修改
text 复制代码
AO = {
    arguments: { 0: 123 },
    b: [Function b],
    d: [Function d]
}
  1. 执行到第四个console。浏览器打印为f () {}
  2. 函数执行完毕。将fn的执行上下文推出调用栈

示例2

js 复制代码
function test() {
  console.log(b);
  if (a) {
    var b = 100;
  }
  c = 234;
  console.log(c);
}
var a;
test();

a = 10;
console.log(c);
  1. 全局执行上下文创建阶段,创建一个变量对象GO,并找到变量声明放入GO。代码中 c = 234为隐式声明全局变量,强烈不推荐此写法
text 复制代码
GO = {
   a: undefined,
   c: undefined
}
  1. 找到函数声明并放入GO
text 复制代码
GO = {
   a: undefined,
   c: undefined,
   test: [Function test]
}
  1. 全局执行上下文执行阶段逐行执行代码。直到test(),将为test创建一个全新的函数执行上下文。
text 复制代码
AO = {
    arguments: { length: 0 },
    [[Scopes]]: GO // 作用域链 这就是为什么可以访问外部变量的原因
}
  1. 没有参数,跳过,找到变量声明。
text 复制代码
AO = {
    arguments: { length: 0 },
    b: undefined,
    [[scopes]]: GO
}
  1. 开始test执行上下文的执行阶段,将test执行上下文压入栈顶。逐行执行代码。
  2. 执行第一个console。AO中b还没有赋值。此打印undefined。这就是所谓的变量提升的原理所在
  3. 执行到if(a) { var b = 100 }时,按照作用链查找规则,在全局对象下有a属性但值为undefined。因此没有进入{}内对b的赋值。
  4. 执行c=234,同样按照作用链查找规则,在全局对象下有c属性,因此执行赋值操作
text 复制代码
GO = {
   a: undefined,
   c: 234,
   test: [Function test]
}
  1. 执行第二个console,也是按照作用链查找规则,因此打印234
  2. test执行上下文运行完毕,将test执行上下文推出栈外
  3. 执行a = 10的赋值操作
text 复制代码
GO = {
   a: 10,
   c: 234,
   test: [Function test]   
}
  1. 执行第三个console。打印234

小结

本章主要讲解了以下内容:

  1. 执行上下文的概念
  2. 执行上下文的有哪些类型
  3. 执行上下文的生成过程分为两个阶段:创建阶段和执行阶段。每个阶段处理了什么。
  4. 通过示例讲解执行上下文生成过程以及对代码的影响。 理解执行上下文对于我们对JS代码的运行有着至关重要的作用。但是本章还没有详细讲解作用域和作用域链以及This的指向的知识点。下一章将详细讲解作用域和作用域链、以及于执行上下文的关系。
相关推荐
AI视觉网奇2 小时前
rknn yolo11 推理
前端·人工智能·python
gplitems1232 小时前
Gunslinger – Gun Store & Hunting WordPress Theme: A Responsible
开发语言·前端·javascript
Winson℡5 小时前
React Native 中的 useCallback
javascript·react native·react.js
wyzqhhhh5 小时前
less和sass
前端·less·sass
Nan_Shu_6146 小时前
学习:uniapp全栈微信小程序vue3后台-额外/精彩报错篇
前端·学习·微信小程序·小程序·uni-app·notepad++
excel7 小时前
Vue3 中的双向链表依赖管理详解与示例
前端
谢尔登7 小时前
【Nest】基本概念
javascript·node.js·express
前端小白从0开始8 小时前
Chrome DevTools高级用法:性能面板内存泄漏排查
前端·chrome·chrome devtools
EveryPossible8 小时前
带有渐变光晕
前端·javascript·css
jojo是只猫8 小时前
Vue 3 开发的 HLS 视频流播放组件+异常处理
前端·javascript·vue.js