闭包一、闭包是什么?将一个 词法作用域 中的 内部函数 作为一个 一级值类型 到处传递,就形成了闭包。怎么去理解呢?这里要敲黑板划重点了,上面的概念性文字介绍了三个点:词法作用域(函数)内部函数一级值类型传递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); },i1000)}666!好!输出了几个6,老铁有点懵逼,不知应该扎心还是双击666。为何?你大爷还是你大爷,即使你在每次迭代都定义了函数,但是都在共享全局作用域中,i 还是这个 i 那要怎么解决?这时候在每个迭代的时候加上一个闭包作用域,并且你得把这个 i 大爷放进作用域中。//放法可以是传参①,可以是赋值②for(var i = 1; i <= 5; i++) { //这里先搞一个闭包作用域,派出我们的 IIFE (function(j) { // —① setTimeout(function timer() { console.log(j); },j1000) })(i); // —① (function() { var j = i; // —② setTimeout(function timer() { console.log(j); },j1000) })(); }上面这个是用了闭包作用域,每次迭代都生成一个新的作用域,来封闭内部变量。说到这里,前面提到的 let 应该还有人记得,let 干嘛用的,不就是劫持变量形成块作用域吗? 放在这里不是恰到好处? 来一发。for(let i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i) },i1000)}直接在定义 i 大爷的地方就 “绑架” 了他。或者,你也可以麻烦一点,先让他上迭代车,上车之后再 let 定义一个变量把 i 大爷赋给他,两种都行,简单点好。三、总结一下闭包应用定时器、事件监听器、Ajax请求、跨窗口通信、Web Workers 或者其他的异步(或同步)任务中(balabala~~~~),只要使用了回调函数,就是在使用闭包。还有一处重要的 模块。模块的两个重要特征:有外部包装函数(创建内部作用域)且需要被调用。外部包装函数返回值至少引用一个内部函数(创建包装函数内部作用域闭包)。