# 闭包
# 到底什么是闭包?
一句话来讲就是:让
函数
在当前词法作用域
之外的作用域
执行,就产生了闭包。
此时函数
可以记住并访问声明时
所在的词法作用域
。
# 如何产生闭包?
根据上述闭包的概念,要想产生闭包,我们只需要把
内部函数
传递到所在的词法作用域之外
就能制造闭包。
注:只要使用了回调函数
,实际上就在使用闭包
。
// 闭包1
function fn1() {
var num1 = 1;
function fn2() {
console.log(num1);
}
return fn2;
}
var fn2 = fn1(); // 此时就产生了闭包,因为 函数fn1 内部的 函数fn2 被传递到了外部,并赋值给了 变量fn2 ,此时 变量fn2 会持有 函数fn1 的词法作用域
fn2(); // 打印1
// 闭包2
var fn3;
function fn1() {
var num1 = 1;
function fn2() {
console.log(num1);
}
fn3 = fn2;
}
function fn4() {
fn3(); // 这里也会产生闭包
}
fn4(); // 打印1
// 闭包3 回调函数
function fn1() {
var num1 = 1;
function fn2() {
console.log(num1);
}
fn3(fn2); // 打印1
}
function fn3(cb) {
cb(); // 这里执行了回调,产生了闭包
}
// 闭包3 回调函数,更常见也更容易被忽视
var num1 = 1;
setTimeout(function fn1() {
console.log(num1); // 打印1
}, 100);
// 此时的fn1也会产生闭包
// 因为fn1声明在setTimeout所在的 词法作用域,但是执行是在 setTimeout 内部
// 此时的 setTimeout 如果在 全局作用域(window),fn1 将持有整个 全局作用域,如果不及时清除,很有可能会产生内存泄漏
# 面试中会碰到的闭包问题
也和JS的
事件循环
(Event Loop
)有关,后续会补充相关知识。
for (var i = 0; i < 6; i++) {
setTimeout(function fn1() {
console.log(i);
}, i * 1000);
}
// 结果:打印 5个6
/**
* 原因:
*
* 这就关系到了JS的事件循环,这里先简单知道这个概念:
*
* setTimeout是异步函数,JS会在所有的 同步代码 执行之后 再执行 异步函数。
*
* for循环执行完之后,i为6,setTimeout执行时,内部函数fn1获取到的i就为6,所以执行结果 为 5个6。
**/
如何解决呢?
// 方法1 创建函数作用域
for (var i = 0; i < 6; i++) {
(function(j) {
setTimeout(function fn1() {
console.log(j);
}, j * 1000);
})(i);
}
// 结果:打印 1 2 3 4 5
/**
* 原因:
*
* IIFE会创建 函数作用域,for循环5次,创建了5个函数作用域。
*
* 函数作用域 内的 变量 都是独立于自己当前的作用域的。
*
* 把i当作实参传递给IIFE,j就成为当前 函数作用域 的值。
**/
// 方法2 使用 let 创建类似块作用域
for (let i = 0; i < 6; i++) {
setTimeout(function fn1() {
console.log(i);
}, i * 1000);
}
// 结果:打印 1 2 3 4 5
/**
* 原因:
*
* 使用了let 块作用域 的特性。
*
* for循环头部的let有一个 特殊行为,每次迭代都会声明。
**/
# Tips
模块化的实现:
大家可以想象一下,自己平时使用的 import
/export
等方式,也是基于闭包的原理实现的。
创建一个封闭的作用域
,把需要的函数
export
出去,这些被export
的函数
在被调用时,就会持有声明所在的词法作用域
。
如果觉得内容对你有帮助,请点个赞和关注,你们的鼓励是我持续更新下去的动力,比心。
如需转载请注明出处,感恩。
更多内容可先关注GitHub (opens new window)