什么是作用域链?图解JavaScript作用域链

247 阅读4分钟

概述

  • 执行环境是JavaScript中一个重要的概念。执行环境定义了变量或函数有权访问的其他数据,决定了他们各自的行为。
  • 执行环境一般会有 全局执行环境() 和 函数执行环境;每个函数都有自己的执行环境。

什么是作用域链

  • 每一段JavaScript代码(无论全局还是函数),都会产生一个它本身的作用域链(scope chain)。这个作用域链是一个对象列表或者链表,其中包含了它所属的执行环境中的变量和函数的声明,也会有函数中形参到实参的传递赋值。

  • 当执行流进入一个函数时,该函数执行环境就会被推入到一个环境栈中,等到函数执行完后,栈再将其环境弹出,把控制权返回给之前的执行环境。

  • JavaScript与C语言的作用域有所不同,js中并没有 像C语言中由花括号封闭的明显的块级作用域;

  • 举一个简单的例子:在 if 语句或 for 语句中定义了一个变量 i,如果是在C、C++或Java中,i会在语句执行完毕后销毁,但在JavaScript中,if或for语句中声明的变量将会被提升到当前的执行环境中,即在if/for语句块之外也能访问到这个变量i。

for(var i = 0; i < 10; i++){	
}
console.log(i);			// 可以打印出 10
  • JavaScript需要查找某个变量的时候(这个过程称做“变量解析”(variable resolution)),它会从scope chain中的第一个对象开始查找,如果找到了则会直接使用这个的值;如果第一个对象中没有找到,JavaScript会继续查找链上的下一个对象,以此类推。如果整个作用域链上都相符合的属性,那么就认为这段代码的作用域链上不存在这个变量,并最终抛出一个引用错误异常。例如:
        var a = 0;
        function test(){
            var b = 1;
            
            function test1(){
                var c = 2;
                console.log(b);      // 2
            }
            
            test1();
            console.log(c);         // ReferenceError: c is not defined
            console.log(a);         // 0
        }
        
        test();
        console.log(b);         // ReferenceError: b is not defined

作用域链的最前端,始终都是当前正在执行的代码的执行环境的变量对象;每一个执行环境都只可以从作用域链的前端向后端搜索,以访问变量和函数名;通俗来说就是内部执行环境可以访问外部(父级)执行环境,而外部不可以访问内部。

GO 和 AO 是什么

关于GO和AO的生成过程,打算在下一篇文章里写,发布之后再放上链接。

图解作用域链

这部分内容都用这一段代码来作示例:

function a(){
	function b(){
    	var b = 2;
    }
    var a = 1;
    b();
}
var c = 3;
a();
  • 在Web浏览器中,window对象是全局环境;在javascript开始执行前就会产生一个GO(global object),也叫全局执行期上下文;全局执行环境直到应用程序退出(例如关闭网页或浏览器时才会被销毁)。

  • 在全局开始执行后,a函数被定义(但还没执行),系统生成[[scope]]属性,来保存 a函数 的作用域链,这个时候 作用域链第 0 位 存储全局执行期上下文(GO),GO里存储全局下的所有对象,这个例子中包含函数a和全局变量c;

  • 下一步,当a函数刚要被执行时,a函数的函数执行期上下文(AO)存储到作用域链的顶端(第0位),同时将GO存储在第1位。在a函数的作用域链中从顶端开始依次向下查找变量;a函数执行,即b函数被定义;

  • 当b函数被执行时(前一刻),生成函数b的[[scope]],存储函数b的作用域链,顶端第0位存储b函数的AO,a函数的AO和全局的Go依次向下排列;

注意! 这里的b函数作用域链是b被执行时的,而不是b函数被定义时的,b是在a函数执行时就被定义了,所以b被定义时的作用域链其实就是和a函数执行时的作用域链是一样的。

  • 当b函数被执行结束后,b函数的AO被销毁,回归被定义时的状态;即 b函数的AO被销毁;

  • 再接下来 a函数被执行结束时,a函数的AO被销毁,a函数回归到被定义时的状态(也就是第一张图)。与此同时 b函数的[[scope]]也将不存在;

参考文献

《JavaScript高级程序设计》


本篇文章就写到这里,盆友萌 有发现我哪个地方写错了或者没写明白的,都可以留言交流!