《JavaScript 高级程序设计》中闭包的概念:
闭包, 其实是一种语言特性, 它是指的是程序设计语言中, 允许将函数看作对象, 然后能像在对象中的操作般在函数中定义实例 (局部) 变量, 而这些变量能在函数中保存到函数的实例对象销毁为止, 其它代码块能通过某种方式获取这些实例 (局部) 变量的值并进行应用扩展.
我们的理解:
其实闭包就是一个函数, 一个外部函数通过调用函数并 return 返回出内部函数, 这里的内部函数就是一个闭包; 此时在内部函数中是可以访问到外部函数的变量的;
要想理解闭包, 首先我们要了解栈堆内存和作用域链; 首先我们来讲解栈堆内存:
首先我们来看个 demo:
- var a=1;
- var obj={
- "name":"咸鱼"
- }
上面简单的两句代码, 其实就是在内存中做了两件事, 效果图如下:
image.PNG
在 JS 简单实现深浅拷贝 (https://www.cnblogs.com/dengyao-blogs/p/11466598.html) 一文中我们知道基本数据类型是存储在栈内存中的, 引用数据类型是存储在堆内存中的, 其实上面的两句代码在内存中就是做了两件事: 1. 首先在栈内存中开辟了一块空间用来存放 a 的变量和值; 2. 在堆内存中开辟了一块空间用来存储 obj 的值, 同时在将地址指向栈内存中的变量名 obj
如果我们在代码下面再加上一句 obj={"name":'张三"}, 这个时候我们之前存储 name 为咸鱼的值也就是 obj 原来的值会被 js 中的垃圾回收机制回收掉, 然后 obj 的值重新的指向 {name:"张三"} 这个值;
作用域链
再来看一下这个例子:
- var a = 1;
- function fn(){
- var b = 2;
- function fn1(){
- console.log(b);//2
- console.log(a);//1
- }
- fn1();
- }
- fn();
效果图如下:
image.PNG
1.var a=1; 这个时候我们是在全局执行环境的, 浏览器的全局环境就是 Windows 作用域, 我们的 Windows 作用域中有 a 和 fn;
2. 当我们往下走到 fn 的时候, 栈内存会开辟一块新的执行环境, 此时 fn 的执行环境中我们有 b 和 fn1;
3. 当我们接着往下走到 fn1 的时候, 这时栈内存同样会开辟一块新的执行环境, 此时 fn1 的执行环境中是没有任何变量数据的, 但是我们在 fn1 中输出 a,b, 我们都是可以读取到的; 这是因为程序在读取变量的时候是从内到外的开始读的, 是随着 fn1 开始往上一层一层的查找, 是这样的执行顺序(fn1 => fn => Windows), 如果找到 Windows 中还没有读取到变量, 这时程序才会报错;
当然在执行的过程中, 垃圾回收机制如果检测到程序执行完了是会进行垃圾回收的, 避免造成内存泄露等问题; 就是说我们的 fn1 里面执行完之后 fn1 的作用域就会被销毁, 接着程序执行 fn,fn 执行完之后 fn 就会被销毁; 往上执行到全局的时候, 整个程序就没有了 fn 的作用域和 fn1 的作用域, 只剩下浏览器的全局作用域 Windows, 这个时候 Windows 里只剩 a 和 fn;
了解了上面的作用域链和栈内存和堆内存的知识之后, 我们来开始讲解 JS 闭包:
- function outer() {
- var a = '123'
- return function add(){
- // 在这里因为作用域的关系, add 是能访问到 outer 的所有变量的, 但是 outer 是访问不到 add 的变量;
- // 所以思路一转, 把 add 的值作为结果 return 出来变通实现 outer 外部函数访问到了内部函数变量
- // add 就是一个闭包函数, 因为他能够访问到 outer 函数的作用域, add 中没有找到变量 a, 则会继续往上层作用域找
- console.log(a);
- }
- }
- var inner = outer() // 获得 add 闭包函数
- inner() //"123"
首先我们可以看到, 在全局作用域下我们是有一个 outer 函数的, outer 作用域里面有 a 和 add,add 作用域里面执行控制台输出 a 的变量, 此时这里的 add 函数就形成了一个闭包, 因为 add 函数里面需要访问到 outer 作用域下的 a 变量, 而他们不处在同一个作用域中, 所以两者相互牵引, 需要输出 a, 上面 outer 中的变量 a 就必须得在, 作用域链查找到 outer 的时候找到 a 了, 输出 a 的时候, 垃圾回收机制会认为 add 还没有执行完成, 因为此时的作用域链查找已经到了 outer 作用域下, 所以不会清理 a 的内存空间; 所以这就会带来一个问题: 如果我们多次的使用闭包, 则会给我们的程序带来内存占用过多, 导致性能问题;
函数内部能访问全局变量是 JavaScript 语言的特殊之处, 但是如果我们想达到函数外部能访问内部变量的时候, 我们就可以使用闭包, 这就是闭包给我们带来的便利;
闭包的优缺点:
优点:
1. 可以读取函数内部的变量;
2. 可以避免全局污染
缺点:
1. 闭包会导致变量不会被垃圾回收机制所清除, 会大量消耗内存;
2. 不恰当的使用闭包可能会造成内存泄漏的问题;
总结:
1. 作用域链查找变量的方式是一层一层的往上查找, 直到找到为止, 如果找到 Windows 全局作用域还未找到, 就报 undefined;
2. 嵌套函数中, 因为不在同一作用域, 正常情况下内外部函数是访问不到内部函数的, 但是通过闭包可以实现;
3. 尽可能少的使用闭包, 因为会造成内存消耗大以及有可能造成内存泄露(如果不需要的时候, 不要随便使用);
来源: http://www.jianshu.com/p/a9fa4b58c777