什么是闭包
MDN给出的定义是可以从内部函数访问外部函数的作用域的一种状态
闭包并不是稀奇玩意, 在JavaScript
中闭包无处不在, 并且闭包并不是一个语法, 而是基于词法-----根据源代码中声明变量的位置来确定该变量在何处可用-----作用域写代码时所产生的自然而然的结果, 因此在实际书写中也许没有刻意使用闭包, 但大概率会产生闭包, 比如:
function init() {
let name = "sifou"; // name 是一个被 init 创建的局部变量
function displayName() { // displayName() 是内部函数,一个闭包
alert(name); // 使用了父函数中声明的变量
}
displayName();
}
init(); // sifou
根据前面的定义, 看起来并不像是, 因为displayName
函数时嵌套在init
内的, 根据作用域链的查找规则, 可以使用上层作用域中的变量是正常的, 而这条查找规则就是产生闭包的最重要的原因. 修改一下上面代码:
function init() {
let name = "sifou"; // name 是一个被 init 创建的局部变量
function displayName() { // displayName() 是内部函数,一个闭包
alert(name); // 使用了父函数中声明的变量
}
return displayName
}
const outFn = init();
outFn() // sifou
由于函数可以作为值进行传递, 因此outFn
与displayName
实际上是通过不同的标识符调用了内部函数displayName
, 而且是在自己定义时的词法作用域外面执行的. 按理来说, 当init
执行结束后应该垃圾回收机制回收, 其整个内部作用域都应该被销毁. 而闭包阻止了这件事发生因为该作用域依然被使用, 被displayName
使用. displayName
依然持有该作用域的引用整个引用就是闭包. 理所当然的, 也就意味着无论以何种方式对函数类型的值进行传递, 当该函数在别处进行调用的时候就可以看到闭包. 本质上一般来说如果将函数作为值类型传递就会有闭包. 比如定时器, 事件监听器, Ajax请求等任务中, 只要使用回调函数实际上就是闭包, 正应了开头所说的在JavaScript
中闭包无处不在
一个经典的闭包面试题:
// 改造使其打印1,2,3,4,5
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
// 改造后
for (var i = 1; i <= 5; i++) {
(function (j) {
setTimeout(function timer() {
console.log(j)
}, j * 1000)
})(i)
}
改造后可以打印1,2,3,4,5的原因与使用let
有相似的原因----块级作用域, 在迭代内部使用IIFE会为每一个迭代都生成一个新的作用域, 使延迟函数的回调可以将新的作用域封闭在每个迭代的内部, 并且由于闭包的存在(timer
对形参j
的使用), IIFE执行后不会被回收, 所以每次的迭代中都有一个正确的变量值进行访问
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。