愿你坚持不懈,努力进步,进阶成自己理想的人

—— 2017.09, 写给3年后的自己

理解JavaScript中的作用域链

在JavaScript中,作用域链是很重要的一块知识点,理解好作用域链,是提升JavaScript编程水平必不可少的

一、作用域链

在JavaScript的执行后台中,每个执行环境都会对应有一个变量对象VO和一个作用域链。这个作用域链实际上是一个链表,里面保存着对每个执行环境中的变量对象VO的引用。理解作用域链的核心知识点:
1)全局环境中存在一个全局的变量对象,保存着对全局变量、函数的引用
2)在创建一个函数的时候,就会新建一个作用域链,将当前能访问到的变量对象VO的引用加入到这个作用域链表中,并将这个作用域保存到内部的[[Scope]]属性中
3)当调用一个函数的时候,会从[[Scope]]属性中复制这个预先创建的作用域链来新建一个作用域链。然后会有一个活动对象AO(在这里作为VO使用)被推入到这个新建的作用域链中,其中包含有arguments对象和命名参数等
4)当在执行环境中访问一个变量或者函数的时候,就会先在当前的作用域链顶端的VO中进行查找,当查找不到的时候,就继续查找作用域链中的下一个VO,直到查找到全局VO为止


二、实例分析

现在假设有这么一段js代码:

function compare(value1, value2) {
    if (value1 < value2) {
        return -1;
    } else if (value1 > value2) {
        return 1;
    }
    return 0;
}

var result = compare(1, 2);

js引擎是这么处理的:
1)当前全局对象变量中包含有compareresult
2)因为创建了compare()函数,所以会同时创建一个作用域链,这个作用域链中现在包含的是全局对象变量的引用,然后这个作用域链会被保存在函数的内部属性[[Scope]]
3)在执行var result = compare(1, 2);的时候,调用了compare()函数,所以此时就去复制函数的[[Scope]]所保存的那个作用域链,新建出一个作用域链。然后再对应着有一个活动对象,这个活动对象中包含了arguments属性,其值为:{0:1, 1:2, length: 2},此外,还有命名参数value1value2,值则分别为:1、2,于是有了以上的分析,当前的各种关系下有联系图:


三、this绑定问题

在理解了作用域链后,我们可以更好地理解this绑定的问题。我们都知道,以下的代码:

var name = 'Global'
var object = {
    name: 'Object',
    getName: function() {
        console.log(this.name);
        return function() {
            console.log(this.name);
        }
    }
}

object.getName()();

在非严格模式下,会输出:

Object
Global

这是为什么呢?这是因为:
在调用object.getName()的时候,创建了一个活动对象,其中不仅有arguments对象,还有this对象,而this对象此时指向的是object,所以能得到Object这个值。然后,object.getName()返回的是一个匿名函数,这个匿名函数在执行的时候,this是绑定运行时环境的,所以this的取值就是全局对象window了。所以匿名函数的活动对象中,包含了this: window这么一个属性,由于在调用this.name的时候,沿着作用域链进行this的查找,首先找到的是匿名函数的活动对象,活动对象中有this,所以就停止了查找,最终效果就相当于访问了window.name,即Global


四、总结

作用域链保证了对执行环境中有权访问的变量和函数的访问是有序的,作用域链中的变量只能是向上访问,而向下访问则是禁止的