注意知识点:只有function xxx(){} 才有自己的私有快杰作用域,匿名函数没有。
1、文件1:index.html
<!DOCTYPE html><!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width-device-width, initial-sacle=1.0"><title>闭包应用</title></head><body><button>按钮1</button><button>按钮2</button><button>按钮3</button><button>按钮4</button><button>按钮5</button><script src="./closure.js"></script></body></html>
2、文件closure.js
var buttonList = document.querySelectorAll('button');// 实现不了,为什么?// 循环中的i是全局的,每一轮循环给对应的元素的click绑定方法(创建函数【存储代码块,字符串】,此时函数没有执行)// 循环结束的时候,全局i为5// 点击某个按钮,执行之前绑定的函数:此时形成一个全新的私有上下文,它的上级上下文是全局上下文(看创建的时候的环境)// 函数代码执行的过程中,遇到一个变量i,但是i不是自己的私有变量,找到的是全局的i,全局的i是5for(var i=0; i<buttonList.length; i++) {buttonList[i].onclick = function() {console.log(`我是第${i+1}个按钮~`)}}
答案:不管点哪个按钮,打印出来都是”我是第6个按钮~”
buttonList是个集合,类型是个类数组的对象。
buttonList[i].onclick属于DOM 0级事件绑定。
buttonList[0].onclick = function(){} =》把函数function(){} 赋值给0x000001里面的click属性,那么click的值null 变为函数的地址0x100001,且0x100001里面存的是代码字符串,里面还是i。

第一个按钮点击的时候:0x100001所指的函数开始执行,形成全新的私有上下文EC(1)。
作用域链是

解决问题的思路
当点击事件触发,执行对应的函数,用到的 i不要再向全局查找了,应该用自己形成的上下文里存储的i,存储的值是指定的索引即可 =》 闭包的保存机制
【弊端】循环多少次,就产生多少个闭包,非常消耗内存
var buttonList = document.querySelectorAll('button');for(var i=0; i<buttonList.length; i++) {// 每一轮循环都会把自执行函数执行,形成一个全新的私有上下文(一共形成了五个)// 把当前这一轮全局i的值作为实参,传递给当前形成的私有上下文中的形参n(私有变量)// 第一轮私有上下文中的n=0,第二轮私有上下文中的n=1......// 每一个形成的上下文中,创建的函数都被外部的元素对象的onclick占用了,所以形成了5个闭包// 当点击按钮执行函数的时候,遇到一个变量n,不是自己私有的变量,则找上级上下文中n,而存储的值就是它的索引(function(n){buttonList[n].onclick = function() {console.log(`我是第个${n+1}按钮`);}})(i)}
var buttonList = document.querySelectorAll('button');for(var i=0; i<buttonList.length; i++){buttonList[i].onclick = (function(n){// n是每一轮形成的闭包中的私有变量,五个闭包中存储的值分别是0~4【索引】// 每一次都是把小函数返回,赋值给元素的点击事情,当点击元素的时候,执行返回的小函数return function(){console.log(`我是第个${n+1}按钮`)}})(i);}
//还是基于闭包的基础,但是不是自己去执行函数构建,而是利用ES6中let产生的私有上下文实习,还是闭包机制(底层实现的)
注意:buttonList[i]的onclick属性引用了每个私有块的堆内存,私有块不销毁,但是父级块(控制循环)销毁,应该是创建了4个块,最后剩下3个块。
var buttonList = document.querySelectorAll('button');for(let i=0; i<buttonList.length; i++){// 第一轮循环,私有块1:私有变量i=0;当前私有上下文中创建的一个函数被全局的元素对象的onclick占用了,形成了闭包// ......buttonList[i].onclick = function(){console.log(`我是第个${i+1}按钮`)}}
let构建的for循环,底层的处理机制
注意:这里是创建了4个块,执行结束都销毁了,因为没有被占用,执行完就会销毁。
块级私有上下文,父作用域
- 控制循环进行
- 每一轮循环都会产生一个私有的块级上下文
- i是每一个块级上下文中的私有变量
for(let i=0; i<3; i++){console.log(i); // 0~2}console.log(i); // Uncaught ReferenceError: i is not defined

for(let i=0; i<3; i++){i++;console.log(i);//1, 3}console.log(i); // Uncaught ReferenceError: i is not defined
问题:循环事件绑定,你是怎么处理索引机制的?
答案:~~ 用闭包或者let解决~~
应该说用闭包来解决,因为let也是闭包。
除了用闭包,还有别的方案么?
方案二:自定义属性(项目中很常用,事先把信息存储到元素身上,后期在一些其他的操作中,想要获取信息,直接基于元素访问) => 操作DOM的时代下,这种方案非常常用
var buttonList = document.querySelectorAll('button');for(let i=0; i<buttonList.length; i++){// 把当前按钮的索引存储在它的自定义属性上(每个按钮都是一个元素对象)buttonList[i].myIndex = i;buttonList[i].onclick = function(){// 给当前元素的某个事件绑定方法,当事件触发,方法执行的时候,方法中的this是当前点击的按钮console.log(`我是第个${this.myIndex+1}按钮`)}}
此方案的性能比闭包的性能提高了很多,堆内存已经有了,只是在堆内存中新增了一个myIndex属性。
但是这个方案还是开辟了很多堆内存,var buttonList = document.querySelectorAll(‘button’);就有6个堆内存,集合是一个,集合每个里面的索引了5个堆内存,赋值给onclick的函数也创建了5个堆内存,堆内存较多,比较浪费。
方案三:利用事件代理机制(性能提高>=40%)
<!DOCTYPE html><!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width-device-width, initial-sacle=1.0"><title>闭包应用</title></head><body><button index="0">按钮1</button><button index="1">按钮2</button><button index="2">按钮3</button><button index="3">按钮4</button><button index="4">按钮5</button><script src="./closure.js"></script></body></html>
document.body.onclick = function(ev) {// 利用事件冒泡机制console.log(ev);let target = ev.target;if(target.tagName === 'BUTTON'){let index = target.getAttribute('index')console.log(`我是第个${+index+1}按钮`)}}
没有获取buttonList,这就一下少了5个。只创建一个函数,一个堆内存。
