注意知识点:只有function xxx(){} 才有自己的私有快杰作用域,匿名函数没有。

1、文件1:index.html

  1. <!DOCTYPE html>
  2. <!DOCTYPE html>
  3. <html>
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width-device-width, initial-sacle=1.0">
  7. <title>闭包应用</title>
  8. </head>
  9. <body>
  10. <button>按钮1</button>
  11. <button>按钮2</button>
  12. <button>按钮3</button>
  13. <button>按钮4</button>
  14. <button>按钮5</button>
  15. <script src="./closure.js"></script>
  16. </body>
  17. </html>


2、文件closure.js

  1. var buttonList = document.querySelectorAll('button');
  2. // 实现不了,为什么?
  3. // 循环中的i是全局的,每一轮循环给对应的元素的click绑定方法(创建函数【存储代码块,字符串】,此时函数没有执行)
  4. // 循环结束的时候,全局i为5
  5. // 点击某个按钮,执行之前绑定的函数:此时形成一个全新的私有上下文,它的上级上下文是全局上下文(看创建的时候的环境)
  6. // 函数代码执行的过程中,遇到一个变量i,但是i不是自己的私有变量,找到的是全局的i,全局的i是5
  7. for(var i=0; i<buttonList.length; i++) {
  8. buttonList[i].onclick = function() {
  9. console.log(`我是第${i+1}个按钮~`)
  10. }
  11. }

答案:不管点哪个按钮,打印出来都是”我是第6个按钮~”

buttonList是个集合,类型是个类数组的对象。
buttonList[i].onclick属于DOM 0级事件绑定。

buttonList[0].onclick = function(){} =》把函数function(){} 赋值给0x000001里面的click属性,那么click的值null 变为函数的地址0x100001,且0x100001里面存的是代码字符串,里面还是i。

image.png
第一个按钮点击的时候:0x100001所指的函数开始执行,形成全新的私有上下文EC(1)。
作用域链是,代码执行的时候是console.log(i+1),i当前EC(1)上下文中没有,去上一级去找,在EC(G)中有i,且此时的值是5。
image.png

4.png

解决问题的思路

当点击事件触发,执行对应的函数,用到的 i不要再向全局查找了,应该用自己形成的上下文里存储的i,存储的值是指定的索引即可 =》 闭包的保存机制
【弊端】循环多少次,就产生多少个闭包,非常消耗内存

  1. var buttonList = document.querySelectorAll('button');
  2. for(var i=0; i<buttonList.length; i++) {
  3. // 每一轮循环都会把自执行函数执行,形成一个全新的私有上下文(一共形成了五个)
  4. // 把当前这一轮全局i的值作为实参,传递给当前形成的私有上下文中的形参n(私有变量)
  5. // 第一轮私有上下文中的n=0,第二轮私有上下文中的n=1......
  6. // 每一个形成的上下文中,创建的函数都被外部的元素对象的onclick占用了,所以形成了5个闭包
  7. // 当点击按钮执行函数的时候,遇到一个变量n,不是自己私有的变量,则找上级上下文中n,而存储的值就是它的索引
  8. (function(n){
  9. buttonList[n].onclick = function() {
  10. console.log(`我是第个${n+1}按钮`);
  11. }
  12. })(i)
  13. }
  1. var buttonList = document.querySelectorAll('button');
  2. for(var i=0; i<buttonList.length; i++){
  3. buttonList[i].onclick = (function(n){
  4. // n是每一轮形成的闭包中的私有变量,五个闭包中存储的值分别是0~4【索引】
  5. // 每一次都是把小函数返回,赋值给元素的点击事情,当点击元素的时候,执行返回的小函数
  6. return function(){
  7. console.log(`我是第个${n+1}按钮`)
  8. }
  9. })(i);
  10. }

//还是基于闭包的基础,但是不是自己去执行函数构建,而是利用ES6中let产生的私有上下文实习,还是闭包机制(底层实现的)
注意:buttonList[i]的onclick属性引用了每个私有块的堆内存,私有块不销毁,但是父级块(控制循环)销毁,应该是创建了4个块,最后剩下3个块。

  1. var buttonList = document.querySelectorAll('button');
  2. for(let i=0; i<buttonList.length; i++){
  3. // 第一轮循环,私有块1:私有变量i=0;当前私有上下文中创建的一个函数被全局的元素对象的onclick占用了,形成了闭包
  4. // ......
  5. buttonList[i].onclick = function(){
  6. console.log(`我是第个${i+1}按钮`)
  7. }
  8. }

let构建的for循环,底层的处理机制

注意:这里是创建了4个块,执行结束都销毁了,因为没有被占用,执行完就会销毁。
块级私有上下文,父作用域

  • 控制循环进行
  • 每一轮循环都会产生一个私有的块级上下文
  • i是每一个块级上下文中的私有变量
    1. for(let i=0; i<3; i++){
    2. console.log(i); // 0~2
    3. }
    4. console.log(i); // Uncaught ReferenceError: i is not defined
    image.png
    1. for(let i=0; i<3; i++){
    2. i++;
    3. console.log(i);//1, 3
    4. }
    5. console.log(i); // Uncaught ReferenceError: i is not defined

问题:循环事件绑定,你是怎么处理索引机制的?
答案:~~ 用闭包或者let解决~~
应该说用闭包来解决,因为let也是闭包。
除了用闭包,还有别的方案么?

方案二:自定义属性(项目中很常用,事先把信息存储到元素身上,后期在一些其他的操作中,想要获取信息,直接基于元素访问) => 操作DOM的时代下,这种方案非常常用

  1. var buttonList = document.querySelectorAll('button');
  2. for(let i=0; i<buttonList.length; i++){
  3. // 把当前按钮的索引存储在它的自定义属性上(每个按钮都是一个元素对象)
  4. buttonList[i].myIndex = i;
  5. buttonList[i].onclick = function(){
  6. // 给当前元素的某个事件绑定方法,当事件触发,方法执行的时候,方法中的this是当前点击的按钮
  7. console.log(`我是第个${this.myIndex+1}按钮`)
  8. }
  9. }

此方案的性能比闭包的性能提高了很多,堆内存已经有了,只是在堆内存中新增了一个myIndex属性。
但是这个方案还是开辟了很多堆内存,var buttonList = document.querySelectorAll(‘button’);就有6个堆内存,集合是一个,集合每个里面的索引了5个堆内存,赋值给onclick的函数也创建了5个堆内存,堆内存较多,比较浪费。

方案三:利用事件代理机制(性能提高>=40%)

  1. <!DOCTYPE html>
  2. <!DOCTYPE html>
  3. <html>
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width-device-width, initial-sacle=1.0">
  7. <title>闭包应用</title>
  8. </head>
  9. <body>
  10. <button index="0">按钮1</button>
  11. <button index="1">按钮2</button>
  12. <button index="2">按钮3</button>
  13. <button index="3">按钮4</button>
  14. <button index="4">按钮5</button>
  15. <script src="./closure.js"></script>
  16. </body>
  17. </html>
  1. document.body.onclick = function(ev) {
  2. // 利用事件冒泡机制
  3. console.log(ev);
  4. let target = ev.target;
  5. if(target.tagName === 'BUTTON'){
  6. let index = target.getAttribute('index')
  7. console.log(`我是第个${+index+1}按钮`)
  8. }
  9. }

没有获取buttonList,这就一下少了5个。只创建一个函数,一个堆内存。