📌 作用域

作用域 [[ scope ]] 决定了代码区块中变量和其他资源的可见性

  • JavaScript函数都是一个对象,其中 [[ scope ]] 不可访问,仅供JavaScript引擎存取属性之一
  • [[ scope ]]即作用域,其存储了运行期上下文集合

作用域分类

  • 全局作用域 直接在script标签(单独JS文件)中的js代码,页面打开创建,关闭销毁
  • 局部作用域 直只能在固定代码片段内可访问到,外部是无法访问的
    • 函数作用域 **:只能在函数内部可访问,函数调用时创建,执行完毕销毁,调用一次创建一次,相互独立**
    • 块级作用域 **:花括号内部声明了const或let变量,只能在花括号内部访问**


📌 作用域链

作用域链 (Scope Chain) [[ scope ]]中存储的执行期上下文对象的集合,该集合呈链式即作用域链

  1. function a(){
  2. function b(){
  3. var b = 2;
  4. }
  5. var a = 1;
  6. }
  7. var c = 3;
  8. a()
  • 当a函数被定义时
    • 系统生成[[ scope ]]属性,其保存a函数的作用域链
    • 该作用域链第0位,存储当前环境下的全局执行期上下文GO
    • 全局执行期上下文GO存储全局下的所有对象
  • 当a函数被执行时(前一刻)
    • 作用域链第0位,存储a函数生成的执行期上下文AO
    • 同时,该作用域链第0位的全局执行期上下文GO被挤至第1位
    • 查找变量是从该作用域链顶端依次向下查找
  • 当b函数被定义时
    • 因b函数在a函数内部,所以此时执行期上下文与a函数执行时(上述)时一样的
  • **当b函数被执行时(前一刻)
    • 生成b函数的[[ scope ]]属性,其保存b函数的作用域链
    • 作用域链第0位,存储b函数的执行期上下文AO
    • 同时,a函数的AO和全局的GO被挤依次向下排列
  • b函数执行结束
    • b函数的AO被销毁,作用域链回归其被定义时的状态
    • a函数的AO升至作用域链第0位,全局的GO至第1位
  • a函数执行结束
    • a函数的AO被销毁,回归其被定义时的状态
    • 全局GO升至作用域链第0位
    • 因其内部存储有b函数的值,所以此时b函数的 [[ scope ]]将不存在

小试牛刀——分析作用域链 当外部环境执行,其内部函数才会被定义

  1. function a(){
  2. function b(){
  3. function c(){
  4. }
  5. c();
  6. }
  7. b();
  8. }
  9. a();
  10. 【作用域链分析】:
  11. 页面刷新预解析定义a函数
  12. a定义:a.[[scope]] ——> 0: GO;
  13. a执行:a.[[scope]] ——> 0: a——>AO;
  14. 1: GO;
  15. b定义:b.[[scope]] ——> 0: a——>AO;
  16. 1: GO;
  17. b执行:b.[[scope]] ——> 0: b——>AO;
  18. 1: a——>AO;
  19. 2: GO;
  20. c定义:c.[[scope]] ——> 0: b——>AO;
  21. 1: a——>AO;
  22. 2: GO;
  23. c执行:c.[[scope]] ——> 0: c——>AO;
  24. 1: b——>AO;
  25. 2: a——>AO;
  26. 3: GO;
  27. c结束:c.[[scope]] ——> 0: b——>AO;
  28. 1: a——>AO;
  29. 2: GO;
  30. b结束:b.[[scope]] ——> 0: a——>AO;
  31. 1: GO;
  32. c.[[scope]] 销毁;
  33. a结束:a.[[scope]] ——> 0: GO;
  34. b.[[scope]] 销毁;

📌 闭包基础

闭包

  • 当内部函数被返回到外部并保存时,一定会产生闭包
  • 闭包会产生原来的作用域链不释放,过渡闭包可能会导致内存泄漏或加载过慢

    1. function test1(){
    2. function test2(){
    3. var b = 2;
    4. console.log(a)
    5. }
    6. var a = 1;
    7. return test2;
    8. }
    9. var c = 3;
    10. var test3 = test1();
    11. test3()
  • 当test1函数被定义时

    • 系统生成test1的[[scope]]属性,其保存test1函数作用域链
    • 该作用域链第0位,存储当前环境下的全局执行期上下文GO
    • 全局执行期上下文GO存储全局下的所有对象
  • 当test1函数被执行时,其内部test2函数被定义
    • 作用域链第0位,存储test1执行期上下文AO,全局GO挤至第1位
    • test1执行期上下文AO存储期内部所有对象
    • 同时,系统生成test2的[[scope]]属性,其保存test2函数作用域链
    • 因test2函数在test1函数内部,此时test2函数作用域链与test1相同都指向test1的执行期上下文AO
  • 当test1函数被结束时,test2被return抛出,被全局变量test3接收
    • 此时,test1函数的作用域链指向test1执行期上下文AO被断开
    • 因test2函数作用域链同时也指向test1执行期上下文AO,所以test1的AO并不会被销毁
  • 当test3函数被执行时,实际是接收的test2被执行
    • 作用域链第0位,存储test2执行期上下文AO,test1的AO和全局GO依次向下排列
    • 当打印a变量时,自己的AO上没有,沿作用域链向下找到test1的AO中a=1
  • 当test3函数执行结束,实际是接收的test2执行结束
    • test2函数的AO被销毁,但其指向的test1执行期上下文AO仍存在

技巧:**闭包找到的是同一地址中父级函数中对应变量最终的值 **