📌 作用域
作用域 [[ 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仍存在
技巧:**闭包找到的是同一地址中父级函数中对应变量最终的值 **