前言:如果有不理解的地方可以评论或私信,我会每一条都回复。建议大家看一下上一章V8引擎执行原理,里面将解释一些原理与本文相联系。(这将是一个系列)
1,在V8引擎执行过程中,js到ast树中间,js在被解析的时候创建了一个对象,叫全局对象GO
GO{
string,
data,
number,
setimeout,
intrvieout,
window
}
我们在执行
var name = ‘hello’
var age = 18
这时候会将其编译,添加到GO里面。这时候代码还未执行,执行在字节码到-cpu,cpu来执行代码那个过程。这里编译后og里会出现name:undifand,num:undifand,所以这就是我们在第一行代码前加上log(‘name’)的时候会打印undifand的原因。只因为在执行log的时候var name = ‘zlk’ 还未执行,但是已经被编译,可以找到。它在GO里,控制器打印window会找到。
补充理解:v8里面有执行上下文栈,栈里有VO对应着GO,执行代码的时候会在VO里找相应的变量,这里可以理解为在GO里找因为VO对应GO。
2,但是我们执行函数的时候就不一样了,同样js在编译阶段,将函数名编译到GO里面但是这时候电脑内存会开辟空间,将函数体和父作用域放进去去,而GO里的函数名后是这个内存的地址。(这就是为什么函数调用写前后都会被执行的原因,因为og里后面是内存地址)
之后v8在执行上下文栈中创建个函数执行上下文,里面有vo对应ao,ao里面是我们函数体里声明的变量函数等键值对,同样会进行编译,但是函数没有执行阶段,变量值为undifand,等执行过后赋值,之后函数执行上下文会被销毁,如果再次调用会再次执行相同操作。
函数执行完后 函数执行上下文FEC会被销毁 ,如果FEC被销毁了 就没有AO就没有被VO指向,所以AO也会被销毁。如果我们在代码中之后又调用了函数,此时会重新创建函数执行上下文,指向重新创建的AO
3,作用域链,v8引擎是安作用域链去查找的,先看AO里面有没有,没有去上级作用域去查找。这里函数foo的上级作用域是全局对象GO,在执行log(name) 的时候,GO里name这时候已经被赋值hello
作用域提升解析
从左数第一个
我们来判断一下log打印的值是多少,思考一下看解析
解析:
首先解析代码创建全局GO对象,里面有n:undifaned foo:开辟foo存储空间的内存地址
执行代码第一行 ,GO里n被赋值为100
执行代码到第四行的时候,执行函数foo过程为,在全局执行上下文栈中开辟函数执行上下文。
创建AO 被VO指向?no
原本要创建AO 的,但是我们函数体中没有var n = 值 var b = 值 或者函数,对象。所以我们这里没有创建AO, 执行函数体中n = 200 它先在自己的AO里找,发现没有n,再去上级作用域GO里面找,GO里面有n 并给n赋值,这时候n被改为200。执行到最后一行打印n 去当前作用域GO 里面找这时n = 200
答案:200
第二个
解析:同样创建GO foo:内存地址,n:undifaned 开辟函数存储空间 开辟函数上下文,创建ao n:undifaned
执行代码到倒数第二行GO中 n 被赋值 100 。
执行代码到最后一行 去执行函数体
执行函数体中第一行 打印n 为undifaned (AO已经创建 里面n为undifaned,不用去上层作用域查找。我们是有值的只不过值为undifaned)
函数体第二行 ao 中 n 被赋值为200
函数体第三行 打印n为200
答案:
第三个
解析:foo1中log回去本作用域找,没有后去上层作用域GO找为100, foo2中log会去本作用域AO找,为200,全局Log会在本作用域GO找为100
4,内存管理:js内存管理分堆和栈,堆是放复杂数据类型,栈放基本数据类型,堆中复杂数据类型会将指针返回值,返回到栈中变量引用。
5,js自动管理内存,创建与回收,回收机制是看被指向次数,如果为0,销毁。比如在堆中,我们有个复杂数据类型体obj2指向了另一个复杂类型体obj,obj3指向obj 。栈中声明的obj的值地址也会指向存在堆中的obj,这时堆中的obj现在数值为3,不会被销毁,如果没有其他数据指向它,它会被销毁。比如obj2 = null obj 3 = null 为retaincount:1 再将obj = null 这时retaincount:0 被回收
6,循环内存泄漏,如果有两个复杂类型相互指向,就永远不会被销毁,这就是循环内存泄露。
概念:闭包是个函数,它可以访问外部定义的自由变量
广义的角度说,js里的函数都是闭包,但这不严谨。
闭包案例
function foo() {
var name = 'zlk'
function bar() {
console.log(name);
}
return bar
}
var fn = foo()
fn()
返回的函数bar可以访问外部自由变量name这就是典型闭包,让我们看一下它内部执行流程。
流程图一(注意:两张图不一样,foo被销毁了,创建了bar函数执行上下文)
流程图二
首先创建全局执行上下文
创建GO对象编译 全局执行上下文中VO:GO
开辟foo函数存储空间
创建函数执行上下文
创建foo函数的AO对象编译
VO指向AO VO:AO
执行函数体 给AO对象中变量赋值
返回值为bar地址 所以全局执行上下文中fn为bar的地址0x00b
那么它对应的GO里的fn变量从undifaned被赋值为bar地址0x00b
函数执行完毕foo被销毁
创建bar函数执行上下文
创建bar函数的AO对象为空,里面只是一个log打印
bar函数执行,本AO里没有name变量,去上级作用域里面找,foo的AO里有name,所以被打印zlk(你会疑惑foo函数不是被销毁了嘛,为什么它的AO还在?这就是闭包的内存泄露,下面有解释)
bar函数执行完毕被销毁
function foo() {
var name = 'zlk'
var age = 18
function bar() {
console.log(name);
}
return bar
}
var fn = foo()
fn()
还是上面的典型闭包,我们来分析一下它的内存泄露,仔细看箭头指向
我们一开始就说过 开闭的函数空间中有两部分,一个是上级作用域,另一个是此函数体,由于画图比较麻烦中间没有细画
我们知道GO编译后会开辟内存空间 GO中变量foo指向函数内存空间0x00a地址。我们想一下foo函数存储空间上级作用域是什么?是全局对象GO。GO也是有内存地址的,所以他们互相指向,同理foo的AO和bar函数存储空间也互相指向。
当foo 返回一个bar的内存地址后,我们说过 全局上下文中fn 为 bar内存地址0x00b ,那么它对应的GO中 fn会被赋值为0x00b。
我们说过内存管理中,如果没有指向了,就会被销毁。当我们函数foo执行完后会被销毁。
为什么它对应的AO没有被销毁呢?因为GO中fn指向着bar的函数空间,所以bar函数空间不会被销毁,bar和foo的AO也互相指向。所以foo的AO也不会被销毁。这就是为什么闭包函数bar访问作用域链上级foo的AO中name变量会成功访问。
function foo() {
var name = 'zlk'
var age = 18
function bar() {
console.log(name);
}
return bar
}
var fn = foo()
fn()
你认为age会别销毁嘛?
我们知道foo 函数创建函数执行上下文后 VO指向AO AO编译赋值后 会有变量name:‘zlk’ 和 age:18
那么我们知道foo执行完会被销毁,但是它的AO被别人指向,无法销毁。
但是age在代码中没有被用到,我们的v8引擎会算法识别出来age,age会被销毁。
所以AO不会被销毁,但自由变量age没被用到会被销毁。