开篇导言
做了好几年程序了,一直想这写点博客笔记什么的,一直拖拖拉拉好久没开始,最近越来越觉得有必要把以前的只是梳理一遍,给自己整理点笔记,巩固一下基础知识,仅限于小白自我学习,哈哈。
之所以浏览器堆栈内存作为我的第一篇笔记,是因为最初学习js时,有很多不知其所以然的疑惑,例如闭包产生的原理,一直没找到合理的解释,后来从浏览器运行机制中找到合理的解释,也算是解开了我这个小白一直不懂的心结。(下面笔记只以webkit的chrome浏览器容器端为参照);由于浏览器内存机制会将不被占用的空间在空闲时做销毁处理=> 出栈,(即进栈执行代码,出栈销毁代码,释放内存),下面来屡屡浏览器是怎样执行我们的代码的吧:
-
编译器处理
浏览器拿到我们的资源后,会做一个编译处理(解析成浏览器看得懂的语言,生成一个AST抽象语法书)
-
引擎处理(v8/webkit内核)
ECS(Execution context stack)执行环境栈 EC(Execution context) 执行上下文(G=>global) VO:Varibale Object 变量对象 AO:Activation Object 活动对象 (函数的叫做AO,理解为VO的一个分支) Scope:作用域,创建的函数的时候就赋予的 Scope Chain :作用域链执行我们js代码前,浏览器会做一系列的准备才能执行我们的js代码:
-
引擎创建了执行上下文环境栈 ECS(Execution context stack)来管理执行上下文(执行代码,存储基本类型值)=> 栈内存
-
创建一个全局的执行环境上下文EC(G),压入ECS中(进栈)来执行js代码,(因为是全局EC,所以不会出栈销毁,除非浏览器窗口关闭,哈哈),执行完压缩到整个栈内存的底部
-
创建一个全局对VO(G)也叫(global Object),才去执行我们的js代码,申明、创建、赋值,并将VO(G)赋值给window;
3.1 其中基本类型的定义是保存的栈内存中,引用数据类型是保存的堆内存中提供一个十六进制的地址出来,通过赋值操作让申明和创建的值联系起来,建立指针;
3.2 定义并赋值函数(A = function(y){...};)会产生这个函数的作用域即Scope,作用域的范围取决于其定义创建的位置;例如在VO(G)中定义的函数,其作用域A[[scope]] = VO(G);函数也是保存在堆内存中,以字符串形式保存保存函数内的代码,并提供十六进制地址出来,同上;
var和函数存在变量提升,也可以在定义函数之前就执行该函数;3.3 执行函数A(); 创建一个新的执行环境上下文EC(a),进栈执行,初始化this指向(后面做个专题总结吧),初始化作用域链【Scope Chain】,创建A0变量对象用来存储变量 执行函数内部代码;
-
-
闭包的产生
由上面的介绍,能理解到函数在定义的时候就确定了作用域,所以,如果是在函数内部定义的函数,其作用域很明显就是定义函数的地方了;作用域链在执行这个函数的时候 也就确定了;如果再将这个内部定义的函数return,并声明一个变量去接收=> 则这个函数会被外界变量占用,所以这个函数所在的执行栈不能被销毁,产生了一个不被销毁的栈,这才是闭包产生的根本原因;这也能看到闭包的两大作用:1.保存了该执行栈中的变量不会被销毁;2.保护了该执行栈中的变量也不会污染EC(G)中的变量;闭包的思想为js的高阶编程提供了很好的思路,最初的模块化思想也是基于这样来的。
-
看代码实例:
let x = 1;
function A(y){
let x = 2;
function B(z){
console.log(x+y+z);
}
return B;
}
let C = A(2);
C(3);
/*第一步:创建全局执行上下文,并将其压入ECStack中*/
ECStack = [
//=>全局执行上下文
EC(G) = {
//=>全局变量对象
VO(G):{
... //=>包含全局对象原有的属性
x = 1;
A = function(y){...};
A[[scope]] = VO(G); //=>创建函数的时候就确定了其作用域
}
}
];
/*第二步:执行函数A(2)*/
ECStack = [
//=>A的执行上下文
EC(A) = {
//=>链表初始化为:AO(A)->VO(G)
[scope]:VO(G)
scopeChain:<AO(A),A[[scope]]>
//=>创建函数A的活动对象
AO(A) : {
arguments:[0:2],
y:2,
x:2,
B:function(z){...},
B[[scope]] = AO(A);
this:window;
}
},
//=>全局执行上下文
EC(G) = {
//=>全局变量对象
VO(G):{
... //=>包含全局对象原有的属性
x = 1;
A = function(y){...};
A[[scope]] = VO(G); //=>创建函数的时候就确定了其作用域
}
}
];
/*第三步:执行B/C函数 C(3)*/
ECStack = [
//=>B的执行上下文
EC(B){
[scope]:AO(A)
scopeChain:<AO(B),AO(A),B[[scope]]
//=>创建函数B的活动对象
AO(B):{
arguments:[0:3],
z:3,
this:window;
}
},
//=>A的执行上下文
EC(A) = {
//=>链表初始化为:AO(A)->VO(G)
[scope]:VO(G)
scopeChain:<AO(A),A[[scope]]>
//=>创建函数A的活动对象
AO(A) : {
arguments:[0:2],
y:2,
x:2,
B:function(z){...},
B[[scope]] = AO(A);
this:window;
}
},
//=>全局执行上下文
EC(G) = {
//=>全局变量对象
VO(G):{
... //=>包含全局对象原有的属性
x = 1;
A = function(y){...};
A[[scope]] = VO(G); //=>创建函数的时候就确定了其作用域
}
}
];