JS 环境执行栈、变量对象、执行上下文

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

图片描述

不是很准确的草图,大概了解就好。数组

  • 首先JS解释器(引擎)开始解释代码,构建执行环境栈(Execution Context Stack),并根据执行环境的不一样生成不一样的执行上下文(Execution Context)

  • 栈底永远是全局上下文(后面说),当遇到test(),确认调用函数,就建立生成test函数本身的上下文,而后将函数执行上下文入栈(push on)到执行环境栈中。

  • testEC(test Execution Context)将会开始建立变量对象,咱们知道,当调用一个函数(激活),一个新的执行上下文就会被建立。而一个执行上下文的生命周期能够分为两个阶段。

    1. 建立阶段:
      在这个阶段中,执行上下文分别会建立变量对象、创建做用域链,以及肯定this的指向。浏览器

    2. 代码执行阶段:
      建立完成后,就会开始执行代码,这个时候,会完成变量赋值,函数引用,以及执行其余代码。函数

  • 因此testEC将会生成一个变量对象,与之还有做用域链、this(这里先不讨论),注意,这里是变量对象的建立阶段,用VO简示。

    变量对象的建立,经历如下过程。测试

    1. 创建arguments对象。检查上下文中的参数,创建该对象下的属性与属性值(key-value)this

    2. 检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名创建一个属性,属性值为指向该函数所在内存地址的引用。若是函数名的属性已经存在,那么该属性将会被新的引用所覆盖。spa

    3. 检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名创建一个属性,属性值为undefined。若是该变量名的属性已经存在,为了防止同名的函数被修改成undefined,则会直接跳过,原属性值不会被修改。指针

因此,因而就有了图中的VO引用的对象的图示,这就是变量提高的真正缘由,对于函数声明,它的优先级是大于变量声明的,因此在建立阶段,函数声明的函数名就持有了函数的引用。而变量赋值须要在后面的执行阶段才被赋值。code

未进入执行阶段前,变量对象中的属性都不能被访问!可是进入执行阶段以后,变量对象转变为了活动对象(Active Object),里面的属性都能被访问了,而后开始进行执行阶段的操做。对象

  • 进入执行阶段,这时可称之为活动对象了(Active Object),将进行赋值操做。因而这时的变量就能被自由访问了。

关于arguments对象,是个类数组结构

目前个人测试:blog

  • 函数内设有形参,可是不传实参,遍历arguments对象时没有任何值

  • 函数内不设形参,可是传实参,遍历arguments对象有值对应的索引

function test(a,b,c,d) {
        for (var i in arguments) {
            console.log(i);
        }
        console.log(arguments.length+"个");
    };
    test(1,2,3,4);
    // 0 1 2 3
    // "4个"
    function test2(a,b,c,d) {
        for (var i in arguments) {
            document.write(i+"<br />")
        }
        document.write(arguments.length+"个");
    };
    test2();
    // 什么也没有
    // 0个

脑子里有这副图后,咱们在来看另外一个栗子:


function test() {
        console.log(foo());
        console.log(bar);
        
        var foo = "Hello";
        console.log(foo);
        var bar = function () {
            return "world";
        };
        function foo() {
            return "hello";
        };
    };
    test();
    // "hello"
    // "undefined"
    // "Hello"

主要讨论同变量声明与同变量声明与函数声明的状况,这也是变量对象在建立时所作的工做;

检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名创建一个属性,属性值为指向该函数所在内存地址的引用。若是函数名的属性已经存在,那么该属性将会被新的引用所覆盖。

检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名创建一个属性,属性值为undefined。若是该变量名的属性已经存在,为了防止同名的函数被修改成undefined,则会直接跳过,原属性值不会被修改。

  • 如代码中的foo,在test函数的执行上下文建立变量对象后,建立阶段,foo就是变量对象中的的键名(Key),而键值就是函数的地址指针,由于函数声明的优先级大于变量声明,因此foo此时就持有了函数的引用,而var foo的变量声明就被跳过。

  • 执行阶段,进行赋值操做,foo被从新赋值"Hello",同时变量bar从未赋值(undefined)到持有一个函数的引用,这就是变量对象=>活动对象,执行栈,执行上下文所发生的操做。


关于全局上下文

var globla = 10;
    function test() {
        return globla++;
    };
    test();


// 以浏览器中为例,全局对象为window
// 全局上下文
windowEC = {
    VO: window,
    scopeChain: {},
    this: window
}

以浏览器中为例,全局对象为window
全局上下文有一个特殊的地方,它的变量对象,就是window对象。而这个特殊,在this指向上也一样适用,this也是指向window。除此以外,全局上下文的生命周期,与程序的生命周期一致,只要程序运行不结束,好比关掉浏览器窗口,全局上下文就会一直存在。其余全部的上下文环境,都能直接访问全局上下文的属性。

相关文章
相关标签/搜索