闭包和设计模式
一、闭包
1、闭包介绍
是由于作用域嵌套,让全局变量和局部的引用类型数据保持了引用关系,导致执行空间不销毁的场景。
例:
// 普通的函数没有嵌套
function fn() {
var a = 1
console.log(a++);
}
fn() // 1
fn() // 1
fn() // 1
// 有函数嵌套
function big() {
var a = 1
function small() {
console.log(a++);
}
return small
}
var s = big()
s() // 1
s() // 2
s() // 3
函数作用域嵌套,造成变量执行完不被销毁的场景就叫闭包。
2、闭包形成的原理
js代码执行,都在内存的调用栈内存中进行,函数调用后,会在调用栈中先创建一个执行空间,局部变量,会在执行空间中创建,然后执行代码,当函数代码执行接收后,会从调用栈中销毁这个执行空间。
所以,没有函数嵌套的时候,函数中的局部变量,在执行结束后,执行空间销毁,变量也会被销毁,下次调用的时候会重新在调用栈创建执行空间。
如下图:
这个过程重复了3次。所以每次输出都是1。
闭包的执行过程如下图:
全局s要和执行空间1中的small保持引用关系,全局s要在后面也能使用,所以执行空间1中的small不能被销毁,所以执行空间1也不能销毁。
所以第二次调用的时候,输出的还是执行空间1中的变量a,是第一次修改后的值2。
通常表现:
- 大函数中返回小函数
- 小函数使用大函数中的变量
- 全局变量跟局部的小函数保持引用关系
3、闭包的好处
- 保护私有变量不被全局污染
- 间接的让函数外可以访问函数内的变量
- 延长了变量的生命周期
外面函数每调用一次,就会在调用栈保留一个执行空间,调用多了话,可能会造成内存溢出/内存泄漏
4、闭包的应用
4.1、循环中使用异步或事件
<ul>
<li>javascript修炼内功</li>
<li>vue修炼独孤九剑</li>
<li>react修炼乾坤大挪移</li>
</ul>
</body>
<script>
var lis = document.querySelectorAll('li')
for(var a=0; a<lis.length; a++) {
lis[a].onclick = click(a)
}
function click(a) {
return function() {
alert(lis[a].innerText)
}
}
</script>
4.2、防抖
有一些事件,触发的频率特别高,往往我们在网页中动了一下,事件就会触发很多次,例如:鼠标移动事件、键盘按下事件、浏览器滚动事件、浏览器大小改变事件、文本框及时改变内容事件等。或者人为的频繁触发事件,例如:滑动轮播图,在第一张图还没有完全滑动过去,就点击了多次按钮来触发滑动行为。
这些频繁触发的事件,往往我们不需要触发太多次数,多个动作中,只需要触发一次或几次,而不是每次都触发。
防抖:指事件在频繁的触发时,函数只执行一次。
获取最后一次执行的结果:
document.onmousemove = debounce(fn, 1000)
function debounce(handler, time) {
var timer = 0
return function() {
clearTimeout(timer)
timer = setTimeout(() => {
fn.call(this, ...arguments)
}, time)
}
}
function fn(e) {
console.log( e.pageX );
}
获取第一次执行的结果:
document.onmousemove = throttling(fn, 1000