关于作用域、作用域链以及闭包
作用域
javascript采用的静态作用域
意思是说作用域是在定义的时候就创建了, 而不是运行的时候
作用域链向上查找是寻找创建它的那个作用域
JS引擎执行顺序
- 全局初始化
创建一个全局对象(Global Object),将Math,String,Date,document 等常用的JS对象作为其属性
(它的属性在任何地方都可以访问,它的存在伴随着应用程序的整个生命周期)JS引擎需要构建一个执行环境栈( Execution Context Stack)
与此同时,也要创建一个全局执行环境(Execution Context)EC
并将这个全局执行环境EC压入执行环境栈中。在javascript中
每个函数都有自己的执行环境
当执行一个函数时,该函数的执行环境就会被推入执行环境栈的顶部并获取执行权
当这个函数执行完毕,它的执行环境又从这个栈的顶部被删除
并把执行权并还给之前执行环境(还给全局执行环境)。JS引擎还要创建一个与EC关联的全局变量对象(Varibale Object) VO
并把VO指向全局对象,VO中不仅包含了全局对象的原有属性,还包括在全局定义的变量。
- 执行函数
JS引擎会创建函数A的执行环境EC,然后EC推入执行环境栈的顶部并获取执行权
创建函数A的作用域链(Scope Chain)
在javascript中,每个执行环境都有自己的作用域链,用于标识符解析,当执行环境被创建时
它的作用域链就初始化为当前运行函数的scope所包含的对象(scope指向定义时的作用域)创建一个当前函数的活动对象(Activation Object) AO
AO中包含了函数的形参、arguments对象、this对象、以及局部变量和内部函数的定义
然后AO会被推入作用域链的顶端。(此时作用域链为:AO(A)->VO(G))
做个简单总结记忆
VO(Varibale Object) 变量对象:定义时创建 包含定义时的环境对象(函数外部环境)
AO(Activation Object) 活动对象:执行时创建 包含函数内部定义的属性
定义函数时,会添加scope属性,指向定义函数时所在的环境
执行函数时,先创建函数的作用域链,初始化为scope所包含的对象,然后创建AO,并推入作用域链的顶端
例子1:当function A() 为全局定义时 作用域链:AO(A) -> VO(G)
例子2:当function B() 为function A()的内部函数时 作用域链:AO(B) -> AO(A) -> VO(G) (此时B的[scope]: AO(A))
闭包
自由变量:指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量
闭包 = 函数 + 函数能够访问的自由变量
闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性
- 从理论角度:所有函数都是闭包
- 从实践角度:以下函数才算是闭包:
即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)(执行上下文执行完成会被销毁)
在代码中引用了自由变量
内部变量会随执行上下文销毁而被回收,但当出现闭包时,在内部函数被多次调用过程中,这些私有变量能够保持其持久性,直到不被调用,才会被销毁
参考:
https://www.cnblogs.com/onepixel/p/5090799.html
https://juejin.im/post/6865184344990810126#heading-21 作用域与作用域链、闭包部分
引申的性能优化
减少全局变量的使用
每引用一次全局变量,JS引擎就要查找整个作用域链 比如作用域链的最底端window和document对象就存在这个问题(会导致查找整个作用域链)闭包内存释放 防止内存泄漏
将引用设为 null
不设引用,直接执行函数,执行完成后,会自动被回收(因为没有引用)
[[若将闭包的内部函数赋给了全局变量,且不手动释放,则占用的内存将一直的不到释放,直到程序关闭]]