引题
var x = [12, 23]
function fn(y) {
y[0] = 100;
y = [100]
y[1] = [200]
}
fn(x)
console.log(x)
执行过程
1、ECStack 、GO、EC、VO、AO
浏览器会在计算机内存中分配一块内存,专门用来供代码执行的栈内存,称作执行环境栈(ECStack) 同时会创建一个全局对象(GO)
将内置的属性方法( isNaN
、setInterval
、setTimeout
… )存放到一块单独的堆内存空间,并且使用 window 指向全局对象
在执行代码前,还需要创建一个全局执行上下文( EC(G) ), 创建完成后,进入到栈内存中去执行( 进栈 ); 在当前全局执行上下文中,因为会创建很多变量并且赋值,所以会创建一个变量对象 VO(Variable Object) 来进行保存,在函数私有上下文中的变量对象叫做 活动对象 AO(Activation Object) (ps: 每个执行上下文都有一个单独的变量对象)
总结下这些陌生不常见的名词
- 执行环境栈ECStack ( Execution Context Stack ):专门用来供代码执行的栈内存
- 全局对象GO(Global Object):存放内置的属性方法,window 指向
- 全局执行上下文EC(G) ( Execution Context(G) ):页面加载后进栈、销毁后出栈
- 变量对象VO (Variable Object):存放当前执行上下文中创建的变量和值
- 活动对象AO (Activation Object):函数私有上下文中的变量对象
2、全局代码执行
当一切准备就绪,就开始从上到下执行代码 (ps: 执行前还会涉及变量提升的问题,这里不进行展开)
var x = [12, 23]
复制代码
js 在解析这段代码时,会按照以下 3 个步骤
- 先创建一个值
[12, 23]
- 再创建一个变量
x
- 最后将变量与值进行关联
当创建的值是引用类型时,会在堆内存中开辟新的内存空间用了保存值,创建完成后,会将堆内存地址(通常是 16 进制 )保存到栈内存中; 如果创建的值是基本类型时,会直接保存到栈内存中
继续向下执行,
function fn(y) {
y[0] = 100;
y = [100]
y[1] = [200]
}
复制代码
函数也是属于引用类型,也需要开辟堆内存空间进行保存,不同于数组和对象保存的是键值对,JS会将函数体通过字符串包裹进行保存,同时也会保存函数相关的属性,例如 函数名 name: fn、形参个数length: 1 等。同时,更重要的是,创建函数的时候,就定义了函数的作用域,也就是 等于当前创建函数的执行上下文。在这个例子中,函数fn 的作用域就是全局执行上下文,标识为 [[scope]]:EC(G)
在函数创建好后,继续向下执行
fn(x)
复制代码
通过上面的动图了解到,fn 和 x 都指向堆内存地址,所以,fn(x) 相当于 AAAFFF000(AAAFFF111) , 在执行函数体代码之前,我们需要知道的是:
- 每次函数执行,都会创建一个函数私有执行上下文,创建完之后,需要进入到栈内存中去执行,此时,执行栈中的全局执行上下文就会被压入到栈底(压栈)
- 同时,需要创建一个活动对象 AO 存放当前函数执行上下文中创建的变量和值等
再完成函数执行上下文入栈后,接下来会做以下几件事
- 初始化作用域链 scopeChain: 作用域链通常标记为 <当前执行上下文, 函数创建时的作用域> ,而作用域链是为了函数执行过程中,当活动对象中不存在某个变量时,会沿着作用域链向上找到
- 初始化 this 指向 : 本例子中,this 等于 window
- 初始化实参集合 arguments
- 形参赋值 y = x = AAAFFF111
- 执行函数体 紧接着就是执行函数体内容,在执行完成后,当前函数的执行上下文就会出栈,退出执行栈,而被压入栈底的全局执行上下文又被推到了栈顶,此时会继续执行全局上下文中的代码
至此,代码执行结束,最终输出的 x 是 [100, 23]
。
二、浏览器常用的垃圾回收机制(内存释放机制):
1、 查找引用方式(weblit内核)
浏览器有自动回收垃圾的机制,定期间隔某段时间,把所有没有被占用的内存回收释放(这种垃圾回收机制,比其它语言要完善一些)
-1).不会销毁的情况:
- 创建一个堆(16进制地址):
- 如果有变量或者其它东西存储了堆内存的地址,则当前堆内存被视为占用,也就不能释放销毁
- 上下文进栈执行;
- 如果当前上下文中的某些内容(一般也是当前上下文中创建的堆)被上下文以外的变量或者其它事务所占用,那么当前上下文就不能出栈释放(但是一般情况下,上下文中代码执行完,上下文自己就出栈释放了)
-2).堆内存释放
如果堆内存用完后,我们想去手动释放它,则取消所有的占用:赋值为NULL(NULL是空对象指针,也就是不指向任何的堆内存)
//=>创建一个引用类型值,就会产生一个堆内存
//如果当前创建的堆内存不被其它东西所占用了(浏览器会在空闲的时候,查找每一个内存的引用状况,不被占用的都会给回收释放掉),则会释放
let obj = {
name : 'xiaozhima'
};
let oop = obj;
//此时obj和oop都占用着对象的堆内存,想要释放堆内存,需要手动解除变量和值的关联(null:空对象指针)
obj = null;
oop = null;
-3).栈内存释放
栈内存:
- 打开浏览器形成的全局作用域是栈内存
- 手动执行函数形成的私有作用域是栈内存
- 基于ES6中的let/const形成的块作用域也是栈内存
- ……
栈内存销毁:
- 全局栈内存:关掉页面的时候才会销毁
- 私有栈内存:
- 1.一般情况下,函数只要执行完成,形成的私有栈内存就会被销毁释放掉(排除出现无限极递归、出现死循环的模式)
- 2.但是一旦栈内存中的某个东西(一般都是堆地址)被私有作用域以外的事物给占用了,则当前私有栈内存不能立即被释放销毁(特点:私有作用域中的私有变量等信息也保留下来了=>这种函数执行形成不能被释放的私有栈内存,也叫做闭包)
function fn(){
//...
}
fn(); //=>函数执行形成栈内存,执行完成栈内存销毁
function X(){
return function(){
//...
}
}
let f=X(); //=>f占用了X执行形成的栈内存中的一个东西(返回小函数对应的堆),则X执行形成的栈内存不能被释放了
2、内存计数器方式(Trident内核)
当前内存被其它东西引用了,则给堆计数1(累加计数),取消占用后,则减1,当减到零之后,浏览器就可以把它释放了
浏览器渲染机制
渲染器进程 进入主线程后 接收的是HTML文件 开始DOM结构渲染
html 中会引入 css 图片等资源 这资源不会阻塞html的渲染 但遇到script标签会停止渲染去执行加载JS 因为script可以会修改Dom结构