image.png

闭包

查阅 mdnwiki#:~: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 采用延迟解析(惰性解析),先解析顶层代码,忽略函数内部代码,但会由预解析器快速预解析函数内部代码。

预解析

预解析器会查看函数内部代码是否有语法错误,有则抛错。
另外查看内部函数是否有引用外部变量,如果有则把变量复制到堆中,并在执行代码时直接访问堆中数据。