闭包的知识点和作用域息息相关。大家如果想了解作用域,可以看我之前的一篇作用域的文章。了解作用域的,都知道,局部作用域可以获取全局作用域的变量,但反之是不行的。那怎样才能让全局作用域可以访问到局部作用域的变量呢?这就产生了闭包。
概念
当内部函数尝试访问其外部函数的作用域链,即在直接词法作用域之外的变量时,会创建一个闭包。 闭包包含自己的作用域链,父级的作用域链和全局作用域。闭包不仅可以访问其外部函数中定义的变量,还可以访问外部函数的参数。
这样说,真的很拗口。
我个人理解的话,可以简单这样记
-
闭包内的变量或函数的内存占用在一个生命周期内永远不被销毁,意味着你的执行上下文栈被销毁了,它还在,所以你可以用来做计数器之类的功能,不过早期由于老旧浏览器的Bug,在闭包中操作DOM会导致内存溢出现象(现在一般不会,所以内存溢出现象基本也不考虑了),因为执行上下文栈被销毁,但函数的激活对象由于使用了闭包,是不被销毁的,所以内存占用会大点,如果有进行性能优化,可以关注下这块
-
闭包就是为了解决跨作用域的调用问题
示例
我们还是看看常用的闭包例子吧
1、计数器
function counter() {
let count = 0
function counterPlus() {
count++
console.log(count)
}
return counterPlus
}
var cp = counter() // 通过cp可以直接反馈counter函数的count变量
cp() // 1
cp() // 2
cp() // 3
2、同一作用域变量共用
function counter() {
var arr = []
for (var i=1; i<=3; i++) {
arr.push(function () {
return i*i
})
}
}
var rst = count()
var f1 = rst[0]
var f2 = rst[1]
var f3 = rst[3]
f1() // 16
f2() // 16
f3() // 16
这边你其实想得到的是1,4,9,但是得到了三个16,因为在作用域那边我们讲过,JavaScript是静态作用域,它并非靠执行过程改变,当这边三个函数返回时,他们引用的变量i其实已经都是4了,所以结果是16,那我们怎么才能得到1,4,9呢?
简单,我们需要创建一个它自己的新作用域,就可以了,把arr.push这样改造
arr.push((function(n){
return function () {
return n*n
}
})(i))
通过函数的参数绑定变量当前的值,无论变量后续如何变化,函数参数值是不变的
3、结果缓存
function getData() {
let cache = {}
return {
add: function(id) {
if (id in cache) {
return cache[id]
}
var d = new ... // 获取数据
cache[id] = d
return d
},
clear: function(id) {
if (id in cache) {
cache[id].clearSelection()
}
}
}
}
const searchData = getData()
searchData.add('jack')
这边通过数据,直接缓存,可以防止多次的网络请求,且缓存直接在函数内部,无需定义一个全局变量,操作很方便
4、私有化封装
const PersionClass = () => {
let name = 'jack'
return {
getName: () => {
return name
},
setName: (newName) => {
name = newName
}
}
}
const persion = PersionClass()
console.log(persion.name) // undefined
console.log(person.getName()) // jack
persion.setName('lucy')
console.log(persion.getName()) // lucy
这边你会看到,这很像Java中的POJO,私有化变更,提供方法来设置,其实JavaScript的很多面向对象的操作方法的基石就是闭包,比如类和继承等。