编译器(把代码解析成为浏览器看的懂的结构)
- 词法解析
- AST抽象语法树
- 构建出浏览器能够执行的代码
webpack babel
引擎(V8 / weblit内核)
- 变量提升
- 作用域 / 闭包
- 变量内存
- 堆栈内存
- GO/VO/AO/EC/ECStack
- …
GO/VO/AO/EC及作用域和执行上下文
- GO:全局对象(Global Object)
- ECStack:Execution [ˌeksɪˈkjuːʃn] Context Stack 执行环境栈
- EC:Execution Context 执行环境(执行上下文)
-
VO:Varibale Object 变量对象
-
AO:Activation Object 活动对象 (函数的叫做AO,理解为VO的一个分支)
-
- Scope:作用域,创建的函数的时候就赋予的
- Scope Chain :作用域链
a = null 空对象指针
让一个变量指向一个空的指针,让它达到对原有被占用被指针指向的占用内存的释放跟回收
—————————————————————————————————
0、undefined、null的区别
在引用类型值中 把一个变量赋值为0 也可以达到销毁的功能 但是会在栈内存中创建一个值为0占用内存
null (意料之中) 不会占用内存 想赋值但是还没有赋值(可以销毁内存)
- let a= null
- let b= null
undefined (意料之外) 声明了一个变量可以赋也可以不赋
—————————————————————————————————
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); //=>创建函数的时候就确定了其作用域
}
}
];
//js代码执行首先会形成一个执行环境栈ECStack 然后形成一个全局的执行上下文EC(G) 紧接着会形成一个全局的变量对象VO(G) VO(G)里面主要存储全局下变量、对象和函数 函数和对象以一个引用类型值存储 遇到要执行的函数 就会把全局变量对象VO(G)放到执行环境栈最下面 最后再执行这个VO(G)
// 并且形成一个新的EC(执行上下文) 又会在这个EC中形成一个新的AO([活动对象],函数的叫做AO,理解为VO的一个分支) 用来存储该EC(执行上下文)中的变量、对象和函数 并且还会形成一个[Scope](作用域,创建的函数的时候就赋予的)和一个<Scope Chain>(作用域链)
// 闭包:保护和保存
// 函数创建的时候:
// 创建了一个堆(存储代码字符串和对应的键值对)
// 初始化当前函数的作用域
// [[scope]] = 所在上下文EC中的变量对象VO/AO
// 函数执行的时候:
// 创建一个新的执行上下文EC(压缩到栈ECStack里执行)
// 初始化this的指向
// 初始化作用域链[[scopeChain]]
// 创建AO变量对象用来存储变量
// arguments ==>形参 ==>代码执行
// ==>存储键值对 name:"函数名" length:形参的个数 prototype:...原型
// 创建变量 三步
// 创建变量名(declare) 创建值 把变量值和值关联在一起也就是赋值(defined) let a = 1
// 创建函数 三步
// 开辟一个堆内存的空间地址;16进制的字符 把函数体中代码当作字符串方式存储起来 把空间地址复制给函数名
浏览器的垃圾回收机制(自己内部处理)
[谷歌等浏览器是“基于引用查找”来进行垃圾回收的]
- 开辟的堆内存,浏览器自己默认会在空闲的时候,查找所有内存的引用,把那些不被引用的内存释放掉
- 开辟的栈内存(上下文) 一般在代码执行完都会出栈释放,如果遇到.上下文中的东西被外部占用,则不会释放
[IE等浏览器是“基于计数器”机制来进行内存管理的]
- 创建的内存被引用一次,则计数1 ,在被引用一次,计数2…移除引用减去1… 当减为零的时候,浏览器会把内存释放掉=>真实项目中,某些情况导致计数规则会出现一些问题,造成很多内存不能被释放掉,产生”内存泄漏”; 查找引用的方式如果形成相互引用,也会导致“内存泄漏”
闭包
函数执行会形成全新的私有上下文,这个上下文可能被释放,也可能不被释放,不论是否被释放,它的作用是:
- 保护:划分一个独立的代码执行区域,在这个区域中有自己私有变量存储的空间,而用到的私有变量和其它区域中的变量不会有任何的冲突(防止全局变量污染)
- 保存:如果上下文不被销毁,那么存储的私有变量的值也不会被销毁,可以被其下级上下文中调取使用
我们把函数执行,形成私有上下文,来保存和保护私有变量的机制,称之为“闭包”=>它是一种机制
市面上一般认为只有形成的私有上下文不被释放,才算是闭包(因为如果一但释放,之前的东西也就不存在了) ;
还有人认为,只有一下级上下文用到了此上下文中的动西才算闭包;