执行上下文与作用域链

执行上下文

查漏补缺

  • 栈:数据结构的一种。先进后出,后进先出。

  • 执行栈(调用栈):存储在代码执行期间创建的所有执行上下文,同样具有后进先出的特点。


EC:执行上下文

每当控制器运行到一段可执行代码时,控制器就进入了一个执行上下文,分为三种

  1. 全局代码:默认代码环境,JS引擎最先进入的环境
  2. 函数代码:执行一个函数时,函数内部的代码
  3. Eval代码:在Eval函数内的代码

执行上下文可以看做是一个对象,其中包含了:

  1. this
    • 直接调用函数this指向全局对象
    • 在函数外调用 this指向全局对象
    • 通过对象调用、new函数,this指向调用对象或new出的新对象
  2. scope: 作用域链
  3. VO(variable object):变量对象,该对象包含
    • arguments对象
    • 参数
    • 内部变量
    • 函数声明
  EC = {
    this: {}
    VO: { arguments, 参数, 变量, 函数声明 },
    Scope: { VO,所有父级执行上下文的VO }
  }

执行上下文栈(ECS)

  • 全局执行上下文:所有JS执行前,必须有该环境。
  • JS引擎始终执行执行上下文栈 栈顶的 执行上下文
  • 栈顶是当前执行上下文
  1. JS引擎首先进入全局执行上下文,导致全局执行上下文在执行上下文栈的底部(栈底)

  2. 当在全局上下文中调用函数则会进入一个函数执行上下文,该函数执行上下文就会被压入执行栈的顶部,执行过程中遇到新的函数调用则又压入新的函数的执行上下文。(JS引擎总会执行栈顶的上下文,所以每压入一个新的执行上下文就会打断原来的执行上下文,并且开始执行新的执行上下文的内容)

  3. 当前函数上下文执行完毕后,会被弹出执行上下文栈,然后继续之前打断的执行期上下文继续执行。

即使是递归函数,每次进入也都会创建一个新的执行上下文,但是函数对象只有一个所以[[scope]]只有一个

执行上下文栈的操作类似于数组的push和pop搭配操作,最后一个push进入数组的数据的总是第一个被pop出数组


变量对象 VO(variable object)

每一个执行上下文都包含一个VO,VO可分为两种

  1. GO(Global object):全局变量对象
  2. AO(Active object):当前正在活跃(执行)的上下文中的VO

执行上下文的建立

执行上下文的建立分为两个步骤,进入执行上下文 以及 执行阶段

  • 进入执行上下文

进入执行上下文后按顺序执行以下步骤:

  1. 确定所有形参的值(即将实参的值赋值给形参的同名变量),若未传参则设为undefined,确定arguments的值。
  2. 确定通过字面量声明的函数,将该变量指向函数对象(若VO中有同名变量则覆盖该变量)
  3. 变量声明:将在函数内定义的变量(通过var声明的变量)值设为undefined(如var a = 1; 拆分为var a = undefined;先进行执行,然后把a = 1;语句等待执行阶段依次执行到该语句后赋值若VO中有同名变量则不会影响该变量)
  • 执行阶段

依次执行代码,将一些有赋值的属性进行赋值。若执行上下文时存执行到某个不存在的属性,则会从之前打断的上下文中进行查找

  • 举个例子
  console.log(a, b); // f a() {}, undefined
  var a = 1;
  var b = 2;
  console.log(a, b);  // 1, 2
  function a () {};
  var b = function() {};
  console.log(a, b);  // 1, f(){}
  1. 进入执行上下文,未执行
    1. 函数声明:将a 的值设置为 a函数 ,(字面量声明函数后,该声明语句在执行阶段可忽略)
    2. 变量声明:b的值设为undfined
    3. 此时a的值为函数a,b的值为undefined
  2. 执行阶段
    1. 输出a (值为函数a) 输出b(值为undefined)
    2. 将1赋值给a,将2赋值给b
    3. 输出a(值为1),输出b (值为2)
    4. 将匿名函数赋值给遍历b
    5. 输出a(值为1),输出b(值为匿名函数)

  function test(a, b, c, d) {
    console.log(a, b, c, d); 
    // 10, 20, ƒ c() {}, undefined
    var a = 1;
    var b = function () {}
    function c() {}
    console.log(a, b, c, d);
    // 1, ƒ () {}, ƒ c() {} , undefined
  }
  test(10, 20, 30)
  1. 进入函数执行上下文,未执行
    1. 确定形参的值,将AO内创建变量a,b,c,d。且a值设置为10,b的值设置为20, c的值为30,未传入的d为undefine(此时arguments内有传入实参的三个值,d没有实参不在arguments内)
    2. 函数声明:将c的值设置为c函数 覆盖原有的c(值为30的c)(字面量声明函数后,该声明语句在执行阶段可忽略)
    3. 变量声明:此时AO内已经存在有a,b的值,所以a不会设置为undefined,b也不会设置为函数对象
    4. 此时a的值为10,b的值为20,c的值为函数c,d的值为undefined
  2. 函数执行阶段
    1. 输出a (值为函数10) 输出b(值为20),输出c(函数对象c),输出d(undefined)
    2. 将1赋值给a,将匿名函数赋值给b
    3. 输出a(值为1),输出b (值为匿名函数),输出c(函数对象c),输出d(undefined)

即使变量在不会执行的代码块内定义(条件不成立的if,while等内)也同样会有变量提示,值为undefined而不会报错

立即执行函数若不为匿名函数,也不会出现在VO中


作用域链 scope

作用域链在函数创建时就已经存在

  • VO中包含一个属性,该属性指向创建该VO的函数本身,全局上下文中没有(即全局代码中执行了A函数创建了函数A的执行上下文,A的执行上下文的VO内含有一个属性指向函数A,该属性目的为了作用域链可以往上查找到上一个函数的[[scope]]从而访问上一个执行上下文的VO)

  • 函数创建时会有一个属性[[scope]],该属性指向创建该函数时的执行上下文的AO(即指向创建新的执行上下文时被打断的执行上下文的AO),创建完新的执行上下文后,AO会变为当前新建的指向上下文的VO。[[scope]]的值是静态的不会改变的

  • 当访问一个变量时,先查找自身VO中是否存在,如果不存在,则依次查找[[scope]]属性
    [[scope]]指向上一个被打断的指向上下文的VO,依次往上直到GO

  • 简而言之,Scope内包含当前AO以及[[scope]]。而[[scope]]则指向上一个被打断的上下文的VO(里面也包含上一个上下文的[[scope]]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值