作用域/作用域链/预编译/闭包基础

107 阅读4分钟

📌 作用域

作用域 [[ scope ]]

决定了代码区块中变量和其他资源的可见性

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

作用域分类

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

📌 作用域链

作用域链 (Scope Chain)

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

function a(){
  function b(){
    var b = 2;
  }
  var a = 1;
}
var c = 3;

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 ]]将不存在

小试牛刀——分析作用域链

当外部环境执行,其内部函数才会被定义

function a(){
  function b(){
    function c(){
    }
    c();
  }
  b();
}
a();

【作用域链分析】:
页面刷新预解析定义a函数

a定义:a.[[scope]] ——> 0: GO;

a执行:a.[[scope]] ——> 0: a——>AO;							
1: GO;

b定义:b.[[scope]] ——> 0: a——>AO;                                                        
1: GO;

b执行:b.[[scope]] ——> 0: b——>AO;
1: a——>AO;
2: GO;

c定义:c.[[scope]] ——> 0: b——>AO;
1: a——>AO;
2: GO;

c执行:c.[[scope]] ——> 0: c——>AO;
1: b——>AO;
2: a——>AO;
3: GO;

c结束:c.[[scope]] ——> 0: b——>AO;
1: a——>AO;
2: GO;

b结束:b.[[scope]] ——> 0: a——>AO;
1: GO;
c.[[scope]] 销毁;

a结束:a.[[scope]] ——> 0: GO;
b.[[scope]] 销毁;
      

📌 闭包基础

闭包

  • 当内部函数被返回到外部并保存时,一定会产生闭包
  • 闭包会产生原来的作用域链不释放,过渡闭包可能会导致内存泄漏或加载过慢
function test1(){
	function test2(){
  	var b = 2;
    console.log(a)
  }
  var a = 1;
  return test2;
}
var c = 3;
var test3 = test1();
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仍存在

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