闭包
要理解闭包,首先必须理解js的变量作用域。作用域传送门
全局作用域在函数的作用域的作用域链上,所以函数内部可以直接使用全局变量。
函数的作用域不在全局作用域的作用域链上,所以在函数外部的全局中是无法读取函数内的局部变量。
1.什么是闭包
指有权访问另一个函数作用域中变量的函数(实际上就是内层函数)。(JavaScript高级程序设计的定义)
2.闭包形成的原因
外层函数的函数作用域对象,因为被内层函数作用域链引用这无法释放,就成了闭包
3.闭包三个特点
- 用外层函数包裹内层函数和要保护的变量
- 外层函数将内层函数返回到外部(return 只是其中一种方法)
- 使用者调用外层函数获得返回的内层函数对象,保存在一个变量中,今后可反复调用。
//外层函数
function parent(){
//要保护的变量
var total = 100;
//将内层函数(闭包函数)返回到外部
return function(money){
total-=money
console.log(total);
}
}
//接住内层函数(闭包函数)
var pay = parent();+
//调用闭包函数
pay(10);
结果: 反复调用内层函数,可重用外层函数的局部变量,且还不会被外部随意篡改。
4.闭包的原理
- 调用外层函数时
先会临时创建外层函数的作用域对象,其中保存了要保护的变量
然后执行函数体里的代码,return 出内层函数
(内层函数并没有被调用,所以只调用外层函数时,只是把它保存在了一个对象中然后抛到了全局)
由上可知内层函数的作用域链是三级: 自己的作用域 -> 外层函数的作用域对象 -> window
- 外层函数调用后
外层函数调用时临时创建的作用域对象和外层函数的局部变量都应该释放
但是因为内层函数的作用域链引用着外层函数的作用域对象,无法释放,就形成了闭包现象
- 将来调用返回到外面的内层函数时
先使用内层函数自己的局部变量,自己没有就沿着作用域链去上层作用域对象中查找。
- 内层函数调用后
只会释放自己的局部变量,不会释放外层函数的作用域,所以外层函数的作用域,得以永久保存。
5.闭包的缺点
比普通函数多占用一块内存空间(外层函数的作用域),容易造成内存泄漏。
闭包案例:
//1.
for(var i = 0; i < 5 ;i++){
setTimeout(function(){
console.log(i)
},500)
}
//2.
for(let i=0; i < 5 ;i++){
setTimeout(function(){
console.log(i)
},500)
}
1题的答案是5个5,于闭包无关,详情请看 this,2是1的变种,如下
//2题可转化为
for(var i=0; i < 5 ;i++){
(function(i){
setTimeout(function(){
console.log(i)
},500)
})(i)
}
let的原理是从当前开始到这段代码块的底部是一个匿名函数自调
由上可知 i 就是要保护的变量,匿名函数就是外层函数,定时器中的function
就是内层函数也就是闭包函数
循环每执行一次就会调用一次匿名函数,把当前的 i 的值保存在匿名函数的作用域对象中
每一个定时器里的函数都会连着一个外层的匿名函数的作用域对象,每个作用域对象中都会存的当前循环中 i 的值
所以输出是 0,1,2,3,4
//3
var funs = [];
for ( var i = 0 ; i < 3 ; i++ ){
funs[i] = ( function(i){
return function(){
console.log(i)
}
} )( i )
}
funs[0]();
funs[1]();
funs[2]();
第三题中循环体中的匿名函数自调就是外层函数,其中return出来的函数就是内层函数,i 就是要保护的局部变量
每次循环执行时,都执行一次匿名函数自调,往funs
数组中的 i 位置添加一个没有调用函数
这个函数连着匿名函数自调临时创建的函数作用域对象,每次传递进来的 i 就会保存在里面
调用funs
数组中的函数时,由于它自己的作用域中没有 i 就会到匿名函数自调临时创建的函数作用域对象中拿
所以输出是 0,1,2
//4
function fun(){
for ( var i=0 , arr=[] ; i<3 ; i++ ){
arr[i] = function(){
console.log(i)
}
}
return arr;
}
var funs = fun();
funs[0]();
funs[1]();
funs[2]();
第四题是第三题的一个变种,此题中外层函数是fun
,内层函数是循环体中的函数, i 是要保护的变量
此题中外层函数只调用了一次,所以也就仅仅只创建了一个外层函数作用域对象
其中每次赋值创建的内层函数其实都是在共用了这个外层函数作用域对象其中的 i
等到 i = 3 时循环结束,所以输出是3,3,3
//5
<body>
<ul>
<li>aaa</li>
<li>aaa</li>
<li>aaa</li>
</ul>
<script>
var lis = document.getElementsByTagName('li');
for (var i = 0; i < lis.length; i++) {
(function (i) {
lis[i].onclick = function () {
alert(i + 1);
}
})(i)
}
</script>
</body>
第五题是一组li
点击第几个就alert
几
这个题和第二个类似,匿名函数自调是外层函数,其中onclick
事件中绑定的函数是内层函数,i 是局部变量
循环执行了3次,外层的匿名函数就执行了3次,创建了3个匿名函数的作用域对象,每个作用域中都保存了本次循环中 i 的值
每次点击调用内层函数使用的就是之前 i 的值
//6
var name = "Window";
var object = {
name:"Object",
getNameFunc:function(){
return function(){
return this.name;
}
}
};
alert(object.getNameFunc()())
//7
var name = "The Window";
var object = {
name : "Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()());
第六题第七题也是一个类型,但是其中又掺杂了this的问题(this传送门)
第六题中getNameFunc
方法中很明显就是两层函数嵌套,其中最关键的是这个this指向的是谁
在getNameFunc
中外层函数中的this指向的是调用时.
前的对象object
,而这个方法抛出了一个函数
这个被抛出的函数自己作用域并没有this,就会去作用域链中找,上层的外层函数的作用域中也没有
就会到全局中找,全局中的this
指向的是window
,所以alert是window
第七题的外层函数中保存了调用时的this,被抛出的函数去上层的外层函数作用域中找到了这个this,
而这个this指向的是调用时.
前的对象object
,所以alert是object
闭包的应用场景
1.setTimeout()
window的setTimeout()方法用于指定毫秒数后调用函数或者计算表达式,但setTimeout()传递的第一个函数不能带参数。通过闭包就可以实现传参的效果。
function fun1(m){
return function(){
console.log(m);
}
}
var fun2 = fun1(123);
setTimeout(fun2,1000);//一秒后输出123
2.封装变量
function isFristLoad(){
var _list = [];
return function(id){
if(_list.indexOf(id) >= 0){
return false;
}else{
_list.push(id);
return true;
}
}
}
var firstLoad = isFristLoad();
firstLoad(10); //true
firstLoad(10); //false
firstLoad(20); //true