JavaScript 数据类型的分类
JavaScript 中的数据类型分为两大类:基本类型(原始类型)和引用类型(复杂类型)。两者之间有什么区别呢?哪些类型是基本类型,哪些又是引用类型呢?
1. 基本类型(原始类型)
基本类型的值直接存储在栈内存中,具有固定大小,访问速度快,基本类型有如下几种:
- String,即字符串类型
代码示例
let str = 'hello world'//这是一个string类型的变量
- number,数字(整数/浮点数)类型,最大安全值为2的53次方,超出计算会出错
代码示例
let num = 123 //number类型
console.log(2**53 + 1)//最大安全值2的53次方,计算会出错
3.Boolean类型,布尔类型只有true和false两个值
代码示例
let isStudent = true//布尔类型
- undefined,在 JavaScript 中, undefined 既表示未定义值也是类型,使用 typeof 操作符可以看到它的类型:
var a = typeof undefined
console.log(a)// "undefined"(类型名称)
- null,在 JavaScript 中,null同样即表示空值也算类型,我们使用typeof 操作符来看看:
var b = typeof undefined
console.log(b)//Object
我们发现输出结果为Object,这是 JavaScript 中一个著名的历史遗留问题,在 JavaScript 最初的实现中:对象在内存中以 0x00 开头,null 被表示为全零,typeof 检查前几位判断类型,因此 null 被误判为 object
- bigint,大整数,当数字超出2的53次方时,想要准确运算必须使用bigint类型
代码示例
let bigNumber = 9007199254740991n;
let b = 3n
console.log(bigNumber + b)//9007199254740994n,结果正确
- symbol,Symbol 是 ES6 引入的第七种基本类型,用于创建 唯一标识符 。
const sym2 = Symbol("id");
const sym3 = Symbol("id");
conseole.log(sym2 === sym3);//false
可以看到即使sym2和sym3同样赋值为Symbol("id"),他们还是不相等
2. 引用类型(复杂类型)
引用类型的值存储在堆内存中,栈中只存储指向堆的引用地址。
- Array,数组类型,是有序元素的集合,数组增删值时若改变其他元素下标则会增加时间复杂度O(n)
代码示例
let numbers = [1, 2, 3, 4, 5] //这是一个Array类型的变量
numbers.push(6) //末尾添加:O(1)
numbers.pop() //末尾删除:O(1)
numbers.unshift(0) //开头添加:O(n),需要移动所有元素
numbers.shift() //开头删除:O(n),需要移动所有元素
- Object,对象类型,是键值对的集合
代码示例:
let person = { name: 'Bob', age: 30 } //这是一个Object类型的变量
- Function,函数类型,是可执行的代码块
代码示例:
let greet = function() { console.log('Hello') } //这是一个Function类型的变量
greet() //调用函数,输出Hello
- Date,日期类型,一个特殊的类型,用于表示日期时间对象
代码示例:
let time = new Date() //这是一个Date类型的变量
console.log(time) //输出当前日期时间
两种数据类型在V8 引擎的执行过程
1. 创建调用栈
调用栈(Call Stack)用于管理函数调用的执行上下文。它是一个 LIFO(后进先出)的数据结构,因此当栈内存过大时,执行效率低,堆由于其先进先出特性,不会影响执行效率
2. 创建执行上下文
执行上下文(Execution Context)即上期讲到的AO与GO,包含了函数执行所需的所有信息:
3. 执行代码(栈与堆的交互)
代码执行时,不同类型的值会被存储在不同的内存区域,先看一段代码:
let str = 'hello' // 基本类型:直接存栈中
let a = 1 // 基本类型:直接存栈中
let person = { // 引用类型:值存堆中,地址存栈中
name: 'Bob',
age: 30
}
let numbers = [1, 2, 3] // 引用类型:数组存堆中
内存布局如下:
┌─────────────────────────────────────────────────────────────┐
│ 内存布局 │
├─────────────────────────────────────────────────────────────┤
│ 栈内存 (Stack) │ 堆内存 (Heap) │
│ │ │
│ ┌─────────────────┐ │ ┌─────────────────┐ │
│ │ str: "hello" │ │ │ │ │
│ ├─────────────────┤ │ │ │ │
│ │ a: 1 │ │ │ │ │
│ ├─────────────────┤ │ │ │ │
│ │ person: 0x001 │ ──────────────→│ │ {name:"Bob", │ │
│ │ │ │ │ age:30} │ │
│ ├─────────────────┤ │ │ │ │
│ │ numbers: 0x002 │ ──────────────→│ │ [1, 2, 3] │ │
│ └─────────────────┘ │ │ │ │
│ │ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
这种设计的好处:
- 性能优化:栈操作速度快,基本类型直接存栈中
- 内存效率:栈大小可控,避免过大影响执行效率
- 避免爆栈:引用类型存堆中,栈只存地址,节省栈空间