提到学习 Javascript ,闭包(Closure)一定是绕不开的话题,这玩意儿起初看上去会让人一脸懵,但是理解了就会发现,其实也就是那么回事儿。
Javascript 中为啥要有闭包这个东西呢?我个人的理解是:由于函数可以被当作返回值来传递,因此 Javascript 不得不使用闭包
作为返回值的函数
首先,先来提几个基础的知识点和规则,方便理解 :
- Javascript中有全局作用域、局部作用域,声明一个函数就会创建一个局部作用域(函数作用域)
- Javascript中,函数运行完毕以后会被销毁,函数作用域中的所有变量也会被销毁
- Javascript的静态作用域机制规定,函数的作用域是在声明时确定的而不是在运行时
function f1() {
const a = 'AAA'
function f2() {
const b = 'BBB'
function f3() {
const c = 'CCC'
console.log(a);
console.log(b);
console.log(c);
}
return f3
}
return f2
}
f1()()()
再来分析下上面的代码,这里提出两个问题:
f1()()()表示运行了函数f3,那么,函数f3可以正确打印出变量a、b、c吗?f1()运行完了以后,f1()就会被销毁,那么 f1 函数中声明的变量和函数也应该被销毁吗?
运行一下上面的代码,你会发现,没有任何报错,这一段代码正确的运行了,这是为什么呢?
按照前面的规则2来说,函数f1调用以后应该被立即销毁,但是f1的返回值是一个函数f2,如果要使f2能够正常运行,访问到f1中的变量,那么f1就不能被销毁;同理,要让f2内部声明的函数f3正常运行,那f2也不能销毁。
Javascript如果解决这个矛盾呢?
Javascript会将一个函数所访问的作用域打包,附加到这个函数上,只要这个函数还没有被真正调用,那么这个包就一直存在,不会被销毁——这就是闭包
想象一下,你一下子回到15年前,你还是个小学生,天天背着书包上学校;函数就好比是小学生,闭包就是小学生的书包😉😉😉
Scope
我们可以将上面的代码运行时打印出来看看
断点处的作用域情况如下图:
可以看到作用域Scope里面是一个栈的结构,从栈底到栈顶依次是Global → Closure(f1) → Closure(f2) → Local,它们的含义依次是:
- Global——浏览器全局作用域,也就是
window对象 - Closure——也就是“闭包”的意思,它就是函数运行时所依赖的作用域,存储了函数引用的所有变量
- Local——函数自身的作用域,存储了函数内部定义的所有变量
我们再来展开这四个对象,验证一下
Closure就是闭包,闭包就是Closure,闭包就是上面那么回事儿
闭包的内容
前面说到闭包会伴随着函数的存在而一直存在,直到函数调用结束,也就是说这一时期闭包将会一直存在于内存中。现实中一个程序里面,往往会有多到数不清的函数调用,所以就会有数不清的闭包;那么问题来了,Javascript在为函数生成闭包的时候是不管什么变量,有用的没用的都一股脑儿放进去吗?🤔
稍微修改一下上面的代码,再看看打印的结果,这回f3还是打印原来那三个变量,不过,在每个函数中多声明了一个变量,只是f3并不会用到它们
function f1() {
const a = 'AAA',
one = 'aaa'
function f2() {
const b = 'BBB',
two = 'bbb'
function f3() {
const c = 'CCC',
three = 'ccc'
console.log(a);
console.log(b);
console.log(c);
}
return f3
}
return f2
}
f1()()()
看清楚了哦,这次Closure(f1)和Closure(f2)里面都没有包含新声明的变量one和two,只有Local里面包含了新声明的变量three,这说明了什么?这说明闭包并不会将函数所涉及到的作用域中的变量统统包含进去,而是只包含那些被函数引用的变量,这么做的目的就是为了最大限度地节省内存空间,把用不到的都扔掉,只留下有用的。打个比方,如果你只是去上一节数学课,那么你书包里就没必要装他个几十本书,什么历史地理语文......统统都塞进去,只放一本数学书不就够啦🤪🤪🤪
总结
闭包其实不难懂,弄懂闭包以后,后面的this也不会很难懂。闭包就是函数运行时所处的作用域,这个作用域里面存放着函数引用的变量——就是这么简单