谈谈js执行上下文

趁着有点空,来讲讲js的执行上下文吧。

先来做个题:

console.log(num)//打印出什么?
var num = 100

知道变量提升的同学可能就知道这个时候会直接打印出undefined而不是报错,那么为什么呢?


一、执行上下文。

        js是单线程,每当js的控制器转到可执行代码的时候,这个时候就会产生一个执行上下文,简单来说就是当前代码的一个执行环境,某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也会销毁,js中执行上下文大概包括三种。

        1.全局环境:整个js运行起来一开始就会进入的环境。高程上说:全局环境是最外围的环境,根据ECMAScript实现所在的宿主环境不同,表示执行环境的对象也不一样。在web浏览器中,全局执行环境被认为是window对象,因此所有的全局变量和函数都是作为window对象的属性和方法创建的。(注意:在es6中就不是这样的,在es6中,let,const,class声明的全局变量不属于全局对象,即window的属性。var和function声明的变量和函数属于全局对象的属性。)

        2.局部环境:也就是当函数被调用的时候,会进入函数局部环境。函数执行上下文被推入函数调用栈中,在函数执行完毕后从栈顶推出。控制权交还给之前的执行环境。

        3.eval:略


        在JavaScript运行的时候,一定会进入多个环境当中,而js会以栈的形式来处理这些环境,也就是函数调用栈(call stack),全局的执行上下文会在底部,而顶部一定是当前的执行上下文。

        所以在JavaScript运行的时候,一旦遇到以上三种情况,都会产生产生一个执行上下文放入栈中,当栈顶的执行上下文执行完毕后会自动出栈,另外全局环境的执行上下文会在关闭浏览器的时候出栈。


我们来用高程中的例子讲一下。

var color = 'blue';

function changeColor() {
    var anotherColor = 'red';

    function swapColors() {
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;
    }

    swapColors();
}

changeColor();

首先第一步,全局执行上下文入栈。


        当全局环境入栈后,遇到函数changeColor()的声明,注意现在这个时候changeColor并不会入栈,因为并没有调用,直到js继续往下走,调用了changeColor()后,changeColor()入栈。



        changeColor()入栈后,继续执行,直到遇到swapColors(),当调用了swapColors()以后,产生一个swapColors()执行上下文,接下来继续入栈。


        swapColors()执行完毕后,没有遇到能进一步产生执行上下文的代码,且本身代码块已经执行完毕了,这时候swapColors()出栈。


        swapColors()出栈后,继续执行changeColor()代码,当当前环境代码块执行完毕以后,继续出栈。


        这时只剩下全局环境执行上下文了。

        当当前栈顶的执行上下文未弹出的时候,第二栈到最底栈都是处于等待中的状态,另外执行上下文并没有个数的限制,只要调用了函数就会产生一个新的执行上下文。



二、变量对象

在执行上下文创建的时候,会分为两个步骤,即分为创建阶段以及执行阶段

创建阶段:创建变量对象,生成作用域链,确定this指向。

执行阶段:变量赋值,函数引用,代码执行。

在创建阶段执行完毕后,进入执行阶段开始代码执行。

在创建变量对象的时候,依次如下:

1.创建arguments对象。

2.检查当前上下文的函数声明,也就是function声明的函数,找到一个,就在变量对象中建立属性,属性名为函数名,属性值为该函数的引用。如果函数名的属性已经存在,那么属性将会被新的引用覆盖掉。

3.检查当前上下文的变量声明,每找到一个变量声明,就在变量对象新建一个属性,属性名为变量名,值为undefined,如果该变量名的属性已经存在,为了防止同名函数被改为undefined,则跳过不做处理。



// demo01
function test() {
    console.log(a);
    console.log(foo());

    var a = 1;
    function foo() {
        return 2;
    }
}

test();

当从全局环境中调用test()函数的时候,此时test()执行上下文创建并入栈:

// 创建过程
testEC = {
    // 变量对象
    VO: {},
    scopeChain: {}
}


// VO 为 Variable Object的缩写,即变量对象
VO = {
    arguments: {...}, 
    foo: <foo reference>  // 表示foo的地址引用
    a: undefined
}

此时上下文的创建阶段已经执行完毕,开始进入执行阶段。

// 执行阶段
VO ->  AO   // Active Object
AO = {
    arguments: {...},
    foo: <foo reference>,
    a: 1,
    this: Window
}

所以刚才代码的真正执行顺序为:

function test() {
    function foo() {
        return 2;
    }
    var a;
    console.log(a);
    console.log(foo());
    a = 1;
}

test();

这就是大名鼎鼎的变量提升啦~


参考:https://www.jianshu.com/p/330b1505e41d

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值