Skip to content

JavaScript 值类型与引用类型

经典面试题:JS 的基本数据类型有哪些?基本数据类型和引用数据类型的区别是什么?

一、数据类型分类

JavaScript (JS) 的数据类型分为两大类:

  1. 基本数据类型 (Primitive Types)
    • String
    • Number
    • Boolean
    • Null
    • Undefined
    • Symbol (ES6新增)
  2. 引用数据类型 (Reference Types)
    • Object (包含 Array, Function 等)

二、基本数据类型 (Primitive Types)

2.1 特点

  • 也被称为 简单值 (Simple Values)原始值 (Primitive Values)
  • 是 JS 中最底层、最简单的数据形式,其值 不可再细分
  • 例如,一个数字 1 或一个布尔值 true 无法再被拆解。

2.2 存储方式

  • 基本数据类型的值直接存储在 栈内存 (Stack Memory) 中。
  • 栈的特点是 后进先出 (LIFO - Last-In, First-Out),类似于乒乓球盒,最后放进去的球最先被取出。

函数调用栈示例

函数调用会形成一个调用栈,后调用的函数先执行完毕并出栈。

// 全局环境 (Global Context) 在栈底
function a() {
    console.log('Enter A');
    b(); // a 调用 b, b 入栈
    console.log('Exit A');
}

function b() {
    console.log('Enter B');
    c(); // b 调用 c, c 入栈
    console.log('Exit B');
}

function c() {
    console.log('Execute C'); // c 执行完毕, 准备出栈
}

a(); // a 入栈

执行顺序与出栈顺序:

  1. a 函数入栈。
  2. b 函数入栈。
  3. c 函数入栈。
  4. c 函数执行完毕,出栈。
  5. b 函数继续执行完毕,出栈。
  6. a 函数继续执行完毕,出栈。

三、nullundefined 的深度解析

3.1 typeof null === 'object' 的历史遗留问题

这是一个从 JS 第一个版本就存在的 Bug。

  • 原因: 在底层,JS 通过检查一个值的前三位二进制位来判断其类型。如果前三位是 000,该值就被认为是对象。
  • null 是一个空值,其二进制表示全部为 0。因此,它的前三位也是 000,导致 typeof 错误地将其报告为 'object'
  • 为何不修复: 修复这个 Bug 会破坏大量已存在的网站和代码库,风险过高,因此被保留了下来。

3.2 undefined 的诞生

undefined 是在 null 之后被引入的,旨在弥补 null 的一些设计缺陷:

  1. 值的类型: 设计者认为表示 "无" 的值最好不是一个对象。
  2. 错误发现: 在早期 JS 中,null 会被自动转换为 0,这使得因变量为空而导致的计算错误难以被发现。

因此,undefined 被设计出来,它在数值转换时会变成 NaN (Not-a-Number),使得错误更容易暴露。

3.3 null vs. undefined 的区别

特性nullundefined
语义表示一个 “意图为空” 的对象指针。它是一个被开发者主动赋值的、明确的“无”。表示一个变量已声明但 “未被赋值”。它代表了值的缺失。
数值转换Number(null) 结果为 0Number(undefined) 结果为 NaN
典型用法1. 作为函数参数,表示该参数不是对象。<br>2. 作为对象原型链的终点。1. 变量声明后未赋值,默认为 undefined。<br>2. 调用函数时,未提供的参数值为 undefined。<br>3. 对象没有赋值的属性,其值为 undefined。<br>4. 函数没有 return 语句时,默认返回 undefined

四、引用数据类型 (Reference Types)

4.1 特点

  • 也被称为 复杂值 (Complex Values)
  • 其值是由多个简单值或其他复杂值组成的集合,可以被拆分
  • 由于可以包含任意多的值,其在内存中的大小是未知的。

4.2 存储方式

引用类型的存储方式比较特殊,同时利用了栈内存和堆内存:

  • 栈内存 (Stack Memory): 存储变量名和一个指向堆内存的 引用地址 (Address)
  • 堆内存 (Heap Memory): 存储对象的 具体数据 (Data)

访问流程: 当访问一个引用类型的变量时,首先从栈中获取其引用地址,然后通过该地址找到堆中存储的实际数据。

Stack (obj_address) ---> Heap ({ name: '谢老师', age: 18 })


五、基本类型与引用类型的核心区别 (面试核心)

1. 访问方式 & 变量赋值

  • 基本类型: 按 访问。当一个变量赋值给另一个变量时,是 拷贝值。两个变量完全独立。
  • 引用类型: 按 引用 访问。当一个变量赋值给另一个变量时,是 拷贝引用地址。两个变量指向堆内存中的同一个对象,修改其中一个会影响另一个。
javascript
// 基本类型:值拷贝
let str1 = 'hello';
let str2 = str1; // 拷贝 'hello' 这个值

str1 = 'world'; // 修改 str1

console.log(str1); // 'world'
console.log(str2); // 'hello' (不受影响)

// 引用类型:引用地址拷贝
let obj1 = { name: '张三' };
let obj2 = obj1; // 拷贝 obj1 的内存地址

obj2.name = '李四'; // 通过地址修改堆内存中的对象

console.log(obj1.name); // '李四' (受影响)
console.log(obj2.name); // '李四'

2. 比较方式

  • 基本类型: 比较的是 是否相等。
  • 引用类型: 比较的是它们的 引用地址 是否相同。
javascript
// 基本类型比较
let a = 10;
let b = 10;
console.log(a === b); // true (值相等)

// 引用类型比较
let arr1 = [1, 2, 3];
let arr2 = [1, 2, 3];
console.log(arr1 === arr2); // false (它们在堆内存中有不同的地址)

let arr3 = arr1;
console.log(arr1 === arr3); // true (引用地址相同)

3. 动态属性

  • 基本类型: 不可以动态添加、修改或删除属性。
  • 引用类型: 可以随时动态地添加、修改和删除其属性和方法。
javascript
// 对引用类型添加属性
let person = {};
person.name = '谢老师';
console.log(person.name); // '谢老师'

// 对基本类型添加属性 (静默失败)
let str = 'hello';
str.name = '谢老师'; // JS会临时创建一个包装对象(Wrapper Object)来执行这行代码,然后立即销毁
console.log(str.name); // undefined (原始字符串并未被改变)