你真的了解执行上下文吗?

执行上下文是JavaScript代码执行的环境,分为全局、函数和Eval类型。JS引擎使用执行上下文栈来管理代码执行,每次只处理栈顶的上下文。本文详细介绍了全局和函数执行上下文的创建过程、变量对象、作用域链和生命周期,帮助理解执行上下文的原理和作用。
摘要由CSDN通过智能技术生成

执行上下文

执行上下文 可以理解为当前代码的执行环境,同一个函数在不同的环境中执行,会因为访问数据的不同产生不一样的结果。

执行上下文分为三种:

  • 全局执行上下文:只有一个,程序首次运行时创建,它会在浏览器中创建一个全局对象(window对象),使this指向这个全局对象
  • 函数执行上下文:函数被调用时创建,每次调用都会为该函数创建一个新的执行上下文
  • Eval 函数执行上下文:运行eval函数中的代码时创建的执行上下文,少用且不建议使用

执行上下文栈

执行上下文栈(Execution context stack,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
复制代码

执行过程可以在 devToolcall 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();
复制代码

因为f1中的函数f2在f1的可执行代码中,并没有被调用执行,因此执行f1时,f2不会创建新的上下文,而直到f2执行时,才创建了一个新的。具体演变过程如下。

es3版本

es3版本执行上下文内有三个重要属性:

  • 变量对象 VO(variable object)
  • 作用域链(scope chain)
  • this

可以将每个执行上下文抽象为一个对象。

执行上下文的组成代码示例:

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

变量对象

变量对象 是与执行上下文相联的数据作用域,用来存储上下文中定义的变量和函数声明。

不同执行上下文中的变量对象也不一样:

  • 全局上下文 中的变量对象就是全局对象,在浏览器中就是 window 对象。在顶层 JavaScript 代码中,可以用关键字 this 引用全局对象。所有的全局变量和函数都是作为 window 的属性和方法存在。
    console.log(this) //window
    var a=1 //挂到window上的属性
    console.log(window.a) //1
    console.log(this.a) //1
复制代码
  • 函数执行上下文 中我们用活动对象 AO (activation object) 来表示变量对象,因为变量对象是规范上的或者说是引擎实现上的,在 JavaScript 环境中是不能被直接访问的,只有当函数被调用时,变量对象被激活为活动对象时,我们才能访问到其中的属性和方法。
  活动对象就是变量对象,只不过处于不同的状态和阶段而已。
复制代码

作用域链

对于 JavaScript 来说作用域及作用域链的变量查询是通过存储在浏览器内存中的执行上下文实现的。当查找变量时,首先从当前上下文中的变量对象查找,如果没有就会往上查找父级作用域中的变量对象,最终找到全局上下文的变量对象,如果没有就报错。这样由多个执行上下文的变量对象构成的链表就叫做作用域链

关于作用域和作用域链可以参考我的另一篇文章: 掌握JavaScript面试:什么是闭包ÿ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值