深入理解闭包
# 闭包的本质
JavaScript 闭包的本质源自两点,词法作用域
和函数当作值传递
。
词法作用域,就是,按照代码书写时的样子, 内部函数可以访问函数外面的变量。引擎通过数据结构和算法表示一个函数,使得在代码解释执行时按照词法作用域的规则,可以访问外围的变量,这些变量就登记在相应的数据结构中。
函数当作值传递,即所谓的 first class 对象。就是可以把函数当作一个值来赋值,当作参数传给别的函数,也可以把函数当作一个值 return。一个函数被当作值返回时,也就相当于返回了一个通道,这个通道可以访问这个函数词法作用域中的变量,即函数所需要的数据结构保存了下来,数据结构中的值在外层函数执行时创建,外层函数执行完毕时理因销毁,但由于内部函数作为值返回出去,这些值得以保存下来。而且无法直接访问,必须通过返回的函数。这也就是私有性。
本来执行过程和词法作用域是封闭的,这种返回的函数就好比是一个虫洞,开了挂。
所以可以说: 使用回调函数就是在使用闭包
# 闭包的常见应用
# 封装私有变量
闭包常见的用法,封装私有变量。用户无法直接获取和修改变量的值,必须通过调用方法;并且这个用法可以创建只读的私有变量哦。我们从下面的例子来理解:
function People(num) {
// 构造器
var age = num
this.getAge = function() {
return age
}
this.addAge = function() {
age++
}
}
var lionel = new People(23) // new方法会固化this为lionel哦
lionel.addAge()
console.log(lionel.age) // undefined
console.log(lionel.getAge()) // 24
var karma = new People(20)
console.log(karma.getAge()) // 20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 计数器
比如网站分析采集用户点击按钮次数
function count() {
var num = 0
return function() {
return ++num
}
}
var getNum = count() // 第一个需要统计的地方
var getNewNum = count() //第二个需要统计的地方
// 如果我们统计的是两个button的点击次数
document.querySelectorAll('button')[0].onclick = function() {
console.log('点击按钮1的次数:' + getNum())
}
document.querySelectorAll('button')[0].onclick = function() {
console.log('点击按钮2的次数:' + getNewNum())
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 节流与防抖
// 节流
function throttle(fn, timeout) {
let timer = null
return function(...arg) {
if (timer) return
timer = setTimeout(() => {
fn.apply(this, arg)
timer = null
}, timeout)
}
}
// 防抖
function debounce(fn, timeout) {
let timer = null
return function(...arg) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, arg)
}, timeout)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
上次更新: 2023/04/05, 09:41:10