1. 闭包的定义作用和弊端
1.1 闭包:函数运行的一种机制(不是某种代码形式)
-
函数执行会形成一个私有上下文,如果上下文中的某些内容(一般指堆内存地址)被上下文以外的事物(变量/事件绑定等)所占用,则当前上下文不能被出栈释放,这是由浏览器的垃圾回收机制GC决定的,这就是闭包机制(形成了一个不被释放的上下文)。
- 保护:保护私有上下文中的私有变量和外界互不影响。
- 保存:上下文不被释放,那么上下文中的私有变量和值都会被保存起来,可以供其下级上下文中使用。
-
如果大量使用闭包,会导致栈内存太大,页面渲染会变慢,性能受到影响,所以真实项目中需要合理运用闭包,某些代码会导致栈溢出或者内存泄露。
1.2 内存溢出
- Uncaught RangeError: Maximum call stack size exceeded
// 无退出条件的递归,一直形成私有上下文
function fn(x) {
console.log(x)
fn(x+1)
}
fn(1);
2. 循环事件点击
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- button*3{我是第$个按钮} -->
<!-- 结构上设置自定义属性,存储按钮的索引 -->
<button index='0'>我是第1个按钮</button>
<button index='1'>我是第2个按钮</button>
<button index='2'>我是第3个按钮</button>
<script src="1.js"></script>
</body>
</html>
2.1 普通处理
- 执行后,点击每个按钮,结果都是一样
var buttons = document.querySelectorAll('button')
console.log(buttons) // buttons => NodeList 类数组集合
// buttons是类数组,无数组方法,手写循环
for(var i = 0; i < buttons.length; i++) {
buttons[i].onclick = function() {
console.log(`当前点击按钮的索引: ${i}`)
}
}
2.2 方案一 基于闭包机制完成
- 方案一循环多少次产生多少个闭包,损耗性能,减少使用
2.2.1 写法一
- 每轮循环执行一次自执行函数,每轮都会形成私有上下文,被buttons占用私有变量i,形成闭包
- 自执行函数执行,产生上下文EC(A),私有形参变量i=0/1/2
- EC(A)上下文中创建一个小函数,并让全局buttons中的某项占用创建的函数
var buttons = document.querySelectorAll('button')
for(var i = 0; i < buttons.length; i++) {
(
function(i) {
buttons[i].onclick = function() {
console.log(`当前点击按钮的索引: ${i}`)
}
}
)(i);
}
2.2.2 写法二
var buttons = document.querySelectorAll('button')
for(var i = 0; i < buttons.length; i++) {
buttons[i].onclick = (
function(i) {
return function() {
console.log(`当前点击按钮的索引: ${i}`)
}
}
)(i);
}
// fn中存储的是执行的返回值
var obj = {
fn: (
function() {
console.log('大函数')
return function() {
console.log('小函数')
}
}
)()
}
obj.fn() // 执行的是返回的小函数
2.2.3 写法三
// 基于let本质也是闭包
let buttons = document.querySelectorAll('button')
for(let i = 0; i < buttons.length; i++) {
buttons[i].onclick = function() {
console.log(`当前点击按钮的索引: ${i}`)
}
}
2.3 方案二 自定义属性
- 每轮循环都给当前对象设置一个自定义属性来存储索引
- 创建了n个button
for(var i = 0; i < buttons.length; i++) {
buttons[i].myIndex = i;
buttons[i].onclick = function() {
// this指向当前点击的按钮
console.log(`当前点击按钮的索引: ${this.myIndex}`)
}
}
2.4 方案三 事件委托
- html中标签中添加index属性
// 无论点击哪个body,都会触发当前点击body的click事件
document.body.onclick = function(ev){
// 事件源
var target = ev.target;
targetTag = target.tagName;
// 通过 tagName 得到的标签名都是大写的
if(targetTag === "BUTTON") {
var index = target.getAttribute('index')
console.log(`当前点击按钮的索引: ${index}`)
}
};