js的执行上下文

javascript的执行上下文/(Execution context,EC)

作为个前端菜鸟,code时经常会遇到一些”奇怪”的行为,不是很明白为什么JavaScript会这么工作,在网上查找了一些技术博客,在这里介绍下js的执行上下文这个知识点,我本人也是一知半解,大家共享。

在JavaScript中有三种代码运行环境:

  • Global Code:JavaScript代码开始运行的默认环境
  • Function Code:代码进入一个JavaScript函数
  • Eval Code:使用eval()执行代码

为了表示不同的运行环境,JavaScript中有一个执行上下文(Execution context,EC)的概念。也就是说,当JavaScript代码执行的时候,会进入不同的执行上下文,这些执行上下文就构成了一个执行上下文栈(Execution context stack,ECS)。

例如对如下面的JavaScript代码:

var a = "global var";

function foo(){
    console.log(a);
}

function outerFunc(){
    var b = "var in outerFunc";
    console.log(b);

    function innerFunc(){
        var c = "var in innerFunc";
        console.log(c);
        foo();
    }

    innerFunc();
}


outerFunc()

代码首先进入Global Execution Context,然后依次进入outerFunc,innerFunc和foo的执行上下文,执行上下文栈就可以表示为:
这里写图片描述

当JavaScript代码执行的时候,第一个进入的总是默认的Global Execution Context,所以说它总是在ECS的最底部。

对于每个Execution Context都有三个重要的属性,变量对象(Variable object,VO),作用域链(Scope chain)和this。这三个属性跟代码运行的行为有很重要的关系,下面会一一介绍。

当然,除了这三个属性之外,根据实现的需要,Execution Context还可以有一些附加属性。

这里写图片描述

VO与AO

VO:变量对象是与执行上下文相关的数据作用域。它是一个与上下文相关的特殊对象,其中存储了在上下文中定义的变量和函数声明。也就是说,一般VO中会包含以下信息:

  • 函数声明 (Function Declaration, FD);
  • 函数的形参
  • 变量 (var, Variable Declaration);

当JavaScript代码运行中,如果试图寻找一个变量的时候,就会首先查找VO。对于前面例子中的代码,Global Execution Context中的VO就可以表示如下:

这里写图片描述

注意,假如上面的例子代码中有下面两个语句,Global VO仍将不变。

(function bar(){}) // function expression, FE
baz = "property of global object"

也就是说,对于VO,是有下面两种特殊情况的:

  • 函数表达式(与函数声明相对)不包含在VO之中
  • 没有使用var声明的变量(这种变量是,”全局”的声明方式,只是给Global添加了一个属性,并不在VO中)

AO:只有全局上下文的变量对象允许通过VO的属性名称间接访问;在函数执行上下文中,VO是不能直接访问的,此时由激活对象(Activation Object,缩写为AO)扮演VO的角色。激活对象 是在进入函数上下文时刻被创建的,它通过函数的arguments属性初始化。

Arguments Objects 是函数上下文里的激活对象AO中的内部对象,它包括下列属性:

  • callee:指向当前函数的引用
  • length: 真正传递的参数的个数
  • properties-indexes:就是函数的参数值(按参数列表从左到右排列)

对于VO和AO的关系可以理解为,VO在不同的Execution Context中会有不同的表现:当在Global Execution Context中,可以直接使用VO;但是,在函数Execution Context中,AO就会被创建

当上面的例子开始执行outerFunc的时候,就会有一个outerFunc的AO被创建:

这里写图片描述

细看Execution Context

当一段JavaScript代码执行的时候,JavaScript解释器会创建Execution Context,其实这里会有两个阶段:

  • 创建阶段(当函数被调用,但是开始执行函数内部代码之前)
    • 创建Scope chain
    • 创建VO/AO(variables, functions and arguments)
    • 设置this的值
  • 激活/代码执行阶段
    • 设置变量的值、函数的引用,然后解释/执行代码

对于”创建VO/AO”这一步,JavaScript解释器主要做了下面的事情:

  • 根据函数的参数,创建并初始化arguments object
  • 扫描函数内部代码,查找函数声明(Function declaration)
    • 对于所有找到的函数声明,将函数名和函数引用存入VO/AO中
    • 如果VO/AO中已经有同名的函数,那么就进行覆盖
  • 扫描函数内部代码,查找变量声明(Variable declaration)
    • 对于所有找到的变量声明,将变量名存入VO/AO中,并初始化为”undefined”
    • 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性

看下面的例子:

function foo(i) {
    var a = 'hello';
    var b = function privateB() {

    };
    function c() {

    }
}

foo(22);

对于上面的代码,在”创建阶段”,可以得到下面的Execution Context object:

fooExecutionContext = {
    scopeChain: { ... },
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        c: pointer to function c()
        a: undefined,
        b: undefined
    },
    this: { ... }
}

在”激活/代码执行阶段”,Execution Context object就被更新为:

fooExecutionContext = {
    scopeChain: { ... },
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        c: pointer to function c()
        a: 'hello',
        b: pointer to function privateB()
    },
    this: { ... }
}

后记

这篇文章基本上是copy网上的代码,不好意思,哈哈哈

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值