闭包
查阅 mdn 与 wiki#:~:text=In%20programming%20languages%2C%20a%20closure,function%20together%20with%20an%20environment.),闭包简言之就是函数与其词法环境(lexical environment)。
但这与我们日常使用闭包时理解有所偏差,我们习惯如此描述闭包:内部函数有对外部函数中变量的访问,且内部函数作为返回值。
作用域
作用域为源码中定义变量的区域(如全局作用域定义的变量可以全局使用,函数/块级作用域内定义的变量只能内部使用)。
作用域分为动态作用域与静态作用域(即词法作用域)。
词法作用域
函数的作用域为词法作用域,即在定义/声明函数处确定,这影响了函数内部查找变量时的作用域链。
动态作用域
JS 里的 this 与 bash script 中的函数都是动态作用域,动态作用域只有在调用时才能确定(此处不展开讲)。
JS 产生闭包的三个先决条件
函数中允许声明函数
不同于 C/C++ 中禁止函数内声明函数,JS 中允许函数内声明函数,且函数内声明的函数一般外部访问不到。
函数中声明的子函数允许访问父函数中声明的变量
这是 JS 中的作用域链机制,函数访问变量,是由内往外直到顶层环境查找的。
函数可以作为函数的返回值
JS 中的函数作为一等公民,可以执行调用,也可以像普通变量进行各种操作,比如作为函数的参数与函数的返回值。
V8 中的延迟解析(惰性解析)
JS 代码在 V8 引擎中是先经过编译生成字节码或二进制机器码,然后由解释器解释执行字节码,或者 CPU 直接执行二进制机器码。
但 V8 不会一次性把所有代码解释为中间代码,因为这会严重影响首次代码的执行的速度,同样也会占用过多内存。如大多数 JS 引擎,V8 采用延迟解析(惰性解析),先解析顶层代码,忽略函数内部代码,但会由预解析器快速预解析函数内部代码。
预解析
预解析器会查看函数内部代码是否有语法错误,有则抛错。
另外查看内部函数是否有引用外部变量,如果有则把变量复制到堆中,并在执行代码时直接访问堆中数据。