闭包
一、闭包是什么?
将一个 词法作用域 中的 内部函数 作为一个 一级值类型 到处传递,就形成了闭包。
怎么去理解呢?这里要敲黑板划重点了,上面的概念性文字介绍了三个点:
- 词法作用域(函数)
- 内部函数
- 一级值类型传递
1、先说词法作用域
形成一个作用域最常见的就是函数了,函数内部会形成一个内部作用域,然后还有 let 、const 以及像 try/catch 结构中的 catch 分句形成的块作用域。
let 就是为其声明的变量隐式劫持了所在的块作用域,这个在后面讲 let 和闭包的时候会详细说明 let 和闭包结合的用法。
通过了解可以知道,这里的作用域其实就是函数的内部作用域。
2、内部函数
内部函数不用介绍了吧,在词法作用域中定义的函数,传递后具有涵盖自身所在作用域的闭包。
3、一级值类型传递
值类型传递方式有很多种啊,函数里面的一级值传递无非就是:返回值( return )、赋值( 赋值给外部变量 )、参数传递( 作为参数传递给外部函数 )。
现在可以画一个基本的闭包出来了:
//三种传递方法①②③分开看,你可以的。
var fn; //定义全局变量,用于内部赋值 ---②
function foo() {
var a = 2;
function bar() {
console.log(a);
};
return bar; //返回值 ---①
fn = bar; //赋值 ---②
baz(bar); //参数传递 ---③
};
//定义外部函数,用于使用内部分配给全局变量的函数 ---②
function cat() {
fn();
};
//定义外部函数,用于内部参数传递 ---③
function baz(func) {
func();
};
foo(); //2 ---①
cat(); //2 ---②
baz(); //2 ---③
再来一例:
function wait(message) {
setTimeout(function timer(){
console.log(message);
},1000);
}
wait("Hi Baby");
解析一下,按照我们前面的思路可以贯穿下来:
首先 wait(..) 里面的作用域,作用域内部的 timer(..)函数,再将内部函数 timer(..) 传递给内置工具函数 setTimeout(..),setTimeout(...)有参数引用( 也就是我们传递的 timer(...) ),然后调用它。
整个过程行云流水,然后词法作用域在这个过程中保持完璧之身。OK!
二、循环中的闭包
说到循环闭包就要掏出大家耳熟能详的栗子了。
for(var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
},i*1000)
}
666!好!输出了几个6,老铁有点懵逼,不知应该扎心还是双击666。
为何?
你大爷还是你大爷,即使你在每次迭代都定义了函数,但是都在共享全局作用域中,i 还是这个 i
那要怎么解决?
这时候在每个迭代的时候加上一个闭包作用域,并且你得把这个 i 大爷放进作用域中。
//放法可以是传参①,可以是赋值②
for(var i = 1; i <= 5; i++) {
//这里先搞一个闭包作用域,派出我们的 IIFE
(function(j) { // ---①
setTimeout(function timer() {
console.log(j);
},j*1000)
})(i); // ---①
(function() {
var j = i; // ---②
setTimeout(function timer() {
console.log(j);
},j*1000)
})();
}
上面这个是用了闭包作用域,每次迭代都生成一个新的作用域,来封闭内部变量。
说到这里,前面提到的 let 应该还有人记得,let 干嘛用的,不就是劫持变量形成块作用域吗? 放在这里不是恰到好处? 来一发。
for(let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
},i*1000)
}
直接在定义 i 大爷的地方就 "绑架" 了他。
或者,你也可以麻烦一点,先让他上迭代车,上车之后再 let 定义一个变量把 i 大爷赋给他,两种都行,简单点好。
三、总结一下闭包应用
定时器、事件监听器、Ajax请求、跨窗口通信、Web Workers 或者其他的异步(或同步)任务中(balabala~~~~),只要使用了回调函数,就是在使用闭包。
还有一处重要的 模块。
模块的两个重要特征:
- 有外部包装函数(创建内部作用域)且需要被调用。
- 外部包装函数返回值至少引用一个内部函数(创建包装函数内部作用域闭包)。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。