一.垃圾回收机制
JavaScript自动回收不再使用的变量,释放其所占的内存,开发人员不需要手动的做垃圾回收的处理.
垃圾回收机制只会回收局部变量.全局变量并不会被回收(全局变量在浏览器关闭之后会回收),所有当我们定义了一个全局对象时,使用完毕之后,最好给它重新复制为null,以便释放其所占的内存(这个变量并没有被回收,只是改变了他的志向,减少内存占用)
目前浏览器基本都使用标记清除(介绍…)的方式,还有一种不常见的引用计数(介绍…)方式
1.标记清除:
当某个变量不再被使用时,该变量就会被回收.
2.引用计数
极少数浏览器(如IE)上针对引用类型数据的回收机制.
当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1,如果这个变量的值又被赋值给了另外一个变量(即两个变量的地址都指向同一个引用类型),则该值的引用次数+1。相反,如果包含这个引用类型的变量又取了另外一个值,则引用次数-1。当这个引用类型的引用次数变为 0 时,则说明无法再访问这个变量。当垃圾收集器下次再运行时,它就会释放引用次数为 0 的值所占用的内存。
二.闭包
1. 什么是闭包
在了解什么是闭包之前,我们先看下面的一个例子:
在上面这几行代码中,有一个局部变量a,有一个函数add, add里面可以访问到变量 a ,这就是一个闭包.
概念:
一个函数中嵌套另一个函数,内部函数使用了外部函数的参数或变量,就构成了闭包(这个内部函数就叫做闭包)。被内部函数使用的外部函数的参数或变量,不会被js的垃圾回收机制所回收
函数和函数内部能访问到的变量的总和就是一个闭包.
(function () {
var a = 1;
document.onclick = function () {
var b = 10;
console.info(++a); // 点击页面后依次输出 2 3 4 5 ...
console.info(++b); // 点击页面始终都输出 11
}
})()
以上代码,局部变量在被使用后会被回收,但由于内部函数使用外部函数的了变量a,所以变量a不会被回收。而 变量b是一个局部变量且没有构成闭包,所以内部函数中 变量b 使用完后会被回收
补充:
- 在使用闭包的时候为什么会用到嵌套函数?
function outer() {
var a =123;
function inner() {
console.log(a);
}
return inner;
}
var in = outer();
in();
因为我们需要的是局部变量,所以才将a放在一个函数里面,如果不把a 放在一个函数中,那么a就成了一个全局变量.这样就失去了使用闭包的目的 ----- 隐藏变量
所以函数套函数只是为了造出一个局部变量,跟闭包无关。
为什么需要return?
如果不 return 就没有办法使用这个闭包, return 的目的就是为了能够在全局中访问到 inner 函数.
return inner
等价于
window.inner = inner
两条语句都是为了将inner函数暴露,以便于我们能够在全局中调用这个函数.
2. 闭包的作用
闭包的作用
闭包常常用来间接的访问一个变量,通俗来讲就是隐藏一个变量.
一个例子:
假设我们此时正在做一个游戏,关于你还剩多少条命
如果不使用闭包,我们可以在全局中定义变量
window.lives = 10;
这样做其实是很不好的,万一不小心改变了这个值怎么办??
我们不能让别人直接的访问到这个变量,如何解决?
使用一个局部变量,这样做也是不合适的,因为使用局部变量别人又访问不到…
这个时候就要请出来闭包了,暴露一个函数,让别人可以直接访问
(function() {
var lives = 10;
window.add = function () {
lives++;
}
window.sub = function () {
lives--;
}
}())
//增加一条命
add();
//减少
sub();
那么在其他的 JS 文件,就可以使用 window.add() 来涨命,使用 window.sub() 来让角色掉一条命。
该函数中存在两个闭包:
3. 闭包的性质
闭包的性质在于每次重新引用函数的时候,闭包都是全新的
function outer(){
var count = 0;
function inner(){
count++;
console.log(count);
}
return inner;
}
var inn1 = outer();
var inn2 = outer();
inn1(); //1
inn1(); //2
inn1(); //3
inn1(); //4
inn2(); //1
inn2(); //2
inn1(); //5
4. 闭包经典案例
<body>
<p>111</p>
<p>222</p>
<p>333</p>
<p>444</p>
<p>555</p>
<script>
var op = document.querySelectorAll('p');
for(var i = 0 ; i < op.length ; i++) {
op[i].onclick = function () {
window.alert(i); //每次弹出的i都是5
}
}
</script>
</body>
如果不了解闭包的话,我们就会理所当然的认为每次点击p会弹出响应的0,1,2,3,4,但实际的结果却是每次弹出都是5
方案一: 这时候就需要加一层闭包,i 以函数参数形式传递给内层函数:
这样的话就可以形成一个闭包,可以弹出我们想要的值.
点击222,弹出如下结果:
使用 ES6 - let 替换 var ,则不需要进行函数嵌套,如下:
<body>
<p>000</p>
<p>111</p>
<p>222</p>
<p>333</p>
<script>
var oP = document.querySelectorAll('p')
for (let i = 0; i < oP.length; i++) {
oP[i].onclick = function () {
console.info(i);
}
}
</script>
</body>
原理:let 是块级作用域,即每次for循环的 大括号内都是一个独立的作用域,而内部函数则是一个子作用域,也相当于作用域内嵌套子作用域(也类似形成了闭包),所以每次循环的 i 不会被回收。