关于JS闭包的学习内容如下
闭包
闭包:在一个作用域内使用函数等手段,在当前作用域内再生成一个局部作用域的过程就叫做闭包的过程。用来打包的函数就叫做闭包函数。
广义上讲:所有的函数都是闭包函数。
闭包一般用来使得全局可以操作局部变量:
<div>
<span id="btn">赞</span>
<span id="count">0</span>
</div>
<script>
const btn = document.getElementById('btn');
const count = document.getElementById('count');
const a = fn();
btn.onclick = a.subtract;
function fn() {
let n = 0;
return {
add: function () {
n++;
count.innerText = n;
},
subtract: function () {
n--;
count.innerText = n;
}
}
}
</script>
上例中,函数fn返回一个对象,其中包含两个函数,这两个函数的作用域链都经过函数fn,也就是都可以使用函数fn内的局部变量。
在全局,我们通过闭包函数可以间接访问或操作fn内的局部变量。
好处:
- 封装,将于某个效果相关的变量、函数、逻辑封装到一个函数中。
- 保持数据持久化。
- 形成单向数据流。
在写大型程序的时候,最忌讳的是一个变量被多个地方改变。
在写大型程序的时候,要求 单向数据流 。要改变变量的值,只能有一条路走。
闭包就能够实现单向数据流,在上例中,想要改变fn内的变量n,只能通过add或subtract这两个闭包函数来操作。
坏处:
正常情况下,函数执行完成后,函数内的所有资源都会被自动销毁,但是因为闭包的存在,函数执行完成后不会被销毁,会驻留内存。
闭包不建议使用,滥用闭包有可能会造成内存泄漏(内存溢出)。
闭包的应用
如果我们需要点击一个元素,得到它在同胞元素中的索引位置时,传统的写法是:
const arrLi = document.querySelectorAll('li');
for (let i = 0; i < arrLi.length; i++) {
arrLi[i].onclick = li_click;
}
function li_click(e) {
for (let i = 0; i < arrLi.length; i++) {
if (arrLi[i] == e.target) {
console.log(i);
}
}
}
坏处是,每次点击时,都需要再次遍历所有的li。
改为闭包的形式:
const arrLi = document.querySelectorAll('li');
for (let i = 0; i < arrLi.length; i++) {
arrLi[i].onclick = function () {
console.log(i);
};
}
for循环执行多少次,就会形成多少个独立的执行上下文,每个执行上下文的变量i的值都不一样。
事件的处理函数就是闭包函数,当事件被触发时,处理函数的作用域链包含当前for循环生成的执行上下文,所以,可以取得当前for循环执行上下文中的i的值。
为了保持for循环内部的代码数量,可以采用如下的方式改进:
const arrLi = document.querySelectorAll('li');
for (let i = 0; i < arrLi.length; i++) {
fn(i);
}
function fn(i) {
arrLi[i].onclick = function () {
console.log(i);
};
}