Skip to content

02 JS 值类型和引用类型的区别

  • typeof 能判断哪些类型
  • 何时使用 === 何时使用 ==
  • 值类型和引用类型的区别
  • 手写深拷贝

在 JavaScript 中,变量可以存储两种类型的值:基本类型(也称为值类型)和引用类型。

特性/类型基本类型(值类型)引用类型
存储位置栈内存堆内存
存储内容实际的值指向堆内存中对象的引用(地址)
赋值操作复制值复制引用(地址)
值的可变性不可变(赋值时创建新副本)可变(通过引用修改原对象)

值类型(基本类型/原始类型)

值类型是简单的数据段,它们存储在栈(stack)内存中。JavaScript 中的基本类型有以下几种:

  1. Undefined:表示变量未定义,默认值。变量已声明但未初始化时的值。
  2. Null:表示一个空的或无效的对象引用。指针指向空地址。
  3. Boolean:布尔值,可以是truefalse
  4. Number:数字,包括整数和浮点数,还包括Infinity(无穷大)、-Infinity(负无穷大)和NaNNot-a-Number 不是一个数字)这几个特殊的值。
  5. String:字符串,表示文本数据,可以是单引号、双引号或反引号(模板字符串)包围的字符序列。
  6. Symbol(ES6 新增):符号,表示唯一的、不可变的数据类型,主要用于对象属性的唯一标识符。
  7. 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 中的引用类型主要包括:

  1. Object:最基本的引用类型,可以用于存储多个键值对和更复杂的实体。
  2. Array:数组,一种特殊的对象,用于存储有序集合。
  3. Function:函数,一段可执行的代码块,也可以看做是一种特殊的对象。
  4. Date:日期和时间的对象,用于处理日期和时间。
  5. RegExp:正则表达式对象,用于匹配字符串中的模式。
  6. Map:(ES6 新增)键值对的集合,键可以是任意类型。
  7. Set:(ES6 新增)值的集合,每个值都是唯一的。
  8. WeakMap:(ES6 新增)弱引用的键值对集合,键必须是对象。
  9. 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: '张三' } 的内存地址。
  • 栈内存状态展示了变量 obj1obj2 存储的引用地址。
  • 堆内存状态展示了该地址所指向的对象及其属性。

由于 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 }