由代码的定义的位置决定,包括它所声明时的所有外部作用域,直至全局作用域(其内部的作用域又称为动态作用域)
首先我们看完这段代码,需要明确几点
全局作用域中,只有foo这个标识
foo内部,有a 、b 、bar这三个标识
bar内部,只有c一个标识
当bar执行的时候,内部没有ab,会向上查找(一旦找到第一个匹配,作用域查询就停止了。相同的标识符名称可以在嵌套作用域的多个层中被指定,这称为“遮蔽(shadowing)”(内部的标识符“遮蔽”了外部的标识符)。无论如何遮蔽,作用域查询总是从当前被执行的最内侧的作用域开始,向外/向上不断查找,直到第一个匹配才停止。)
函数里边的函数,里边的函数就称之为闭包。它存储了一个函数和一个关联的环境。严格意义上来讲,一个javascrip函数,如果访问了外层作用域的变量,那它就是一个闭包。通俗来讲闭包就是函数能够记住并访问它的词法作用域,即使当这个函数在它的词法作用域之外执行时
函数创建的时候,函数的隐藏属性[environment]会记住函数创建的位置,即当时的词法环境,这样无论函数在哪里调用,都会去[environment]引用的词法环境中查找变量,先去词法环境内部查询,没有的话再去当前词法环境记录的外部词法环境寻找
ab和add就组成了严格意义的闭包,但同时也存在着内存泄漏问题,app函数调用完毕之后,demo函数会销毁,但是其中的变量a b不会被销毁,因为add函数对其有引用,那么如何解决呢? app = null
假如我们打印 c,当前环境中没有 c,我们会向上级作用域去查找,这种链式称之为作用域链, 其实在闭包当中很明显应用的作用域链的概念,即当前环境的中存在指向父级作用域的引用
函数中返回函数
在定时器、冒泡、事件监听、Ajax 请求、Web Workers 或者任何异步中,只要使用了回调函数,实际上就是在使用闭包。请看下面这段代码,这些都是平常开发中用到的形式。
setTimeout 为宏任务,由于 JS 中单线程 eventLoop 机制,在主线程同步任务执行完后才去执行宏任务,因此循环结束后 setTimeout 中的回调才依次执行。
因为 setTimeout 函数也是一种闭包,往上找它的父级作用域链就是 window,变量 i 为 window 上的全局变量,开始执行 setTimeout 之前变量 i 已经就是 6 了,因此最后输出的连续就都是 6
当内部的函数没有执行完毕的时候,其外部函数变量不会被销毁