在阅读本篇文章之前可以先参考我的 JavaScript: 作用域与作用域链及 JavaScript 预编译的过程可以更好的理解
闭包在红宝书中的解释就是: 有权访问另一个函数作用域中的变量的函数.
在外层函数中, 嵌套一个内层函数, 那么这个内层函数可以向上访问到外层函数中的变量.
既然内层函数可以访问到外层函数的变量, 那如果把内层函数 return 出来会怎样?
- function outer () {
- var thing = '吃早餐';
- function inner () {
- console.log(thing);
- }
- return inner;
- }
- var foo = outer();
- foo(); // 吃早餐
函数执行完后, 函数作用域的变量就会被垃圾回收. 而这段代码看出当返回了一个访问了外部函数变量的内部函数, 最后外部函数的变量得以保存.
这种当变量存在的函数已经执行结束, 但扔可以再次被访问到的方式就是 "闭包".
1. 闭包
1. 闭包在红宝书中的解释就是: 有权访问另一个函数作用域中的变量的函数.
2. 写法:
- <script type="text/javascript">
- function fun1(){
- var a = 100;
- function fun2(){
- a++;
- console.log(a);
- }
- return fun2;
- }
- var fun = fun1();
- fun()
- fun()
- </script>
3. 效果如下:
4. 分析:
执行代码
- GO{
- fun:underfined
- fun1:function fun1()
- {
- var a = 100;
- function fun2()
- {
- a++;
- console.log(a);
- }
- return fun2;
- }
- }
然后第十一行开始这里, 就是 fun1 函数执行, 然后把 fun1 的 return 赋值给 fun, 这里比较复杂, 我们分开来看,
这里 fun1 函数执行, 产生 AO{
- a:100
- fun2:function fun2(){
- a++;
- console.log(a);
- }
- }
此刻 fun1 的作用域链为 第 0 位 AO
第 1 位 GO
此刻 fun2 的作用域链为 第 0 位 fun1 的 AO
第 1 位 GO
解释一下, fun2 只是声明了, 并没有产生调用, 所以没有产生自己的 AO,
正常的, 我们到第 7 行代码我们就结束了, 但是这个时候来了一个 return fun2, 把 fun2 这个函数体抛给了全局变量 fun, 好了, fun1 函数执行完毕, 消除自己的 AO,
此刻 fun2 的作用域链为 第 0 位 fun1 的 AO
第 1 位 GO
第十二行就是 fun 执行, 然后, 它本身是没有 a 的, 但是它可以用 fun1 的 AO, 然后加, 然后打印,
因为 fun 中的 fun1 的 AO 本来是应该在 fun1 销毁时, 去掉, 但是被抛给 fun, 所以现在 fun1 的 AO 没办法销毁, 所以现在 a 变量相当于一个只能被 fun 访问的全局变量.
所以第十三行再调用一次 fun 函数, a 被打印的值为 102.
2. 闭包之深入理解
举例 1:
- <script type="text/javascript">
- function fun1(){
- var a = 100;
- function fun2(){
- a ++;
- console.log(a);
- }
- return fun2;
- }
- var fn1 = fun1(); // 生成自己的 AO, 上面有 a
- var fn2 = fun1();
- fn1()//101
- fn1()//102
- fn2()//101
- </script>
fn1 和 fn2 互不干涉, 因为 fun1 函数调用了两次, 所以两次的 AO 是不一样的.
举例 2:
- <script type="text/javascript">
- function fun(){
- var num = 0;
- function jia(){
- num++;
- console.log(num);
- }
- function jian(){
- num--;
- console.log(num)
- }
- return [jia,jian];
- }
- var fn = fun();
- var jia = fn[0];
- var jian = fn[1];
- jia()//1
- jian()//0
- </script>
jia 和 jian 共用一个 fun 的 AO, 一动全都动, 十二行返回了一个数组,
举例 3:
- <script type="text/javascript">
- function fun(){
- var num = 0;
- function jia(){
- num++;
- console.log(num);
- }
- function jian(){
- num--;
- console.log(num)
- }
- return [jia,jian];
- }
- var jia = fun()[0];
- var jian = fun()[1];
- jia()//1
- jian()//-1
- </script>
这里有一个坑, jia = fun()[0];jian = fun()[1];fun 函数执行了两遍, 所以两次的 AO 不一样, 所以 jia 和 jian 操作的对象不一样.
3. 闭包好处与坏处
好处:
1保护函数内的变量安全 , 实现封装, 防止变量流入其他环境发生命名冲突
2在内存中维持一个变量, 可以做缓存 (但使用多了同时也是一项缺点, 消耗内存)
3匿名自执行函数可以减少内存消耗
坏处:
1其中一点上面已经有体现了, 就是被引用的私有变量不能被销毁, 增大了内存消耗, 造成内存泄漏, 解决方法是可以在使用完变量后手动为它赋值为 null;
2其次由于闭包涉及跨域访问, 所以会导致性能损失, 我们可以通过把跨作用域变量存储在局部变量中, 然后直接访问局部变量, 来减轻对执行速度的影响
4. 闭包解决的问题
- <!DOCTYPE html>
- <HTML>
- <head>
- <meta charset="UTF-8">
- <title>
- </title>
- </head>
- <body>
- <ul>
- <li>
- 0
- </li>
- <li>
- 1
- </li>
- <li>
- 2
- </li>
- <li>
- 3
- </li>
- <li>
- 4
- </li>
- <li>
- 5
- </li>
- <li>
- 6
- </li>
- <li>
- 7
- </li>
- <li>
- 8
- </li>
- <li>
- 9
- </li>
- </ul>
- <script type="text/javascript">
- var lis = document.getElementsByTagName("li");
- for (var i = 0; i < lis.length; i++) {
- lis[i].onclick = function() {
- console.log(i)
- }
- }
- </script>
- </body>
- </HTML>
不管点击哪个都是 10, 那是因为点击事件是我们点击才触发的函数, 等到触发的时候, i 早就变成 10 跳出循环了,, 这个时候我们就需要立即执行函数, 创造了十个不同的作用域
解决方案:
- <!DOCTYPE HTML>
- <HTML>
- <head>
- <meta charset="UTF-8">
- <title>
- </title>
- </head>
- <body>
- <ul>
- <li>
- 0
- </li>
- <li>
- 1
- </li>
- <li>
- 2
- </li>
- <li>
- 3
- </li>
- <li>
- 4
- </li>
- <li>
- 5
- </li>
- <li>
- 6
- </li>
- <li>
- 7
- </li>
- <li>
- 8
- </li>
- <li>
- 9
- </li>
- </ul>
- <script type="text/javascript">
- var lis = document.getElementsByTagName("li");
- for (var i = 0; i < lis.length; i++) {
- // lis[i].onclick = function(){
- // console.log(i)
- // }
- (function(i) {
- lis[i].onclick = function() {
- console.log(i)
- }
- })(i)
- }
- </script>
- </body>
- </HTML>
来源: https://www.cnblogs.com/xuxiaoyu/p/10141392.html