底层分析
HTML代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>循环添加事件</title>
</head>
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<script src="./add-event-loop.js"></script>
</body>
</html>
原始代码图解分析,为何打印结果都为3?
原始代码
// add-event-loop.js
var aButtons = document.querySelectorAll('button');
for (var i = 0; i < aButtons.length; i++) {
aButtons[i].onclick = function() {
// 下面的this 就是触发onclick事件的dom对象,即aButtons[i]
console.log(`当前索引值为${i}`);
}
}
图示分析
- 图示1-初始化内存图示
- 图示2-第一轮循环
- 图示3 - 三轮循环产生的内存使用
- 图示4 - onclick中存地址
- 图示5 - 点击事件进栈执行
- 图示6 - 整体执行流程结束
基于闭包思想改良,能够正确打印结果,从内存使用角度分析其性能
闭包改良版
// add-event-loop.js
var aButtons = document.querySelectorAll('button');
// 方式1
for (var i = 0; i < aButtons.length; i++) {
(function (i) {
aButtons[i].onclick = function () {
console.log(`当前索引值为${i}`);
}
})(i)
}
// 方式2
for (var i = 0; i < aButtons.length; i++) {
aButtons[i].onclick = (function(i) {
return function() {
console.log(`当前索引值为${i}`);
}
})(i)
}
图示分析
- 图示1 - 初始化
- 图示2 - onclick进行赋值
- 图示3
- 图示4 - 完整流程明细
闭包可能带来的负面影响
开辟一个(堆)内存空间存储函数表示一个函数的创建,进栈执行表示函数的执行,一般函数执行完成之后会进行出栈并释放堆内存空间,而使用闭包的话则不会出栈释放内存空间,会一直往执行环境栈中添加执行上下文,一直行进堆内存的占用,增加内存负担
- 在使用闭包时注意内存溢出,在合适位置清除内存引用
自定义属性改良版,相较于闭包版优势在哪?
不会开辟新的内存空间,对内存的消耗较小,与原始版代码底层原理相同,只是在每个button(事件)对象身上加了新的属性
自定义属性代码
// add-event-loop.js
var aButtons = document.querySelectorAll('button');
for (var i = 0; i < aButtons.length; i++) {
aButtons[i].myIndex = i;
aButtons[i].onclick = function() {
// 下面的this 就是触发onclick事件的dom对象,即aButtons[i]
console.log(`当前索引值为${this.myIndex}`);
}
}
图示分析
- 图示1 - 初始化
- 图示2 - 开始执行循环
- 相当于在每个button对象里加了一个myIndex的属性
- 相当于在每个button对象里加了一个myIndex的属性
- 图示3 - 事件执行
ES6 let关键字版
ES6版代码
// add-event-loop.js
// 3.ES6 let关键字:同样也是基于闭包思想
var aButtons = document.querySelectorAll('button');
for (let i = 0; i < aButtons.length; i++) {
aButtons[i].onclick = function() {
console.log(`当前索引值为${i}`);
}
}
其底层原理与闭包相同,其实也是基于闭包基础的