一、JavaScript中的两种数据类型
1.简单数据类型(原始类型)
简单数据类型他们的值直接储存在调用栈中,主要有number,字符串,Boolean,undefined和null这5种。
let num=123// number
let str='hello'// 字符串
let flag=true// Boolean类型
let un=undefined// undefined类型
let nu=null// null类型
对于这几种类型的作用这里不多做赘述
2.复杂数据类型(引用类型)
复杂数据类型的值储存在堆中,在调用栈里存放的只是堆中的引用地址。对于复杂数据类型和简单数据类型的这两句简单的介绍你现在可能看不懂,但是别急,待会我会详细的解释。复杂数据类型主要有对象,函数,数组和Date这四种。
let obj={}//对象
let fn=function(){}//函数
let arr=[]//数组
let data=new Date()//Date
二、这两种数据类型有什么区别
相信大家很疑惑,为什么要这样区分为两种数据类型,而不是直接就是分成9种不同的数据类型。答案就在这两句话中。
原始类型:值直接储存在调用栈中
引用类型:值储存在堆中,在调用栈里存放的只是堆中的引用地址
首先给大家看两段代码
let a=1
let b=a
a=2
console.log(b);
let a={
age:18
}
let b=a
a.age=20
console.log(b.age);
很奇怪,为什么同样修改了a的值,可是b输出的值一个改变了一个没有改变。接下来我将在调用栈的角度解释这两行代码。
在读取到这行代码时,调用栈首先要进行预编译然后创建一个全局执行上下文并压入栈中,在全局执行上下文中有一个变量环境和一个词法环境,变量环境主要是存放用var声明的变量,词法环境主要存放let和const存放的变量。关于预编译的内容可以看看我之前的文章。(什么是js的预编译?深入了解声明提升 - 掘金 (juejin.cn))在预编译结束阶段词法环境存放一个a=undefined和b=undefined。然后就是代码的执行阶段,将a赋值为1,和b=a此时直接在词法环境中读取a的值为1赋给b,最后再把a赋值为2。这就是第一段代码的整个运行过程。接着是第二段代码的执行过程。
首先依然是先预编译,预编译结束时在全局执行上下文中的词法环境有一个a=undefined和b=undefined。接着是代码的运行阶段,v8引擎发现a是一个对象是一个复杂数据类型,在v8引擎的逻辑中复杂数据类型并不是直接储存在词法环境中,而是储存在堆中。 词法环境中只是存储了他在堆中的下标,然后读取复杂数据类型的值时是先读取下标再访问堆获取对应的值。因此在执行b=a这个赋值语句时,是直接将a在堆中储存的值的下标赋给b,此时a和b在访问时是访问的同一个值,因此在对a.age进行修改时,b.age也会被修改,所以会输出20。
这里解释一下为什么要这样存储复杂数据类型 因为调用栈在设计之初大小被设计的不是很大,方便我们查找数据,提高我们的代码运行效率。而复杂数据类型就像名字一样,相对于简单数据类型有可能会是一个非常非常大的数据。如果还放进调用栈中不仅容易爆栈还会降低代码的执行效率。