堆/栈/ECStack/EC/GO/VO/AO的理解
heap(堆)
在js中,堆存储引用数据类型(对象,函数代码字符串),返回十六进制地址值.
栈(Stack)
在js中,栈
- 执行js
- 存储基本数据类型的值.
ECStack
ECStack(Execution Context Stack): js执行环境栈 => js想要执行代码,就一定会创建一个执行栈(栈内存).
EC - 执行上下文
EC(Execution Context): 执行上下文 => 某个域下的代码执行都有自己的执行上下文.
- 全局执行上下文 EC(G) golbol
- 某个函数执行上下文 EC(Function) 函数执行上下文
把创建的上下文(EC(...))压缩到栈中执行 => 进栈
- 执行完有的上下文就没有用了 => 出栈
- 有的还有用,会把当前上下文压到栈底 => 形成闭包
GO - 全局对象
GO(golbal object): 全局对象-- 在浏览器端,会把全局对象赋值给window
GO:{
setTimeout: fn,
Math: {},
Date:{},
...
}
VO - 变量对象
VO(Variable Object): 全局变量对象 => VO不仅包含了全局对象的原有属性,还包括了在全局定义的变量与函数.
VO(G):{
window: GO(G),
全局定义的变量
全局定义的函数
}
AO - 活动变量对象
AO(activation object): 只有函数执行时,会创建AO. AO相当于是VO的分支. AO中包含了
- 函数的实参(arguments)集合对象
- this对象
- 函数的形参
- 局部函数的定义
- 内部函数的定义
作用域与作用域链
作用域
- 1.作用域就是代码的执行环境,全局执行环境就是全局作用域,函数的执行环境就是私有作用域,他们都是栈内存.
- 全局作用域: 在全局代码执行的时候,会形成全局作用域.
- 私有作用域: 在函数创建的时候,会形成私有作用域.
- 2.在Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建.(ES6用let和const创建的变量,不挂载到window上)
- 3.在Node环境中,全局执行环境就是global.
函数创建和执行分别做了哪些事
函数创建做了哪些事
- 1.创建了一个堆内存对着(堆中存放着函数代码块和函数的键值对)
- 代码字符串
alert('...')...... - 键值对
name: '函数名', length: 形参的个数
- 代码字符串
- 2.初始化当前函数的作用域
- [[Scope]] = 所在上下文EC中的变量对象VO/AO
- 函数的作用域是谁,和他在哪执行没有关系,和他在哪(定义)创建有关系,在哪创建的,它的上级作用域就是谁
- 函数在全局创建的 [[Scope]] == (VO(G))
- 函数在函数(FN)中创建的 [[Scope]] == (AO(当前函数),AO(FN)) AO(FN)就是他的上级作用域
function FN(){ return function f(){ alert('...') } } // FN的作用域就是 全局(,FN(VO-FN),VO(G)) // f的作用域就是 (f(VO-f),FN(VO-FN))
函数执行做了哪些事
- a.函数执行,会把存储的函数代码块压到ECStack(代码执行环境)栈中
- b.创建一个新的执行上下文EC (每一个函数执行都会形成一个全新的执行上下文)
- 初始化this执行
- 初始化作用域链[[Scope Chain]]
- 创建AO(活动变量对象)
- 1.初始化实参集合(arguments)
- 严格模式下(实参集合和形参没有映射关系)
- 非严格模式下(有映射关系)
- 实参和形参如果length不相同,相对应的映射(位置),不对应的不映射
- 实参和形参如果length相同,一一映射
- 2.声明形参变量
- 3.变量提升 (在function里,用var,let等定义的变量是局部变量;不用var,let定义的变量为隐式全局变量)
- 4代码执行
- 1.初始化实参集合(arguments)
作用域链
我们在局部作用域中,访问一个变量时,系统首先会在当前作用域中寻找(变量声明or函数形参声明),如果找到则使用. 如果没有找到,则继续向上级作用域中查找(变量声明or函数形参声明),如果找到,就使用. 如果没有找到,则继续上级作用域中查找,找到就使用,没有找到就继续向上级作用域查找,一直找到全局作用域.这就是作用域链机制.
let x = 5;
function fn(){
var y = 1
return function f(z){
// 这里z,z是形参变量,属于私有的, z = 10
// 这里y,不属于私有的,向上级作用域查找,上级作用域为FN,找到了y,y=1
/*
这里x,不属于私有的,向上级作用域查找,
上级作用域为FN,没有找到.
继续向上查找,FN的上级作用域为VO(G).
在VO(G)中找到了x=5
*/
// 10+1+5 = 16
console.log(z+y+x)
}
}
fn()(10)
js引擎工作原理简单流程
var a = 10;
function Fn(b){
var a = 20;
function f(c){
console.log(a+b+c)
}
return f;
}
var f = Fn(10);
f(20)
全局初始化
创建ECStack/EC
JS代码执行时,js引擎会创建js代码执行环境栈(ECStack),与此同时创建一个全局的执行上希望(EC(G)),并将这个全局执行上下文(EC(G))压入执行环境栈中.
- 执行环境栈(ECStack)的作用就是为了给js提供正确的顺序的执行环境.
在javascript中,每个函数都有自己的执行上下文(EC(函数)),当函数执行时,就会把压到执行环境栈(ECStack)的栈顶并获取执行.当这个函数执行完毕后:
- 如果函数内部创建的变量(指return一个函数的地址值)没有被外部所引用,这个函数就会被销毁,把执行权交给原来的执行环境.
- 如果函数内部创建的变量(指return一个函数的地址值)被外部所引用,这个函数就会被压到栈底(形成闭包),并且把执行权交给原来的执行环境.
var ECStack = []; // 创建执行环境栈 ECStack
var EC = {}; // 创建执行上下文(EC),执行空间
ECStack.push(EC) // 入栈
ECStack.pop(EC) // 出栈
创建全局对象GO(Global Object)
创建一个全局对象(GO Object),这个对象全局只存在一份,它的属性在任何地方都可以访问,它的存在伴随着应用程序的整个生命周期. 如Math,Date,setTimeout等常用的js对象作为它的属性. 在Web浏览器端,全局对象会多一个window属性,并将window指向了自身.这样就可以通过window访问这个全局对象了
// 创建全局对象
var GO = {
Date: {},
Math: {},
setTimeout: fn,
setInterval: fn
}
创建VO
js引擎还会创建一个全局变量对象(Varibale Object) VO,并把VO指向全局对象, VO中不仅仅包含了全局对象的原有属性,还包括了全局定义的变量a和函数Fn.
- Fn在创建函数的时候,会创建一个堆内存地址AAAFFF000,存储函数的代码字符串与函数的对象键值对({name:'Fn',length:1}),并把这个堆内存地址AAAFFF000赋值给Fn.
- 以此同时,还会给函数FN创建一个内部属性(作用域[[Scope]]),并把这个**[[Scope]]**指向了当前创建的执行上下文EC(G)下的VO. Fn[[Scope]]==VO(G)
// 此时ECStack结构
ECStack = [ // 全局执行上下文(ECStack)
EC(G) : { // 全局执行上下文(EC(G))
VO(G): { // 定义全局变量对象(VO)
...全局变量对象原有属性
变量提升: Fn = AAAFFF000 // 定义函数
Fn[[Scope]] = VO; 定义Fn[[Scope]],并赋值为VO(G)本身
var a;
a = 1; // 定义变量a
f = Fn()执行后返回的结果,
}
}
]
执行函数Fn
当执行Fn(10)时,js会
创建EC
- js引擎会创建函数A的执行上下文EC(Fn),把创建的EC(Fn)压入执行环境栈(ECStack)顶.
- 初始化this指向,此时Fn中的this->window
- 初始化作用域链[[ScopeChain]]=> (AO(ECFn),VO(G));
- 创建一个当前函数的活动变量对象AO(Activatiob Object),
- AO中包含了函数的实参对象,
- 形参变量,
- this对象,
- 以及局部变量和
- 内部的函数定义
- 代码执行
返回一个堆内存地址给变量f = AAAFFF111;
ECStack:[
EC(Fn):{
// 初始化this执行 -> window
this: window,
// 初始化作用域链
[[ScopeChain]]: (EC(Fn),VO(G)),
// 创建AO
AO:{
初始化实参集合: arguments: { 0:10 },
形参定义: b = 10
变量提升 f = AAAFFF222(函数在变量提升的阶段声明加定义)
f[[Scope]] = AO(Fn)
var a;
代码执行 a = 20;
return AAAFFF222;
}
}
EC(G): {
...GO(global Object )
a = 1;
Fn = AAAFFF000,
Fn[[Scope]] = VO;
f = AAAFFF111;
f(); // 紧接着f();
},
]
执行函数f
执行函数f相当于执行Fn返回的堆内存函数.
执行函数f,因为函数f的地址值在A中创建的,而这个地址值(AAAFFF222)被f占用,所以不能销毁,形成了一个暂时不销毁的私有作用域,这种机制叫闭包.
创建EC(f)
创建函数f执行上下文(EC(f)),并把EC(f)压入到执行环境栈ECStack的栈顶.步骤和Fn函数执行一样
- 初始化this执行; this->window
- 初始化函数的作用域链 [[ScopeChain]] = (EC(f),VO(Fn))
- 创建VO
- 初始化实参结合arguments: { 0: 20 }
- 形参变量定义 c=20;
- 变量提升(没有)
- 代码执行 console.log(a+b+c)
ECStack:[
EC(f):{
// 初始化this执行 -> window
this: window,
// 初始化作用域链
[[ScopeChain]]: (EC(f),VO(Fn)),
// 创建AO
AO:{
初始化实参集合: arguments: { 0:20 },
形参定义: c = 10,
变量提升: 没有,
代码执行 console.log(a+b+c) => 50
}
},
EC(Fn):{ // 此时EC(FN)中创建的AAAFFF222被外面所占用,不能销毁.
// 初始化this执行 -> window
this: window,
// 初始化作用域链
[[ScopeChain]]: (EC(A),VO(G)),
// 创建AO
AO:{
初始化实参集合: arguments: { 0:10 },
形参定义: b = 10
变量提升 f = AAAFFF222(函数在变量提升的阶段声明加定义)
f[[Scope]] = AO(Fn)
var a;
代码执行 a = 20;
return AAAFFF222;
}
},
EC(G): {
...GO(global Object )
a = 1;
Fn = AAAFFF000,
Fn[[Scope]] = VO;
f = AAAFFF111;
f(); // 紧接着f();
},
]
作用域链查找机制:
我们在局部作用域访问一个变量时,系统首先会在局部作用域中查找,如果找到则使用,停止查找;如果没有找到,则会向上级作用域查找,如果找到则使用,停止查找;如果没有找到,继续向上级作用域查找,如果一直没有找到,继续向上,知道windon为止.
执行代码console.log(a+b+c);
根据作用域链查找机制
- 从当前作用域找变量a或者形参变量b,a不是私有的,向EC(f)上级作用域VO(Fn)中找,VO(Fn)中有a,所以 a=>20;
- 从当前作用域找变量b或者形参变量b,b不是私有的,向EC(f)上级作用域VO(Fn)中找,VO(Fn)中有形参变量b,所以 b=> 10;
- 从当前作用域找变量c或者形参变量c,c是私有的,停止查找,c=>20
所以最后的执行结果为 50
总结与后语
多多沉淀技术,吸取经验.
不要吹灭你的灵感和你的想象力; 不要成为你的模型的奴隶。
欢迎大佬随时指点与批评.谢谢.