作用域链、预编译、闭包基础
目录
作用域链
预编译
闭包基础
为啥要学AO和GO
- 因为后续学习作用域和作用域链需要用到AO和GO的概念
AO
- function 独立放东西的仓库
- 在学习作用域和作用域链前需要讨论了解对象
函数的属性
- 函数也是一种对象类型,(引用类型、引用值)
- 函数是一种对象所以函数也有属性,例如
.name、.length、.prototype等 - 对象有些属性是无法访问的,这些属性是JS引擎内部固有的隐式属性,也可以说是天生的
一、作用域链
隐式属性[[scope]]
[[scope]]是函数创建时,生成的一个js内部的隐式属性- 这个属性是函数存储作用域链的容器
- 在函数执行完后,AO会被销毁
- AO是即时存储容器
函数的作用域链
- 只要函数被定义就会生成该函数的作用域和作用域链GO
- 每一个函数在执行时作用域链上都会有GO(全局执行上下文)和AO
- 函数被提升时该函数的作用域链上就有GO,执行时生成自己的AO
二、预编译
scope属性
- 函数的scope属性储存的是作用域链的地址
- 先保存GO再保存自己的AO从上往下存储,找外部变量时从上往下查找
- 所以作用域内可以访问作用域外的数据,而作用域外确不能访问作用域内的数据
函数作用域流程
- 全局执行前,GO内函数定义提升,var变量提升(函数字面量不提升)
- 全局执行时,解释一行,执行一行(赋值、执行函数)
- 函数被定义时,就有
[[scope]]-> 作用域链存的只有GO - 函数执行时,生成自己的GO并存进作用域链内
function a() {
function b() {
function c() {}
c();
}
b();
}
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]] -> c的AO销毁
0 : b -> AO
1 : a -> AO
2 : GO
b结束: b.[[scope]] -> b的AO被销毁
0 : a -> AO
1 : GO
a结束: a.[[scope]] -> a的AO被销毁
0 : GO
*/
为什么作用域外访问不到作用域内的变量
- 因为外部作用域链没有存作用域内的AO
三、闭包基础
- 当内部函数被返回到外部并保存时,一定会产生闭包
- 闭包会让原生的作用域链不释放
- 过度是用闭包可能会造成内存泄露,或加载过慢
- 闭包可以做数据缓存
代码示例
// 闭包
function test() {
function test2() {
var b = 2;
console.log(a);
}
var a = 1;
return test2();
}
var c = 3;
var test3 = test();
test3;
// 当test1函数被执行结束时,因为test2倍返回到外部,且被全局变量test3接收。
// 这时test1的AO并没有销毁,只是把线剪断了,test2的作用域链还连着的
闭包实例1
// 闭包实例1
function test() {
var n = 100;
function add() {
n++;
console.log(n);
}
function reduce() {
n--;
console.log(n);
}
return [add, reduce];
}
var arr = test();
arr[0](); // 101
arr[1](); // 100
闭包实例2
// 闭包案例2
function breadMgr(num) {
var breadNum = num || 10;
function supply() {
breadNum += 10;
console.log(breadNum);
}
function sale() {
breadNum--;
console.log(breadNum);
}
return [supply, sale];
}
var breadMgr = breadMgr();
bareadMgr[0](50) // 60
bareadMgr[1]() // 59
\