耀极客论坛

 找回密码
 立即注册
查看: 551|回复: 0

JavaScript高级之闭包详解

[复制链接]

336

主题

318

帖子

22万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
220553
发表于 2022-5-9 00:56:17 | 显示全部楼层 |阅读模式
  这篇文章主要为大家介绍了JavaScript闭包,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助

1. 闭包的概念
  来看一般函数的执行和启发:
  1.         function stop() {
  2.             var num = 0;
  3.             console.log(num);
  4.         }
  5.         stop(); // 打印出来num是0
  6.         console.log(num); // 报错 函数未定义
复制代码
  1. 此时,函数的外部无法访问函数内部的变量
  2. 函数内部定义的变量不会一直存在,随着函数的运行结束而消失
  闭包的概念:
  1. 是一个函数,这个函数有权访问另一个作用域中的变量。
  2. 另一种说法,当内部函数的生命周期大于外部函数的声明周期,而内部函数以某一种方式被外部作用域访问时,闭包就产生了。
  来看如下闭包的代码和解释:
  1.         function fn() {
  2.             var num = 10;
  3.             // function fun() {
  4.             //     console.log(num);
  5.             // }
  6.             // return fun;
  7.             return function () {
  8.                 console.log(num); // 10
  9.             }
  10.         }
  11.         var f = fn();
  12.         f();
复制代码
  我们可以拆解为几个部分:
  1. fn函数里面有内部的返回值且就是一个函数。
  2. return的这个函数内部打印了num变量。为什么能够打印num变量,原因在于作用域链的访问机制,下面会补充作用域和作用域链的知识点。
  3. 我们在外部用f变量接受了fn(),也就是接受了fn的返回值【内部函数】
  4. 紧接着调用f,也就是调用了fn里面的内部函数。最终能够打印10

知识点的补充:
  1. 作用域:
  变量在某个范围内起作用,超出了这个范围,就不起作用。这个范围就是作用域。作用域在函数的定义时就产生,而不是函数调用时产生的。
  2. 作用域链:
  一句话概括:根据【内部函数可以访问外部函数变量】,采用就近原则一层一层向上查找变量,这个机制就叫作作用域链。
  函数A包含了函数B,那么函数B就是函数A的内部函数,
  而内部函数如果要使用一个变量,首先看自己内部有没有这个变量,
  如果没有,就会去紧挨着的上一级查找,【就近原则】
  如果函数一层一层都找不到,最后才会去全局变量下面找。
  1.         var a = 1;
  2.         var b = 11;
  3.         function fn1() {
  4.             var a = 2;
  5.             var b = '22';
  6.             fn2();
  7.             function fn2() {
  8.                 var a = 3;
  9.                 fn3();
  10.                 function fn3() {
  11.                     var a = 4;
  12.                     console.log(a); // 4
  13.                     console.log(b); // '22'
  14.                 }
  15.             }
  16.         }
  17.         fn1();
复制代码
  3. 垃圾回收机制
  可以参考这位大哥对于JS垃圾回收机制的描述:
  //www.jb51.net/article/229425.htm
  我们结合这三个概念看闭包的作用

2. 闭包的作用:
  我们把函数A叫作外层的函数,这个函数内部有一个函数B。
  外部用一个变量f接受函数A的返回值【函数B】
  而函数A作用域的变量叫作num
  1. 能够在函数的外部访问函数内部的变量【搭建外部访问内部作用域的通道】
  原理:上面其实有解释过。
  第一要理解,作用链的原理看上面。函数B能够调用函数A的变量num
  第二要理解,首先函数A的返回值是函数B【内部函数】,其次这个返回值要在函数外部用变量f接受,接受以后就能够调用函数B,函数B就会访问函数A的变量num。而这个内部函数B就是闭包函数啦。
  2. 能够延长函数内部变量的生命周期。
  第一个作用带来第二个作用。js的变量存在垃圾回收机制,如果函数执行完毕,变量会被清除,内存也会消除。可是如果利用闭包,变量可以不被立即清除。
  原因是,外部的变量f接受了一个函数A的内部函数B,而这个内部函数访问了函数A作用域的变量num,只要函数B执行且变量f一直存在,那么变量num就会一直存在。不会因为函数A的执行结束就消失。
  参考了下面的文章,讲的非常详细,推荐看。
  JavaScript闭包详解

3. 闭包示例
  后面会补充闭包的一些应用。
  我们要想起什么场合用闭包,闭包不能滥用。

3.1 点击li,输出当前li的索引号
  1.     ‹ul class="nav">
  2.         ‹li>榴莲‹/li>
  3.         ‹li>臭豆腐‹/li>
  4.         ‹li>鲱鱼罐头‹/li>
  5.         ‹li>大猪蹄子‹/li>
  6.     ‹/ul>
  7.     ‹script>
  8.         // 闭包应用-点击li输出当前li的索引号
  9.         // 1. 我们可以利用动态添加属性的方式
  10.         var lis = document.querySelector('.nav').querySelectorAll('li');
  11.         for (var i = 0; i ‹ lis.length; i++) {
  12.             lis[i].onclick = function () {
  13.                 console.log(i); // 四个4
  14.             }
  15.         }
  16.     ‹/script>
复制代码
  原理:上图这样写,打印出来的i永远都是4。原因是,此时首先是非严格模式,在非严格模式下,for循环是同步执行任务,而按钮点击再执行是异步任务,同步执行完毕,i加到了4.再执行异步任务打印i,都是4。
  改法1:用闭包
  1.for循环生成四个立即执行函数
  2. 立即执行函数是闭包的一种应用。立即执行函数里面的所有函数包括【点击 回调】函数都可以使用立即执行函数的传递的形参。
  1.         for (var i = 0; i ‹ lis.length; i++) {
  2.             (function (i) {
  3.                 // console.log(i);
  4.                 lis[i].onclick = function () {
  5.                     console.log(i);
  6.                 }
  7.             })(i);
  8.         }
复制代码
  改法2:var--->let
  点击对应小li,打印i是对应索引号。使用let是ES6语法,此时for有块级作用域
  1.         var lis = document.querySelector('.nav').querySelectorAll('li');
  2.         for (let i = 0; i ‹ lis.length; i++) {
  3.             lis[i].onclick = function () {
  4.                 // console.log(i);
  5.                 console.log(i);
  6.             }
  7.         }
复制代码
  改法3:用设置自定义属性index的方法
  1.         var lis = document.querySelector('.nav').querySelectorAll('li');
  2.         for (var i = 0; i ‹ lis.length; i++) { // 注意这里是var不是let
  3.             lis[i].index = i; // 注意这里是lis[i]不是this.index,此时没有点击,哪里来的this
  4.             lis[i].onclick = function () {
  5.                 console.log(this.index);
  6.             }
  7.         }
复制代码
总结

  本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|耀极客论坛 ( 粤ICP备2022052845号-2 )|网站地图

GMT+8, 2022-11-28 20:12 , Processed in 0.075217 second(s), 20 queries .

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表