wuhuihao
Frontend Developer
Hangzhou,Wenzhou

JavaScript-作用域相关

当我们JavaScript代码定义一个变量,如何去确定这个变量的作用范围?当我使用一个变量的时候,如何去确定这个变量的值?这就是这篇文章所需要探讨的内容。

执行环境(execution context)

执行环境是一个规范机制用于追踪运行环境的代码,这些代码是基于ECMAScript的实现。在任何一个时刻,最多只有一个执行环境在处理代码,这就是所谓的运行环境。

每次当控制流转到某一段可执行代码的时候,即会进入到一个执行环境。执行环境定义了变量或函数有权访问的其它数据,决定了它们各自的行为。

执行环境的类型总共有两种,全局执行环境和局部执行环境(函数)。

全局执行环境(global context)

代码在宿主环境开始执行时所创建的执行环境成为全局执行环境,是最外围的执行环境。

局部执行环境

控制流转入到一个函数中的代码时会创建一个执行环境,这个执行环境便是局部执行环境。

变量对象

每个执行环境都有个与之相关联的变量对象(variable object),环境中所创建的变量和函数都保存在这个对象中。

活动对象

当代码执行进入到函数时,会为当前函数创建局部执行环境,该函数的活动对象即为该环境的变量对象,活动对象初始包含arguments和该函数的命名参数。

执行环境栈

当代码在宿主环境开始执行或者进入到一个函数中都会创建一个执行环境,并推入到一个堆栈中。当代码执行完毕后将这个执行环境从堆栈中推出,并将控制权交还给上一个执行环境。因为代码总是在宿主环境中开始执行的,所以堆栈的底部永远是全局执行环境。

作用域

全局作用域

在函数之外的范围为全局作用域,在全局作用域中定义的变量为全局变量。

函数作用域

与Java和C等语言不同的时,JavaScript只能通过函数创建独立的作用域,for和if等语句用花括号包裹的代码块等不能创建独立的块作用域。

作用域模型—词法作用域

JavaScript采用的是词法作用域,即作用域在词法分析阶段就已经确定,由于只有函数才能创建独立的作用域,作用域的范围在函数定义的时候就已经确定了。

作用域链

为了保障对执行环境内有权访问的变量和函数的有序访问,后台会为每个环境的变量对象创建一个作用域链。作用域链相当于一个指针数组,存储着指向各个执行环境的变量对象的引用。作用域链的最前端时当前的执行环境,接下去是当前环境的外围环境。当代码执行读取或写入操作需要根据标识符去获取一个变量的值的时候,会经历一个根据作用域链所有的过程。

###Example


var v1 = 1;

function scope1(){
    var v2 = 2,
        v3 = 3,
        foo;
    
    foo = function(x){
        var v4 = 4,
            v5 = 5,
            res;
        
        res = v1 + v2 + v3 + v4 + v5 +x;
    
        return res;
    }
	
    console.log(foo(6));
}
	
scope1();

在上面的例子中,可以看到有2个局部作用域,分别由scope1函数和foo函数创建,foo函数创建的作用域被scope1函数创建的作用域包围,scope1函数创建的作用域被全局作用域包围。


var x = 10;

function fn(){

    console.log(x);
}


function show(f){

    var x = 20;

    f();
}

show(fn);  //10

在上面给出的例子中,可以看到有2个局部作用域,分别由函数fn和函数show创建,都被直接包含在全局作用域中。