js内存机制-数据存储
- 一般基本数据类型变量存储在栈中,使用完后栈顶空间会销毁,在栈中类似线性排列的空间,每个小单元大小基本相等。
boolean number string null undefined 对象变量的指针(指向对象在堆中地址)
- 堆内存主要存储引用类型如Object,大小未知。其中闭包变量也是存储在堆内存中的,因为闭包变量不能立即销毁
- 对于‘赋值’操作,原始类型直接复制变量值,对象数据类型则复制引用地址
let obj={ a:1 }
let newObj=obj;
newObj.a=2; //修改a,因为obj和newObj都指向该地址,所以a的值都会变成2
console.log(obj.a) //2
------------
//这里我们可以使用Object.create来创建newObj
let obj={ a:1 }
let newObj=Object.create(obj);
newObj.a=2;
console.log(obj.a) //1
Object.create(obj)创建一个新变量,使用obj作为新变量的__proto__,即可以使用obj中的属性方法,但是是另开辟的一个地址,不会相互影响
拓展:const
我们知道,const声明一个只读的常量,一旦声明,值便不能修改,但是对复合数据类型可以操作对象本身。
- const实际保证的是变量指向的那个内存地址所保存的数据不得改动,即const对象对应的堆内存指向是不变的,但是堆内存中数据本身大小、属性是可变的。这就是为什么const不能重新赋值,但是可以在复合类型数据上进行操作
垃圾回收机制
为什么不用栈存储对象?
我们刚刚看到,堆存储对象有一定弊端,对象复制会引用同一块地址,而栈则不会出现这种情况,可能我们会问:为什么不用栈保存呢?
- 对于系统栈,除了保存变量外,还有创建并切换上下文的功能。如果存储复杂类型,开销后特别大
- 堆内存空间大,可以存放大量数据,但是没有像栈一样切换上下文后自动回收空间。于是堆内存垃圾回收就比较复杂
V8内存限制
与其他后端语言不通,V8只能使用系统一部分内存,为什么V8要给它设置内存上限呢?
- JS单线程的运行机制:这意味着一旦进入垃圾回收,其他运行逻辑都要暂停。而垃圾回收是非常耗时的,
以 1.5GB 的垃圾回收堆内存为例,V8 做一次小的垃圾回收需要50ms 以上,做一次非增量式(ps:后面会解释)的垃圾回收甚至要 1s 以上。
在这么长时间内,js代码没有响应回造成应用卡顿,应用性能和响应能力都会直线下降
内存回收
V8把堆内存分成两部分–新生代内存和老生代内存
- 新生代:临时分配,存活时间短
- 老生代:常驻内存:存活时间长
V8执行一段js代码过程
学过编译原理就知道,机器是读不懂JS代码的,只能将JS转为特定的机器码才能让机器识别;JS属于解释型语言,会进行如下分析
- 生成AST语法树
- 生成字节码
1.生成AST
生成AST需要两步–词法分析、语法分析
- 词法分析:就是分词,扫描程序把源程序变为单词序列.其中把一个句子分成四个部分
- 语法分析:把词法分析生成的单词,根据一定规则自顶向下生成AST语法树
- 生成AST后解释器后续工作依靠AST。babel的工作原理就是将ES6的代码解析成ASt,然后将AST转为ES5的AST,最后通过AST转为具体的ES5代码
2.生成字节码
通过V8的解释器对AST生成字节码
字节码是介于AST和机器码之间的一种代码,与特定的机器码无关,需要通过解释器将其转换为机器码然后执行
既然字节码不能让机器识别,为什么要生成字节码呢?
- 字节码是比机器码轻量得多的代码,V8早起是直接把AST转为机器码,但是机器码体积太大,引发严重的内存占用问题。
- 可以通过解释器逐行执行字节码,降低内存压力。
3.执行代码
执行字节码过程中,如果某一部分代码重复出现(记做热点代码),就会把这些代码编译成机器码保存起来。此时用到了编译器
js不是完全的解释型语言,因为字节码使用了解释器和编译器,两者的根本区别在于编译器会编译生成二进制文件,两者结合即为即时编译