这次把 javascript 闭包给你讲的明明白白

目录

引入(闭包和块作用域)

理解闭包:

常见的闭包:

闭包的作用:

闭包的生命周期:

利用闭包的实际例子(返回价格的区间元素)

移动动画的抖动和加速(闭包应用,动画演示)

根据闭包进行传入字段排序(通用排序)

闭包内存泄漏的解决方法

闭包导致的this遗留问题(教你判断this指向口诀)

闭包的其他应用 : 定义JS模块


 

点赞再看,养成好习惯,总结不易,老铁多多支持~

 

引入(闭包和块作用域)

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <title>00_引入</title>
  </head>

  <body>

    <button>测试1</button>
    <button>测试2</button>
    <button>测试3</button>
    <!--
需求: 点击某个按钮, 提示"点击的是第n个按钮"
-->
    <script type="text/javascript">
      var btns = document.getElementsByTagName('button');
      //遍历加监听
      /*
      for (var i = 0,length=btns.length; i < length; i++) {
        var btn = btns[i]
        btn.onclick = function () {
          alert('第'+(i+1)+'个'); // 永远是第btns.length个,因为for循环已结束
        }
      }*/
      // 方法一,利用对象属性
      /*
      for (var i = 0,length=btns.length; i < length; i++) {
        var btn = btns[i]
        //将btn所对应的下标保存在btn上
        btn.index = i
        btn.onclick = function () {
          alert('第'+(this.index+1)+'个')
        }
      }*/

      // 方法二,利用闭包
      // for (var i = 0, length = btns.length; i < length; i++) {
      //   // (function (j) {
      //   //  var btn = btns[j]
      //   //  btn.onclick = function () {
      //   //    alert('第' + (j + 1) + '个')
      //   //  }
      //   // })(i);
      //   // 等同于上面一段
      //   (function () {
      //     var j = i; // 因为当前作用域是没有i的,i在作用域之外,这样onclick就持有了该作用域的引用,这个引用就叫做闭包
      //     var btn = btns[j];
      //     btn.onclick = function () {
      //       alert('第' + (j + 1) + '个');
      //     }
      //   })();
      // }

      // 或者利用ES6的let声明和块作用域结合起来
      // for循环头部的let声明会在每次循环迭代的过程中被声明
      for (let i = 0, length = btns.length; i < length; i++) {
        let btn = btns[i];
        btn.onclick = function () {
          alert('第' + (i + 1) + '个'); // 持有外部作用域的引用,形成闭包
        }
      }
      // 块作用域和闭包联手便可以天下无敌
    </script>
  </body>

</html>

运行效果:

理解闭包:

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <title>01_理解闭包</title>
  </head>

  <body>
    <!--
1. 如何产生闭包?
  * 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
2. 闭包到底是什么?
  * 使用chrome调试查看
  * 理解一: 闭包是嵌套的内部函数(绝大部分人)
  * 理解二: 包含被引用变量(函数)的对象(极少数人)
  * 注意: 闭包存在于嵌套的内部函数中
3. 产生闭包的条件?
  * 函数嵌套
  * 内部函数引用了外部函数的数据(变量/函数)
-->
    <script type="text/javascript">
      function fn1() {
        var a = 2;
        var b = 'abc';
        function fn2() { //执行函数定义就会产生闭包(不用调用内部函数)
          console.log(a);
        }
        // fn2();
      }
      fn1();

      function fun1() {
        var a = 3;
        var fun2 = function () {
          console.log(a);
        }
      }
      fun1();
    </script>
  </body>

</html>

 

常见的闭包:

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <title>02_常见的闭包</title>

  </head>

  <body>
    <!--
1. 将函数作为另一个函数的返回值
2. 将函数作为实参传递给另一个函数调用
-->
    <script type="text/javascript">
      // 1. 将函数作为另一个函数的返回值
      function fn1() {
        var a = 2
        function fn2() {
          a++
          console.log(a)
        }
        return fn2
      }
      var f = fn1()
      f() // 3
      f() // 4

      // 2. 将函数作为实参传递给另一个函数调用
      function showDelay(msg, time) {
        setTimeout(function () {
          alert(msg)
        }, time)
      }
      showDelay('atguigu', 2000)

    </script>
  </body>

</html>

 

闭包的作用:

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <title>03_闭包的作用</title>

  </head>

  <body>
    <!--
1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)

问题:
  1. 函数执行完后, 函数内部声明的局部变量是否还存在?  一般是不存在, 存在于闭中的变量才可能存在
  2. 在函数外部能直接访问函数内部的局部变量吗? 不能, 但我们可以通过闭包让外部操作它
-->
    <script type="text/javascript">
      function fn1() {
        var a = 2
        function fn2() {
          a++
          console.log(a)
          // return a
        }
        function fn3() {
          a--
          console.log(a)
        }
        return fn3
      }
      var f = fn1()
      f() // 1
      f() // 0
    </script>

  </body>

</html>

 

 

闭包的生命周期:

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <title>04_闭包的生命周期</title>

  </head>

  <body>
    <!--
1. 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
2. 死亡: 在嵌套的内部函数成为垃圾对象时
-->
    <script type="text/javascript">
      function fn1() {
        //此时闭包就已经产生了(函数提升, 内部函数对象已经创建了)
        var a = 2
        function fn2() {
          a++
          console.log(a)
        }
        return fn2
      }
      var f = fn1()
      f() // 3
      f() // 4
      f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)
    </script>
  </body>

</html>

利用闭包的实际例子(返回价格的区间元素)

let lessons = [
  {
    title: "媒体查询响应式布局",
    click: 89,
    price: 12
  },
  {
    title: "FLEX 弹性盒模型",
    click: 45,
    price: 120
  },
  {
    title: "GRID 栅格系统",
    click: 19,
    price: 67
  },
  {
    title: "盒子模型详解",
    click: 29,
    price: 300
  }
];
function between(a, b) {
  return function(v) {
	return v.price >= a && v.price <= b;
  }
}
console.table(lessons.filter(between(10, 100)));

执行结果

移动动画的抖动和加速(闭包应用,动画演示)

<!DOCTYPE html>
<html lang="en">
    <body>
        <style>
            button {
                position: absolute;
            }
        </style>
        <button>按钮</button>
    </body>
    <script>
        let btns = document.querySelectorAll("button");
        btns.forEach(function (item) {
            let bind = false;
            item.addEventListener("click", function () {
                // if (!bind) {
                let left = 1;
                bind = setInterval(function () {
                    item.style.left = left++ + "px";
                }, 100);
                // }
            });
        });
    </script>
</html>

运行结果

这居然?鬼畜了???

就是因为你的left写在了click回调函数里面。因为每点击一次就会创建一块function空间,里面left变量去定时改变style,每改变一次style.left就会导致一次回流从而再渲染一次。每次点击left初始值为1,上一次的已经为+了很多次,上上次的已经为+了非常多次。渲染的时候你就会看到一会1px一会很多px的鬼畜情况,也就是动画抖动(渲染一次抖动一次)。

那么可以把left变量提到click上面一行就解决了吧?

......   
            let bind = false;
            let left = 1;
            item.addEventListener("click", function () {
                // if (!bind) {
                bind = setInterval(function () {
                    item.style.left = left++ + "px";
                }, 100);
                // }
            });
......

 运行结果

这???居然加速了 ,越来越快了!!!因为每点击一次就会有一个定时器100ms轮询改变left变量,这个left变量对于click回调函数来说是的共有的一块作用域。所以越来越多的定时器不断的left++,你就看到了加速现象。

正确做法如下:

<!DOCTYPE html>
<html lang="en">
    <body>
        <style>
            button {
                position: absolute;
            }
        </style>
        <button>按钮</button>
    </body>
    <script>
        let btns = document.querySelectorAll("button");
        btns.forEach(function (item) {
            let bind = false;
            item.addEventListener("click", function () {
                if (!bind) {
                    let left = 1;
                    bind = setInterval(function () {
                        item.style.left = left++ + "px";
                    }, 100);
                }
            });
        });
    </script>
</html>

 

现象就正常了,没有抖动也没有加速。

 

根据闭包进行传入字段排序(通用排序)

    <script>
        let lessons = [
            {
                title: "媒体查询响应式布局",
                click: 89,
                price: 12
            },
            {
                title: "FLEX 弹性盒模型",
                click: 45,
                price: 120
            },
            {
                title: "GRID 栅格系统",
                click: 19,
                price: 67
            },
            {
                title: "盒子模型详解",
                click: 29,
                price: 300
            }
        ];
        function order(field, type = 'asc') { // 默认asc升序
            return (a, b) => {
                if (type == "asc") return a[field] > b[field] ? 1 : -1;
                return a[field] > b[field] ? -1 : 1;
            }
        }
        console.table(lessons.sort(order("price"))); // order("price", "desc")可以降序
    </script>

 

闭包内存泄漏的解决方法

闭包特性中上级作用域会为函数保存数据,从而造成的如下所示的内存泄漏问题

<!DOCTYPE html>
<html lang="en">
    <body>
        <div desc="zaixianxuexi">在线学习</div>
        <div desc="kaiyuanchanpin">开源产品</div>
    </body>
    <script>
        let divs = document.querySelectorAll("div");
        divs.forEach(function (item) {
            item.addEventListener("click", function () {
                console.log(item.getAttribute("desc"));
            });
        });
    </script>
</html>

下面通过清除不需要的数据解决内存泄漏问题

let divs = document.querySelectorAll("div");
divs.forEach(function(item) {
  let desc = item.getAttribute("desc");
  item.addEventListener("click", function() {
    console.log(desc);
  });
  item = null;
});

再给一个例子加深印象

    <script type="text/javascript">
      function fn1() {
        var arr = new Array[100000]
        function fn2() {
          console.log(arr.length)
        }
        return fn2
      }
      var f = fn1()
      f()

      f = null //让内部函数成为垃圾对象-->回收闭包
    </script>

 

闭包导致的this遗留问题(教你判断this指向口诀)

let hd = {
  user: "lcy",
  get: function() {
    console.log(this); // 这里的this是hd对象
    return function() { // 这里的this是window对象
      return this.user;
    };
  }
};
var a = hd.get(); // 执行get(),返回里面的function
// this 总是指向调用该函数的对象,即函数在搜索this时只会搜索到当前活动对象。

// 下面是函数因为是在全局环境下调用的,所以this指向window
console.log(a()); // this.user为undefined,因为this指向window

this 总是指向调用该函数的对象,即函数在搜索this时只会搜索到当前活动对象。所以这里get()里面的function的this是Window。

顺带总结一下this记忆方法

如果你定义的是对象,对象里面定义了函数,这个函数叫做方法,方法里面的this是指向当前对象,如果方法里面还有函数2,那么函数2的this指向Window,理由就是上面这个例子。

如果你定义的函数,而你执行的时候是new 函数,那么认为你创建了对象,this判断同上。

如果你定义的函数,执行的时候直接调用,比如function a(){...},调用为a(),那么a里面的this指向Window

上面犀利的总结一般人我不告诉他。

 

闭包的其他应用 : 定义JS模块

闭包的自定义js模块:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>05_闭包的应用_自定义JS模块</title>
</head>
<body>
<!--
闭包的应用2 : 定义JS模块
  * 具有特定功能的js文件
  * 将所有的数据和功能都封装在一个函数内部(私有的)
  * 只向外暴露一个包信n个方法的对象或函数
  * 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
-->
<script type="text/javascript" src="myModule.js"></script>
<script type="text/javascript">
  var module = myModule()
  module.doSomething()
  module.doOtherthing()
</script>
</body>
</html>

 

myModule.js

function myModule() {
  //私有数据
  var msg = 'My atguigu'
  //操作数据的函数
  function doSomething() {
    console.log('doSomething() ' + msg.toUpperCase())
  }
  function doOtherthing() {
    console.log('doOtherthing() ' + msg.toLowerCase())
  }

  //向外暴露对象(给外部使用的方法)
  return {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
}

 

闭包的自定义js模块2:

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <title>05_闭包的应用_自定义JS模块2</title>
  </head>

  <body>
    <!--
闭包的应用2 : 定义JS模块
  * 具有特定功能的js文件
  * 将所有的数据和功能都封装在一个函数内部(私有的)
  * 只向外暴露一个包信n个方法的对象或函数
  * 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
-->
    <script type="text/javascript" src="myModule2.js"></script>
    <script type="text/javascript">
      myModule2.doSomething()
      myModule2.doOtherthing()
    </script>
  </body>

</html>

myModule2.js

(function () {
  //私有数据
  var msg = 'My atguigu'
  //操作数据的函数
  function doSomething() {
    console.log('doSomething() '+msg.toUpperCase())
  }
  function doOtherthing () {
    console.log('doOtherthing() '+msg.toLowerCase())
  }

  //向外暴露对象(给外部使用的方法)
  window.myModule2 = {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
})()

这个应用还有新玩法,见这里: js除了立即执行函数,你还可以这么玩 (预计阅读 1 min)

 

 

关注、留言,我们一起学习。

 

===============Talk is cheap, show me the code================

©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页