闭包 是函数执行的一种机制

函数执行会产生一个全新的私有上下文

闭包机制:

我们函数执行产生的两大机制“保存/保护”,称之为闭包机制!!

  • 【保护】:保护里面的私有变量不受外界的干扰. 防止全局变量的污染
  • 【保存】:一但这个上下文形成+不销毁的作用域 不被释放,里面的私有变量和值,就保存起来了,可以供其“下级”上下文中调取使用

    缺点

    • 因为闭包会产生不销毁的上下文,这样导致栈/堆内存消耗过大,有时候也会导致内存泄漏等,影响页面的运行性能,所以在真实项目中,要合理应用闭包(不要滥用)

1、闭包在实战中的应用

【1】闭包之私有变量的保护应用

  • -1).团队协作开发中;
    • A/B共同开发一个页面,最后要把代码合并在一起,为了防止全局变量的冲突污染,我们建议每个开发者,都把自己的代码放置到一个闭包中(自执行函数执行即可,这样就是私有的上下文)保护起来
      1. // A的代码
      2. (function anonymous() {
      3. /* 自执行函数执行,会形成一个私有的上下文,在这里声明+定义的变量或者函数都是私有的 */
      4. var x = 100,
      5. y = 200;
      6. function func() {
      7. // ...
      8. }
      9. })();
      10. // B的代码
      11. ~ function anonymous() {
      12. // console.log(anonymous); //=>函数本身
      13. //=>匿名函数设置的函数名只能在函数里面应用,函数外面是无法访问的
      14. var x = 200,
      15. n = 0;
      16. function func() {
      17. // ...
      18. }
      19. function handled() {
      20. // ...
      21. }
      22. }();
      23. // console.log(anonymous); //=>Uncaught ReferenceError: anonymous is not defined

【jquery】 通过window添加属性暴漏到全局

  1. (function(){
  2. function jquery(){
  3. }
  4. //把jquery 这个方法通过window添加属性暴漏到全局
  5. window.jquery=window.$=jquery;
  6. })()
  7. 在使用的时候: jquery() 或者$()

【zepto】把自执行函数的通过return把返回结果在外面用一个变量进行接收

  1. var zepto=(function(){
  2. return {
  3. fn:function(){},
  4. .....
  5. }
  6. })()
  7. // 在使用的时候:zepto.fn

【2】私有变量之保存机制-选项卡案例再忆

【选项卡案例原版】

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  7. <title>Document</title>
  8. <style>
  9. * {
  10. margin: 0;
  11. padding: 0;
  12. }
  13. ul,
  14. ol {
  15. list-style: none;
  16. }
  17. .main {
  18. width: 500px;
  19. margin: 0 auto;
  20. }
  21. ul>li {
  22. width: 100px;
  23. height: 40px;
  24. line-height: 40px;
  25. text-align: center;
  26. border: 1px solid #333;
  27. margin-right: 10px;
  28. display: inline-block;
  29. position: relative;
  30. top: 1px;
  31. }
  32. .main>div {
  33. height: 300px;
  34. line-height: 300px;
  35. border: 1px solid #333;
  36. text-align: center;
  37. display: none;
  38. }
  39. .main li.current {
  40. background: darkcyan;
  41. border-bottom-color: darkcyan;
  42. }
  43. .main div.current {
  44. background: darkcyan;
  45. display: block;
  46. }
  47. </style>
  48. </head>
  49. <body>
  50. <div class="main" id="main">
  51. <ul>
  52. <li class="current">音乐</li>
  53. <li>电视</li>
  54. <li>综艺</li>
  55. </ul>
  56. <div class="current">音乐内容</div>
  57. <div>电视内容</div>
  58. <div>综艺内容</div>
  59. </div>
  60. </body>
  61. </html>
  62. <script>
  63. var main = document.getElementById("main");
  64. var lis = main.getElementsByTagName("li");
  65. var divs = main.getElementsByTagName("div");
  66. for (var i = 0; i < lis.length; i++) {
  67. lis[i].index = i;
  68. lis[i].onclick = function () {
  69. var index = this.index;
  70. change(index);
  71. }
  72. }
  73. function change(index) {
  74. for (var i = 0; i < lis.length; i++) {
  75. lis[i].className = "";
  76. divs[i].className = "";
  77. }
  78. lis[index].className = "current";
  79. divs[index].className = "current";
  80. }
  81. </script>

2、回忆当初里面的i 为啥是3

1、【作用域方式去思考】

当我们触发点击事件的时候,这个函数执行,形成私有上下文, 在这个私有上下文里面,并没有私有变量i,所以就会向上级上下文进行查找,此时上级作用文就是全局上下文里面的i,当我们发生点击事件的时候,此时for 循环早已完成,i早就是3

上下文:

  • window 全局上下文 EC(G)
  • 函数执行形成私有上下文
 for (var i = 0; i < lis.length; i++) {
        lis[i].onclick = function () {
           // 这里的i为啥会变成3?当我们触发点击事件的时候,这个函数执行,形成私有上下文,
           // 在这个私有上下文里面,并没有私有变量i,所以就会向上级上下文进行查找,此时上级上下文就是全局
           // 而全局作用上下里面的i,当我们发生点击时间的时候,此时for 循环早已完成,i早就是3
            change(i);
        }
    }

2、【同步异步事件去思考】

【同步事件】:当一件事件做完之后,再继续下一件事情

【异步事件】:当一件事件还没有做完,不再等待,直接去做下一个事件。所有的事件都是异步编程。
【举列子】:比如你想吃着泡面去刷剧。你先打开开关去烧水,这个时候我就站在那等,直到水烧好了,把面泡好了,我再去看电视。这个是同步事件 。 我把烧水的开关打开,我就去刷剧了,一边刷剧,一边等着烧水,水烧好了,我再去关掉…. 这个就是异步事件

 for (var i = 0; i < lis.length; i++) {
        lis[i].onclick = function () {
            change(i);
        }
    }

for循环是同步事件,执行完i=3;点击事件是异步事件,当我们点击页面上的按钮的时候,这个for循环早已经执行完了。

lis[0].onclick = function () {
            change(i);
}

lis[1].onclick = function () {
            change(i);
}
lis[2].onclick = function () {
            change(i);
}

解决方法:参照选项卡文档中选项卡解决方案

自定义属性,闭包 let 等

练习题

1)

let x = 5;
const fn = function fn(x) {
    return function (y) {
        console.log(y + (++x));
    }
};
let f = fn(6);
f(7);
fn(8)(9);
f(10);
console.log(x);

1-2.png

浏览器的垃圾回收机制 GC 【“内存释放机制”】

@1堆内存的释放机制

【谷歌】标记清除

  - 如果当前堆内存的16进制地址,被其它事物所引用,则堆内存不能释放掉【强引用】
  -  如果没有东西占用这个堆内存,浏览器在空闲的时候,会把这些未被引用的堆内存“回收/释放”掉
//此时不能被释放 因为被obj占用
let obj={
    name:'zhufeng'
}
//obj=null;//让obj不指向对象的堆内存;这样上述对象就可以被释放掉了【手动释放堆内存的方法:赋值为null】
       引用计数<br />                内存泄漏 =》扩展

@2栈内存(执行上下文)释放机制

  - 全局上下文是加载页面的时候产生的,也只有关闭页面的时候才会释放
     - 刷新页面 会释放的全局上下文 重新产生新的全局上下文
  - 私有上下文(无论是函数的私有上下文还是块级上下文)
     - 一般情况下 执行完都会出栈释放掉,以此来优化栈内存
     - 特殊情况:如果当前上下文中创建的某个对象(一般指一个对象{堆内存})被上下文以外的其它事物占用了,那么不仅创建的这个东西不能被释放和其有关联的这个私有上下文也不能被释放!!
        - 这么做会消耗内存【慎用】
        - 因为不被释放,所以这个私有上下文中的东西都被保留下来了,以后可以拿来用

思考

下面代码是否可以,每隔1000MS依次输出 0 1 2 3 4 5 ?
如果不可以,说明为啥?
以及如何解决?

// 不行:基于var在循环中声明的变量“i”是全局变量
//   第一轮循环 全局i=0 设置第一个定时器{1000} 给定时器设置的回调函数并没有执行,等待1秒后执行,循环继续
//   第二轮循环 全局i=1 设置第二个定时器{2000}
//   ...
//   第五轮循环 全局i=4 设置第五个定时器{5000} i++,让全局的i=5,循环结束
// ->每一个定时器设置的回调函数,只有等到循环结束后,到了等待的时间,才会触发执行
for (var i = 0; i < 5; i++) {
    setTimeout(function () {
        /!*
         * EC(AN)
         *   
         * 作用域链:<EC(AN),EC(G)>
         * 形参赋值:--
         * 变量提升:--   
         *!/
        console.log(i); //i不是自己私有的,是全局的,但是此时全局的i已经是循环结束后的5了
    }, (i + 1) * 1000); 
}

解决方案如下

利用闭包机制解决 用了保存的应用

自执行函数

循环结束:设置了五个闭包,每一个闭包中都有一个自己的私有变量“i”,存储的值分别是0~4;全局i是5;

for (var i = 0; i < 5; i++) {
            //手动设置个闭包 
            (function (i) {
                setTimeout(function () {
                    console.log(i); 
                }, (i + 1) * 1000);
            })(i)
 }

大函数返回小函数手动生成闭包

 var fn=function(i){
            return function () {
             console.log(i)
        }}
 for (var i = 0; i < 5; i++) {
        setTimeout(fn(i), (i + 1) * 1000);
  }

使用forEach

// new Array(5).fill(null) 创建一个长度为5的数组,每一项填充null,把其变为密集数组,这样可以forEach
//   “_”就是形参占位,原本应该有个形参变量,但是我不想用,所以我占个位即可
new Array(5).fill(null).forEach(function (_, index) {
    // 第一轮  EC(AN1) index=0  设置定时器{1000}  闭包
    // 第二轮  EC(AN2) index=1  设置定时器{2000}  闭包
    // ...
    setTimeout(function () {
        console.log(index);
    }, (index + 1) * 1000);
});

真实项目使用let

for (let i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i);
    }, (i + 1) * 1000);
}

终极方案:基于定时器传参「核心:闭包」

关于定时器的详细参照 https://www.yuque.com/go/doc/44294922

for (var i = 0; i < 5; i++) {
   setTimeout(function (i) {
        console.log(i);
   }, 1000, i);
}