【Javascript】- 闭包

228 阅读4分钟

提到学习 Javascript ,闭包(Closure)一定是绕不开的话题,这玩意儿起初看上去会让人一脸懵,但是理解了就会发现,其实也就是那么回事儿。

Javascript 中为啥要有闭包这个东西呢?我个人的理解是:由于函数可以被当作返回值来传递,因此 Javascript 不得不使用闭包

作为返回值的函数

首先,先来提几个基础的知识点和规则,方便理解 :

  1. Javascript中有全局作用域、局部作用域,声明一个函数就会创建一个局部作用域(函数作用域)
  2. Javascript中,函数运行完毕以后会被销毁,函数作用域中的所有变量也会被销毁
  3. 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 可以正确打印出变量abc吗?
  • f1() 运行完了以后,f1() 就会被销毁,那么 f1 函数中声明的变量和函数也应该被销毁吗?

运行一下上面的代码,你会发现,没有任何报错,这一段代码正确的运行了,这是为什么呢?

按照前面的规则2来说,函数f1调用以后应该被立即销毁,但是f1的返回值是一个函数f2,如果要使f2能够正常运行,访问到f1中的变量,那么f1就不能被销毁;同理,要让f2内部声明的函数f3正常运行,那f2也不能销毁。

Javascript如果解决这个矛盾呢?

Javascript会将一个函数所访问的作用域打包,附加到这个函数上,只要这个函数还没有被真正调用,那么这个包就一直存在,不会被销毁——这就是闭包

想象一下,你一下子回到15年前,你还是个小学生,天天背着书包上学校;函数就好比是小学生,闭包就是小学生的书包😉😉😉

Scope

我们可以将上面的代码运行时打印出来看看

image-20210509205449806.png

断点处的作用域情况如下图:

image-20210509205555723.png

可以看到作用域Scope里面是一个栈的结构,从栈底到栈顶依次是GlobalClosure(f1)Closure(f2)Local,它们的含义依次是:

  • Global——浏览器全局作用域,也就是window对象
  • Closure——也就是“闭包”的意思,它就是函数运行时所依赖的作用域,存储了函数引用的所有变量
  • Local——函数自身的作用域,存储了函数内部定义的所有变量

我们再来展开这四个对象,验证一下

image-20210509210324199.png

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()()()

image-20210509210324199.png

看清楚了哦,这次Closure(f1)Closure(f2)里面都没有包含新声明的变量onetwo,只有Local里面包含了新声明的变量three,这说明了什么?这说明闭包并不会将函数所涉及到的作用域中的变量统统包含进去,而是只包含那些被函数引用的变量,这么做的目的就是为了最大限度地节省内存空间,把用不到的都扔掉,只留下有用的。打个比方,如果你只是去上一节数学课,那么你书包里就没必要装他个几十本书,什么历史地理语文......统统都塞进去,只放一本数学书不就够啦🤪🤪🤪

总结

闭包其实不难懂,弄懂闭包以后,后面的this也不会很难懂。闭包就是函数运行时所处的作用域,这个作用域里面存放着函数引用的变量——就是这么简单