JS闭包| 青训营笔记

75 阅读2分钟

关于JS闭包的学习内容如下

闭包

闭包:在一个作用域内使用函数等手段,在当前作用域内再生成一个局部作用域的过程就叫做闭包的过程。用来打包的函数就叫做闭包函数。

广义上讲:所有的函数都是闭包函数。

闭包一般用来使得全局可以操作局部变量:

   <div>
     <span id="btn">赞</span>
     <span id="count">0</span>
   </div>
   <script>
     const btn = document.getElementById('btn');
     const count = document.getElementById('count');
     const a = fn();
 ​
     btn.onclick = a.subtract;
 ​
     function fn() {
       let n = 0;
       return {
         add: function () {
           n++;
           count.innerText = n;
         },
         subtract: function () {
           n--;
           count.innerText = n;
         }
       }
     }
   </script>

上例中,函数fn返回一个对象,其中包含两个函数,这两个函数的作用域链都经过函数fn,也就是都可以使用函数fn内的局部变量。

在全局,我们通过闭包函数可以间接访问或操作fn内的局部变量。

好处:

  1. 封装,将于某个效果相关的变量、函数、逻辑封装到一个函数中。
  2. 保持数据持久化。
  3. 形成单向数据流。

在写大型程序的时候,最忌讳的是一个变量被多个地方改变。

在写大型程序的时候,要求 单向数据流 。要改变变量的值,只能有一条路走。

闭包就能够实现单向数据流,在上例中,想要改变fn内的变量n,只能通过add或subtract这两个闭包函数来操作。

坏处:

正常情况下,函数执行完成后,函数内的所有资源都会被自动销毁,但是因为闭包的存在,函数执行完成后不会被销毁,会驻留内存。

闭包不建议使用,滥用闭包有可能会造成内存泄漏(内存溢出)。

闭包的应用

如果我们需要点击一个元素,得到它在同胞元素中的索引位置时,传统的写法是:

     const arrLi = document.querySelectorAll('li');
 ​
     for (let i = 0; i < arrLi.length; i++) {
       arrLi[i].onclick = li_click;
     }
     function li_click(e) {
       for (let i = 0; i < arrLi.length; i++) {
         if (arrLi[i] == e.target) {
           console.log(i);
         }
       }
     }

坏处是,每次点击时,都需要再次遍历所有的li。

改为闭包的形式:

     const arrLi = document.querySelectorAll('li');
     for (let i = 0; i < arrLi.length; i++) {
       arrLi[i].onclick = function () {
         console.log(i);
       };
     }

for循环执行多少次,就会形成多少个独立的执行上下文,每个执行上下文的变量i的值都不一样。

事件的处理函数就是闭包函数,当事件被触发时,处理函数的作用域链包含当前for循环生成的执行上下文,所以,可以取得当前for循环执行上下文中的i的值。

为了保持for循环内部的代码数量,可以采用如下的方式改进:

     const arrLi = document.querySelectorAll('li');
     for (let i = 0; i < arrLi.length; i++) {
       fn(i);
     }
 ​
     function fn(i) {
       arrLi[i].onclick = function () {
         console.log(i);
       };
     }