闭包是JavaScript很重要的知识点
闭包在JS 的世界中无处不在
掌握了它能够使我们对JavaScript理解的更加深入
闭包是基于词法作用域书写代码时所产生的必然结果
词法作用域大家可以理解为就是静态作用域
静态作用域意思就是书写代码时, 变量属于哪个作用域就已经定下来了
首先给出闭包的定义:
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数在当前词法作用域之外执行
不过这个定义不是很好理解
先来个简单的例子
function demo(){
var a = 123;
function foo(){
console.log(a);
}
return foo;
}
var fn = demo();
fn();// 123
首先我定义了一个函数demo
demo里面声明了一个变量a和一个函数foo
demo返回了foo函数
在demo函数外声明了一个变量fn得到demo函数的返回函数引用
然后执行fn得到了结果123
这就是一个闭包小实例
当demo函数执行完毕后, 浏览器通常会想销毁内部作用域(引擎的垃圾回收机制)
但是fn把foo函数的引用保存了下来
使得作用域没有释放,所以可以取得a变量
fn的这个引用就叫做闭包
无论用什么手段把内部的函数传递到所在词法作用域的外部, 它都会紧握原有的作用域不放
无论在何处执行这个函数都会使用闭包
再看一个定时器的例子
function wait(msg){
setTimeout(function timer(){
console.log(msg);
},1000)
}
wait('这就是闭包~');
setTimerout函数是一个定时器, 它的意思是1000ms之后执行timer函数
结果就是1s后浏览器输出了结果
能看出来这是一个闭包吗?
再看看闭包的定义
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数在当前词发作用域之外执行
timer函数记住了他所在wait作用域的闭包
1s后浏览器引擎调用了这个函数
这个计时器函数可不是在那个位置上等1000ms再往下执行, 它并不是阻塞的
感兴趣的可以看看 JavaScript单线程
而是1000ms后, timer在wait函数外部执行的
这个过程中词法作用域保持了完整
JavaScript中函数是第一类公民
如果你将函数到处传递, 就能够看到很多闭包的应用
只要使用了回调函数, 实际上就是使用了闭包
回调函数就是当满足了一定条件时调用的函数执行
比如把函数当作参数传递到另一个函数这种高阶函数的使用
再来看一个循环产生闭包的例子
function demo() {
var arr = [];
for(var i = 0; i < 10; i++) {
arr[i] = function () {
console.log(i);
};
}
return arr;
}
var retArr = demo();
for(var j = 0; j < retArr.length; j++) {
retArr[j]();
}
输出结果是10个10
我们本来想要输出0~9
可是结果却和我们预期的不同
下面我们从闭包的角度来分析一下代码
在for循环中, 给arr数组保存了10个函数引用
但要注意,此时函数中的i还不知道是什么, 函数只有在执行时才会去查找作用域链去获取i
这个匿名函数持有涵盖demo函数的作用域
所以拆解for循环应该是这样的
arr[0] = function(){
console.log(i);
};
arr[1] = function(){
console.log(i);
}
arr[2] = function(){
console.log(i);
}
//......
最后demo函数返回了一个数组包含了10个相同的函数引用
数组中是这样的
retArr =
[function(){console.log(i)},
function(){console.log(i)},
function(){console.log(i)},
......]
这个数组保存在了变量retArr中
遍历这个数组,执行数组中每一个函数元素
此时每一个函数都会作用域链上去找i是什么
由于JavaScript中的词法作用域是函数作用域
i是属于demo函数的变量
此时的i经历了10次迭代, 已经变成了10
所以每一个函数都拿到了10
所有函数都共享一个i的引用
于是结果返回了10个10;
(@﹏@)~
那我们怎样解决这个闭包问题呢
上面的问题就是所有函数共享一个闭包作用域
不知道大家还记不记得IIFE(立即执行函数)
IIFE也是函数
JavaScript中只有函数作用域(暂且不考虑with等等)
IIFE可以创建作用域
所以我们来试试这个
function demo() {
var arr = [];
for(var i = 0; i < 10; i++) {
(function(n){
arr[n] = function () {
console.log(n);
};
})(i);
}
return arr;
}
var retArr = demo();
for(var j = 0; j < retArr.length; j++) {
retArr[j]();
}
嗯..这就是我们想要的(^-^)V
可是它是怎么解决闭包问题的呢
可能大家已经看明白了
加了一个立即执行函数
数组中的10个函数元素就不再共享一个闭包作用域了
而是各用各的, 谁也不打扰谁
我暂且给这个立即执行函数起名为IIFE
数组中确实还是存了10个长的一模一样的函数
但是它们查找的东西不同
每一个函数去查找作用域时找到IIFE
因为IIFE中保存着它们要的变量n
并且各自的n都不同
这样返回的值就是正确的值啦
其实就是
加立即执行函数之前是1对10的关系, 10个元素找的是那1个i
加立即执行函数之后是1对10对10的关系, 10个元素找了10个n
只要你愿意, 立即执行函数参数n也可以写成同名的i
立即执行函数解决了我们闭包的缺陷...
如果我们在做项目时发现, 发现bug找不到
调试发现连续输出多个相同值
那问题大概出在闭包了
解决问题的办法就是立即执行函数
一定要去理解这句话
只要使用了回调函数, 实际上就是使用了闭包