JS-作用域理解

wujiawen 发布于

关于作用域、作用域链以及闭包

作用域

javascript采用的静态作用域
意思是说作用域是在定义的时候就创建了, 而不是运行的时候
作用域链向上查找是寻找创建它的那个作用域

JS引擎执行顺序

  • 全局初始化
  1. 创建一个全局对象(Global Object),将Math,String,Date,document 等常用的JS对象作为其属性
    (它的属性在任何地方都可以访问,它的存在伴随着应用程序的整个生命周期)

  2. JS引擎需要构建一个执行环境栈( Execution Context Stack)
    与此同时,也要创建一个全局执行环境(Execution Context)EC
    并将这个全局执行环境EC压入执行环境栈中。在javascript中
    每个函数都有自己的执行环境
    执行一个函数时,该函数的执行环境就会被推入执行环境栈的顶部并获取执行权
    当这个函数执行完毕,它的执行环境又从这个栈的顶部被删除
    并把执行权并还给之前执行环境(还给全局执行环境)。

  3. JS引擎还要创建一个与EC关联的全局变量对象(Varibale Object) VO
    并把VO指向全局对象,VO中不仅包含了全局对象的原有属性,还包括在全局定义的变量

  • 执行函数
  1. JS引擎会创建函数A的执行环境EC,然后EC推入执行环境栈的顶部并获取执行权

  2. 创建函数A的作用域链(Scope Chain)
    在javascript中,每个执行环境都有自己的作用域链,用于标识符解析,当执行环境被创建时
    它的作用域链就初始化为当前运行函数的scope所包含的对象(scope指向定义时的作用域

  3. 创建一个当前函数的活动对象(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))

    闭包

  • 自由变量:指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量

  • 闭包 = 函数 + 函数能够访问的自由变量

  • 闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性

  1. 从理论角度:所有函数都是闭包
  2. 从实践角度:以下函数才算是闭包:
    即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)(执行上下文执行完成会被销毁)
    在代码中引用了自由变量

内部变量会随执行上下文销毁而被回收,但当出现闭包时,在内部函数被多次调用过程中,这些私有变量能够保持其持久性,直到不被调用,才会被销毁

参考:
https://www.cnblogs.com/onepixel/p/5090799.html
https://juejin.im/post/6865184344990810126#heading-21 作用域与作用域链、闭包部分

引申的性能优化

  • 减少全局变量的使用
    每引用一次全局变量,JS引擎就要查找整个作用域链 比如作用域链的最底端window和document对象就存在这个问题(会导致查找整个作用域链)

  • 闭包内存释放 防止内存泄漏

  1. 将引用设为 null

  2. 不设引用,直接执行函数,执行完成后,会自动被回收(因为没有引用)

    [[若将闭包的内部函数赋给了全局变量,且不手动释放,则占用的内存将一直的不到释放,直到程序关闭]]