用垃圾回收机制解释JavaScript中的闭包

4,871 阅读4分钟

说起javascript中的闭包,首先要知道为什么会存在闭包,其作用又是什么。且为什么闭包中就能让外层函数的变量始终保存呢?下面我们将从这两个角度去剖析它。当然,大神绕道,谢谢哈。

开门见山,直接总结闭包的两大核心作用:

  1. 读取函数内部的变量;
  2. 让变量始终保存在内存中。

一、读取函数内部的变量

众所周知,变量在javascript中有全局变量和局部变量,函数内部可以访问外部的全局变量,而外部无法访问函数内部的局部变量,这是JS的一个特点:

var n = 999;
function f1() {
    console.log(n) 
}
f1(); // 999 

那么摆在我们面前的一个问题就是:如何在函数外部访问到函数内部的变量?有一种办法就是在函数内部再定义一个函数,通过这个内层函数去访问外层函数的变量,因为内层函数同属外层函数的作用域中(符合链式作用域结构规则,子对象可以一级一级地向上寻找所有父对象的变量):

funtion f1() {
    var n = 999;
    function f2() {
        console.log(n);
    }
    return f2;
}

var result = f1();
result(); // 999

对此:阮一峰老师给出了一个通俗的关于闭包的解释: 闭包就是能够读取其他函数内部变量的函数(关于到底是内层函数是闭包还是外层函数是闭包的解释各方不一致,但这不是重点)。

由于在JS中只有函数内部的子函数才能读取局部变量,因此也可以理解为定义在一个函数内部的函数。

二、让变量始终保存在内存中

暂且我们说闭包是指有权访问另一个函数作用域中的变量的函数吧,函数内部的函数使用到外层函数的变量,使得外层函数的变量的生存时间延长,造成常驻内存。

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

var baz = foo();

baz(); // 3
baz(); // 4
baz(); // 5
baz(); // 6

上面的例子为什么每次调用baz时,a都没有被初始化赋值呢? 接下来就要从JS的垃圾回收机制去考虑了。

1. 垃圾回收机制

通常我们可以使用引用计数法判断代码中的变量是否被释放,即语言引擎中有一张“引用表”,保存了内存里面所有的资源(通常是各种值)的引用次数,如果一个值的引用次数为0,则表示该值不再用到了,因此改值也被内存释放了。比如:

cont arr = [1,2,3];
// 数组[1,2,3]是一个值,会占用内存变量arr是仅有的对这个数组的引用,因此引用次数为1。尽管只赋值一次,后面代码中再没有调用,但却依然占据内存.

譬如JS这样的高级程序语言中都嵌入了一种称为垃圾回收器的机制,其工作是跟踪内存的分配和使用,以便发现任何时候不再需要的内存并对其进行释放。举例如下:

var o1 = {
    o2: {
        x: 1
    }
};
//创建2个对象o2和o1,其中o2被o1对象引用作为其属性,此时没有垃圾可收集

var o3 = o1; //创建变量o3,引用由o1指向的对象的变量
o1 = 1 ; //现在将o1重新赋值为1,最初的o1中的对象由o3变量表示
var o4 = o3.o2; //创建变量o4,引用对象o2,此时o2被两个地方引用:一个是作为o3变量的属性,一个是作为o4变量
o3 = '666'; // 此时最初o1对象应没有再被引用了,可以被垃圾收集了,但是最初的o2还在被o4引用,因此还不能被垃圾收集

o4 = 16; //此时 o2也可以说再见了...

2. 解释内存保留

好了,现在再回过头来看看上面那个闭包题,用垃圾回收的思想来作出解答:

function foo(){
    var a = 2;
    function outer() {
        a += 1;
	console.log(a);
    }
    return outer
}

var baz = foo(); //在全局作用域下创建变量baz

此时,baz指向的就是 outer,所以outer始终都没有被销毁,而根据垃圾回收机制,由于在outer中有引用外层函数的变量a,因此a也一直没有被销毁,所以就出现这种现象:

baz(); //3
baz(); //4
baz(); //5

是不是以下就明白了,接下来看一个简单的经典例题

三、经典例题

看一下经典的面试题吧,用垃圾回收的思想来思想一下结果,就会很顺利:

var callbacks;
for(var i = 0 ; i <= 5 ;i ++) {
   callbacks = function() {
      console.log(i);
   }
}
callbacks(); //6
callbacks(); //6
callbacks(); //6

参考文献

  1. 阮一峰老师 《学习Javascript闭包》 www.ruanyifeng.com/blog/2009/0…
  2. 《重读JavaScript高级程序设计》 reng99.cc/2018/03/01/…