用垃圾回收机制解释JavaScript中的闭包
栏目: JavaScript · 发布时间: 5年前
内容简介:说起javascript中的闭包,首先要知道为什么会存在闭包,其作用又是什么。且为什么闭包中就能让外层函数的变量始终保存呢?下面我们将从这两个角度去剖析它。当然,大神绕道,谢谢哈。开门见山,直接总结闭包的两大核心作用:众所周知,变量在javascript中有全局变量和局部变量,函数内部可以访问外部的全局变量,而外部无法访问函数内部的局部变量,这是JS的一个特点:
说起javascript中的闭包,首先要知道为什么会存在闭包,其作用又是什么。且为什么闭包中就能让外层函数的变量始终保存呢?下面我们将从这两个角度去剖析它。当然,大神绕道,谢谢哈。
开门见山,直接总结闭包的两大核心作用:
- 读取函数内部的变量;
- 让变量始终保存在内存中。
一、读取函数内部的变量
众所周知,变量在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 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。