10分钟吃透js作用域链原理

发布者: xiaozhimn

在讲到作用域链的时候先来了解几个概念:
作用域

a. 作用域是指当前正在执行的代码能够访问到变量的范围;
b. 每个函数都有各自的作用域,存储函数所有的局部变量;

变量对象

a.变量对象用于存储函数各自的局部变量;
b.每个函数都有各自的变量对象,并且在函数执行时被创建;
c.“每个函数都有各自的作用域,用于存储函数的局部变量”,其实这句话并不严谨。作用域中存储的其实是变量对象的引用,而变量对象才是存储函数局部变量的地方.

作用域链

a. 把多个作用域串起来便形成了作用域链;
b. 每个函数在初始化完成之后就拥有了各自的作用域链,但此时的作用域链中并不包含自己的作用域;只有当函数执行时,才会创建       
   自己的作用域,并加入到作用域链的开头;
c. 作用域链中不仅存储了函数本身的作用域,还存储了该函数能够访问的其他函数的作用域;

接下来快速进入代码演练,更加形象化的对作用域链模型有更加深刻的了解。
通过以下代码我们来逐步分析在执行以下函数后,执行环境栈,作用域链以及变量对象是如何协同工作的。

var 全局变量 = "柴毛毛";
function 外层函数(){
    var 局部变量1 = "大闲人";

    return function(){
        var 局部变量2 = "是傻逼";
        return 全局变量+局部变量1+局部变量2;
    };
}
var 函数 = 外层函数();
函数();

1. 首先初始化全局执行环境

1. 创建全局变量对象。其中包含所有的全局变量,上述代码中分别是“全局变量”和“外层函数”。
2. 创建全局作用域链。该作用域链中只包含一个全局变量对象。
3. 创建外层函数的作用域链。我们知道,函数一旦被初始化后就会创建它的作用域链,只不过这个作用域链中不包含函数本身的作用   
   只包含其父级函数的作用域链。这里就是全局作用域。
4. 创建全局执行环境。全局环境通过一个指针指向它的作用域链,作用域链中又通过指针指向它的变量对象。

通过上面的过程,大家应该了解了这个过程是从右到左进行的。

2. 调用外层函数时

1. 创建外层函数的变量对象。变量对象中包含外层函数的全部局部变量,这里分别是“局部变量1”和那个匿名函数。
2. 将当前函数的作用域添加到当前函数作用域链的顶部。也就是把先前创建的“外部函数作用域链”中第一个作用域的指针指向“外部函    
   变量对象”,第二个指针指向“全局变量对象”。
3. 将“外层函数的执行环境”压入执行环境栈的顶部。PS:执行环境栈顶表示当前正在执行的环境。

3.调用闭包时

1. 销毁“外层函数”的作用域链和执行环境。
2. 创建闭包的变量对象。
3. 创建闭包的作用域,并压入闭包作用域链的头部。
4. 创建闭包的执行环境,并指向闭包的作用域链。 
5. 只有当闭包执行结束后,“外层函数的变量对象”才会被释放,否则它将一直驻留内存,因此闭包会比普通函数占用更多的内存,因    此要慎用!

4.变量查找过程

当上述代码执行到“return 全局变量+局部变量1+局部变量2;”时,此时执行环境栈的栈顶是闭包的执行环境,因此通过闭包的作用域链寻找这三个变量的值。 查找过程首先从作用域链的顶部开始,首先在闭包变量对象中寻找“全局变量”的值,若没有,则去外层函数的变量对象中查找;若仍未找到,则去全局变量对象中查找,直到找到为止;若在全局变量对象中仍未找到,则查找失败。 
若在某一个变量对象中找到该值,则立即停止查找。 PS:查找过程必须从作用域链的头部开始,依次向后查找。

通读全文后,大家应该对闭包为什么会产生内存泄露有了更进一步的了解了吧!

0赞