一文带你搞懂原始类型和引用类型(复杂类型),V8的内存机制,以及==和===的“爱恨情仇”。
本文将从类型分类讲起,带你走进V8的调用栈和堆调用,最后彻底搞清 == 和 === 的区别。读完这篇文章,你在写代码时将会更加有底。
一.JavaScript的两大类类型
JS中的数据类型分为基本类型和引用类型。
1.基本类型
主要分为七种:string、number、boolean、undefined、null、bigint、symbol。
它们的特点:
- 存储在栈内存中
- 直接保存值,赋值时是“值拷贝”
- 不可变:无法修改初始值,只能重新赋值
var a = 1;
var b = a;//b得到了a相同的值
b = 2;
console.log(a);//1,a的初始值并没有改变
引用类型(复杂类型)
包括Arrar、Object、Function、Date。
它们的特点是:
- 存储在堆内存中
- 变量保存的是内存地址(引用),赋值时拷贝的是引用
- 可变:通过引用修改对象的内容,所有指向该对象的内容都会发生变化。
let obj = { name : '张三'};
let obj2 = obj;
obj2.name = '李四';
console.log(obj.name);//
二.V8引擎执行过程:栈和堆的完美协作
V8是Chorme和Node.js背后的JS引擎,它的执行过程大致如下:
1.创建调用栈
调用栈是一个栈结构,用来存放预编译的过程,管理函数调用和代码执行顺序。每个函数执行时都会创建一个执行上下文,并被压入栈顶。
2.执行上下文的创建
V8开始执行代码时首先创建全局执行上下文,其中包含:
- 变量环境:记录变量和函数声明
- 词法环境
3.栈中存值,堆中存对象
在代码执行过程中,V8对不同数据的处理方式不同:
- 原始类型的值:直接存储在调用栈的当前执行上下文中。因为原始值的大小固定并且占用的内存小,放入栈内存中可以快速访问。
- 引用类型的值:对象、数组等实际数据存储在堆内存中,V8会在堆中分配一块空间,然后把这个空间的引用地址(指针)压入栈中。
那么为什么要这样设计呢?
- 栈内存:容量小、存取速度快
- 堆内存:容量大、速度慢、效率低
- 将大体积的对象放入堆中,保证了栈不用设计很大从而影响 V8 的执行效率,也不会发生爆栈。
三.补充知识:==与===的区别了解
很多代码中隐藏的bug都源于混淆这两个运算符。它们的本质区别:==会偷偷的进行类型转换,而===不会进行类型转换。
1.严格相等 ===
- 比较两个值的类型是否相同且值是否相同
- 类型不同直接返回false
- 不会进行任何转换
2.抽象相等 ==
- 如果两个值类型相同则与===比较结果一致
- 如果类型不同,会进行类型转换,再进行值的比较
== 的转换规则:
| 类型组合 | 转换方式 |
|---|---|
| number vs string | 将字符串转换为数字,然后比较 |
| boolean vs 任何类型 | 将true->1,fales->0,再转换为数字进行比较 |
建议:除了一些极少数的场景,日常编码中最好都使用===,避免发生一些错误转换bug。
四.总结
- JavaScript中的类型分为7种基本类型(存在栈中)和一些引用类型(堆中存储实体,栈中存放地址)
- V8引擎通过调用栈和堆内存的分离设计,保证了执行效率并且避免了爆栈
- ==会进行类型转换,规则复杂且容易出错;===不会进行类型转换,比较严格并且安全
清楚了解了类型和它们的存储方式,可以帮助你写出更加高效可靠的代码。
类型是JavaScript的基石,V8的执行机制规则让你知其然更知其所以然。希望这篇文章能帮助你彻底打通类型和底层存储的任通二脉。