02 JS 值类型和引用类型的区别
- typeof 能判断哪些类型
- 何时使用 === 何时使用 ==
- 值类型和引用类型的区别
- 手写深拷贝
在 JavaScript 中,变量可以存储两种类型的值:基本类型(也称为值类型)和引用类型。
| 特性/类型 | 基本类型(值类型) | 引用类型 |
|---|---|---|
| 存储位置 | 栈内存 | 堆内存 |
| 存储内容 | 实际的值 | 指向堆内存中对象的引用(地址) |
| 赋值操作 | 复制值 | 复制引用(地址) |
| 值的可变性 | 不可变(赋值时创建新副本) | 可变(通过引用修改原对象) |
值类型(基本类型/原始类型)
值类型是简单的数据段,它们存储在栈(stack)内存中。JavaScript 中的基本类型有以下几种:
- Undefined:表示变量未定义,默认值。变量已声明但未初始化时的值。
- Null:表示一个空的或无效的对象引用。指针指向空地址。
- Boolean:布尔值,可以是
true或false。 - Number:数字,包括整数和浮点数,还包括
Infinity(无穷大)、-Infinity(负无穷大)和NaN(Not-a-Number不是一个数字)这几个特殊的值。 - String:字符串,表示文本数据,可以是单引号、双引号或反引号(模板字符串)包围的字符序列。
- Symbol(ES6 新增):符号,表示唯一的、不可变的数据类型,主要用于对象属性的唯一标识符。
- BigInt(ES11 新增):大整数,用于表示大于
2^53 - 1的整数,处理超过Number类型安全整数范围的整数。。
值类型的特征:
- 不可变性:基本类型的值是不可变的。
- 赋值:当将一个变量赋值给另一个变量时,实际上是将值复制一份给新变量,两者之后的操作互不影响。
js
const a = 10
const b = a // b 是 a 的一个副本
a = 20
console.log(b) // 输出 10,b 的值没有变| 操作 | 栈内存状态(键 Key: 值 Value) |
|---|---|
| 初始状态 | 空栈 |
const a = 10; | a: 10 |
const b = a; | a: 10, b: 10 |
b = 20; | a: 10, b: 20 |
console.log(a); | a: 10, b: 20(控制台输出 10) |
引用类型
引用类型指的是那些可能由多个值构成的对象,它们存储在堆(heap)内存中,而变量实际上存储的是指向该对象的一个引用(地址)。JavaScript 中的引用类型主要包括:
- Object:最基本的引用类型,可以用于存储多个键值对和更复杂的实体。
- Array:数组,一种特殊的对象,用于存储有序集合。
- Function:函数,一段可执行的代码块,也可以看做是一种特殊的对象。
- Date:日期和时间的对象,用于处理日期和时间。
- RegExp:正则表达式对象,用于匹配字符串中的模式。
- Map:(ES6 新增)键值对的集合,键可以是任意类型。
- Set:(ES6 新增)值的集合,每个值都是唯一的。
- WeakMap:(ES6 新增)弱引用的键值对集合,键必须是对象。
- WeakSet:(ES6 新增)弱引用的值的集合,值必须是对象。
引用类型的特征:
- 可变性:引用类型的值是可变的,意味着可以修改它们包含的属性。
- 赋值:当将一个引用类型变量赋值给另一个变量时,实际上是将引用(地址)复制一份给新变量,两个变量指向堆内存中的同一个对象。
js
const obj1 = { name: '张三' }
const obj2 = obj1 // obj2 和 obj1 指向同一个对象
obj2.name = '李四'
console.log(obj1.name) // 输出 '李四',obj1 的 name 属性也被改变了| 操作 | 栈内存状态(Key: Value) | 堆内存状态(Key: Value) |
|---|---|---|
| 初始状态 | 空栈 | 空堆 |
const obj1 = { name: '张三' }; | obj1: <引用地址1> | <引用地址1>: { name: '张三' } |
const obj2 = obj1; | obj1: <引用地址1>, obj2: <引用地址1> | <引用地址1>: { name: '张三' } |
obj2.name = '李四'; | obj1: <引用地址1>, obj2: <引用地址1> | <引用地址1>: { name: '李四' } |
console.log(obj1.name); | obj1: <引用地址1>, obj2: <引用地址1> | <引用地址1>: { name: '李四' }(控制台输出 李四) |
<引用地址1>代表堆内存中对象{ name: '张三' }的内存地址。- 栈内存状态展示了变量
obj1和obj2存储的引用地址。 - 堆内存状态展示了该地址所指向的对象及其属性。
由于 obj2 是通过 obj1 赋值的,它们指向堆内存中的同一个对象。因此,当通过 obj2 修改对象的 name 属性时,obj1 指向的对象的 name 属性也会被修改,因为它们引用的是同一个对象。
常见值类型
注意
const 声明的变量必须在声明时初始化,并且之后不能重新赋值。不过,对于引用类型的变量,虽然变量名不能重新赋值,但其内容是可以改变的。
js
// Undefined
const a; // 语法错误 (Uncaught SyntaxError: Missing initializer in const declaration,const 声明时必须初始化)
// 正确做法应该是:
let a; // const a = undefined;
console.log(a); // 输出 undefined
// Null
const b = null;
console.log(b); // 输出 null
// Boolean
const isTrue = true;
console.log(isTrue); // 输出 true
// Number
const num = 42;
console.log(num); // 输出 42
// String
const str = "Hello, World!";
console.log(str); // 输出 "Hello, World!"
// Symbol
const sym = Symbol('description');
console.log(sym); // 输出 Symbol(description)
// BigInt
const bigInt = 1234567890123456789012345678901234567890n;
console.log(bigInt); // 输出 1234567890123456789012345678901234567890n常见引用类型
js
// Object
const obj = { name: 'John', age: 30 }
console.log(obj) // 输出 { name: "John", age: 30 }
// Array
const arr = [1, 2, 3]
console.log(arr) // 输出 [1, 2, 3]
// Function
const greet = function () {
console.log('Hello!')
}
greet() // 输出 "Hello!"
// Date
const date = new Date()
console.log(date) // 输出当前日期和时间
// RegExp
const regex = /ab+c/
console.log(regex) // 输出 /ab+c/
// Map
const map = new Map()
map.set('key', 'value')
console.log(map.get('key')) // 输出 "value"
// Set
const set = new Set()
set.add(1)
set.add(2)
set.add(1)
console.log(set) // 输出 Set { 1, 2 }