让你分分钟理解 JavaScript 闭包
在接触一个新技术的时候我首先会做的一件事就是找它的 demo。对于我们来说看代码比自然语言更能理解一个事物的本质。其实闭包无处不在比如jQuery、zepto的核心代码都包含在一个大的闭包中所以下面我先写一个最简单最原始的闭包以便让你在大脑里产生闭包的画面12345678function A(){function B(){console.log(Hello Closure!);}returnB;}varC A();C();// Hello Closure!这是最简单的闭包。有了初步认识后我们简单分析一下它和普通函数有什么不同上面代码翻译成自然语言如下定义普通函数 A在 A 中定义普通函数 B在 A 中返回 B执行 A并把 A 的返回结果赋值给变量 C执行 C把这5步操作总结成一句话就是函数A的内部函数B被函数A外的一个变量 c 引用。把这句话再加工一下就变成了闭包的定义当一个内部函数被其外部函数之外的变量引用时就形成了一个闭包。因此当你执行上述5步操作时就已经定义了一个闭包这就是闭包。闭包的用途在了解闭包的作用之前我们先了解一下 Javascript 中的 GC 机制:在 Javascript 中如果一个对象不再被引用那么这个对象就会被 GC 回收否则这个对象一直会保存在内存中。在上述例子中B 定义在 A 中因此 B 依赖于 A ,而外部变量 C 又引用了 B , 所以A间接的被 C 引用。也就是说A 不会被 GC 回收会一直保存在内存中。为了证明我们的推理上面的例子稍作改进123456789101112function A() {varcount 0;function B() {count ;console.log(count);}returnB;}varC A();C();// 1C();// 2C();// 3count 是函数A 中的一个变量它的值在函数B 中被改变函数 B 每执行一次count 的值就在原来的基础上累加 1 。因此函数A中的 count 变量会一直保存在内存中。当我们需要在模块中定义一些变量并希望这些变量一直保存在内存中但又不会 “污染” 全局的变量时就可以用闭包来定义这个模块。闭包的高级写法上面的写法其实是最原始的写法而在实际应用中会将闭包和匿名函数联系在一起使用。下面就是一个闭包常用的写法123456789101112131415(function (document) {varviewport;varobj {init: function(id) {viewport document.querySelector(# id);},addChild: function(child) {viewport.appendChild(child);},removeChild: function(child) {viewport.removeChild(child);}}window.jView obj;})(document);这个组件的作用是初始化一个容器然后可以给这个容器添加子容器也可以移除一个容器。功能很简单但这里涉及到了另外一个概念立即执行函数。 简单了解一下就行需要重点理解的是这种写法是如何实现闭包功能的。可以将上面的代码拆分成两部分(function(){})和()。第1个()是一个表达式而这个表达式本身是一个匿名函数所以在这个表达式后面加()就表示执行这个匿名函数。因此这段代码执行执行过程可以分解如下12345678910111213141516varf function(document) {varviewport;varobj {init: function(id) {viewport document.querySelector(# id);},addChild: function(child) {viewport.appendChild(child);},removeChild: function(child) {viewport.removeChild(child);}}window.jView obj;};f(document);在这段代码中似乎看到了闭包的影子但 f 中没有任何返回值似乎不具备闭包的条件注意这句代码1window.jView obj;obj 是在函数 f 中定义的一个对象这个对象中定义了一系列方法 执行window.jView obj 就是在 window 全局对象定义了一个变量 jView并将这个变量指向 obj 对象即全局变量 jView 引用了 obj . 而 obj 对象中的函数又引用了函数 f 中的变量 viewport ,因此函数 f 中的 viewport 不会被 GC 回收viewport 会一直保存到内存中所以这种写法满足了闭包的条件。总结