注意知识点:只有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是5
for(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个。只创建一个函数,一个堆内存。