JavaScript 值类型与引用类型
经典面试题:JS 的基本数据类型有哪些?基本数据类型和引用数据类型的区别是什么?
一、数据类型分类
JavaScript (JS) 的数据类型分为两大类:
- 基本数据类型 (Primitive Types)
StringNumberBooleanNullUndefinedSymbol(ES6新增)
- 引用数据类型 (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 入栈执行顺序与出栈顺序:
a函数入栈。b函数入栈。c函数入栈。c函数执行完毕,出栈。b函数继续执行完毕,出栈。a函数继续执行完毕,出栈。
三、null 与 undefined 的深度解析
3.1 typeof null === 'object' 的历史遗留问题
这是一个从 JS 第一个版本就存在的 Bug。
- 原因: 在底层,JS 通过检查一个值的前三位二进制位来判断其类型。如果前三位是
000,该值就被认为是对象。 null是一个空值,其二进制表示全部为0。因此,它的前三位也是000,导致typeof错误地将其报告为'object'。- 为何不修复: 修复这个 Bug 会破坏大量已存在的网站和代码库,风险过高,因此被保留了下来。
3.2 undefined 的诞生
undefined 是在 null 之后被引入的,旨在弥补 null 的一些设计缺陷:
- 值的类型: 设计者认为表示 "无" 的值最好不是一个对象。
- 错误发现: 在早期 JS 中,
null会被自动转换为0,这使得因变量为空而导致的计算错误难以被发现。
因此,undefined 被设计出来,它在数值转换时会变成 NaN (Not-a-Number),使得错误更容易暴露。
3.3 null vs. undefined 的区别
| 特性 | null | undefined |
|---|---|---|
| 语义 | 表示一个 “意图为空” 的对象指针。它是一个被开发者主动赋值的、明确的“无”。 | 表示一个变量已声明但 “未被赋值”。它代表了值的缺失。 |
| 数值转换 | Number(null) 结果为 0。 | Number(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 (原始字符串并未被改变)