红宝书第四讲:JavaScript原始值与引用值行为差异详解
资料取自《JavaScript高级程序设计(第5版)》。
查看所有教程:红宝书学习大纲
一、基本定义与存储方式
-
原始值(Primitive Values)
-
引用值(Reference Values)
-
类型:对象(
Object)、数组(Array)、函数(Function)等。 - 存储方式:实际数据存储在堆内存中,变量保存的是指向堆内存的指针地址[1]。
-
类型:对象(
二、变量复制时的差异
-
原始值的复制:独立拷贝
- 复制后,新旧变量完全独立。
let num1 = 5; let num2 = num1; // 复制值本身 num1 = 10; console.log(num2); // 输出5(num2不受num1修改影响)[^2]
- 复制后,新旧变量完全独立。
-
引用值的复制:共享对象
- 复制的是指针地址,新旧变量指向同一个对象:
let obj1 = { name: "Alice" }; let obj2 = obj1; // 复制指针地址 obj1.name = "Bob"; console.log(obj2.name); // 输出"Bob"(通过指针修改同一对象)[^2]
- 复制的是指针地址,新旧变量指向同一个对象:
三、动态属性操作能力
-
引用值可动态增删属性
- 对象可以随时添加或删除属性:
let person = {}; // 空对象 person.name = "Alice"; // 新增属性 delete person.name; // 删除属性[^4]
- 对象可以随时添加或删除属性:
-
原始值无法添加属性
- 即使试图添加属性,也不会保留:
let name = "Charlie"; name.age = 30; // 不会报错,但无意义 console.log(name.age); // 输出undefined[^4]
- 即使试图添加属性,也不会保留:
四、类型检测与包装对象
-
类型检测工具
-
原始包装对象的特殊行为
- 原始值调用方法时临时创建对象包装器:
let s = "hello"; console.log(s.substring(1)); // 临时转换为String对象调用方法[^5] - 包装对象与原始值的区别:
let a = "text"; let b = new String("text"); console.log(typeof a); // "string"(原始值) console.log(typeof b); // "object"(引用值)[^1]
- 原始值调用方法时临时创建对象包装器:
五、典型陷阱:原始值与包装对象的逻辑差异
-
布尔值的逻辑陷阱
- 原始值
false与对象new Boolean(false)的不同行为:let bool1 = false; let bool2 = new Boolean(false); console.log(bool1 && true); // false(原值按false处理) console.log(bool2 && true); // true(对象按true处理)[^6]
- 原始值
总结核心差异
| 特性 | 原始值(如 5, "text") |
引用值(如 {}, []) |
|---|---|---|
| 存储位置 | 栈内存 | 堆内存(变量保存指针地址) |
| 复制行为 | 独立拷贝值 | 共享同一对象 |
| 属性动态修改 | 不允许 | 允许 |
| 类型检测 |
typeof 正确区分 |
instanceof 检测对象类型 |
原始包装对象特殊行为详细解释
一、原始值为什么能调用方法?
JavaScript 的基础类型(如 "hello")不是对象,但在调用方法时,会临时创建对应的包装对象,使原始值具备对象的行为:
let s = "hello";
console.log(s.substring(1)); // "ello"
-
实际发生了什么:
- 当调用
substring()时,JS 引擎自动将s转换为new String("hello")(String 对象)。 - 调用完方法后,临时对象被销毁[2]。
- 当调用
二、原始值与包装对象的本质区别
通过 typeof 可以直观看出两者的存储方式不同:
let a = "text"; // 原始值
let b = new String("text"); // 包装对象
console.log(typeof a); // "string"(直接存储值)
console.log(typeof b); // "object"(指针,指向堆内存中的对象)[^1]
-
行为差异:
-
a无法添加属性(如a.age = 20无效)。 -
b可以动态增删属性(如b.age = 20生效)。
-
三、典型陷阱:布尔值的逻辑判断
原始值和包装对象在逻辑运算中行为完全不同:
let bool1 = false; // 原始值
let bool2 = new Boolean(false); // 包装对象(本质是对象)
console.log(bool1 && true); // false(直接按原值 false 处理)
console.log(bool2 && true); // true(对象在逻辑运算中始终被视为 true)[^1]
原因解释:
- 所有对象(包括
new Boolean(false))在逻辑运算中会被强行转为true。 - 原始值则严格按原值处理。
四、如何记住这些差异?
| 特征 | 原始值("text") |
包装对象(new String("text")) |
|---|---|---|
| 存储方式 | 直接存储值 | 存储指针,指向堆内存中的对象 |
| 方法调用 | 临时创建包装对象 | 直接调用 |
| 逻辑判断 | 按实际值(如 false) |
始终视为 true(因为是对象) |
| 添加属性 | 无效 | 有效(操作真实对象) |
包装对象的键(key)与值(value)详解
1. new Boolean(false) 的键与值
关键点:
new Boolean(false)实际创建的是一个 对象,内部存储了原始值false,但其行为完全遵循对象的规则[3]。-
键(keys):
无显式可枚举属性(自身属性)。通过Object.keys()会返回空数组:let boolObj = new Boolean(false); console.log(Object.keys(boolObj)); // [](无直接属性) -
值(value):
- 实际存储的原始值通过
valueOf()获取:console.log(boolObj.valueOf()); // false(原始值)[^5] - 但在布尔表达式中,对象始终被视为
true:console.log(boolObj && true); // true(包装对象的逻辑行为)[^2]
- 实际存储的原始值通过
2. new String("text") 的键与值
关键点:
new String("text")会创建一个 字符串包装对象,内部存储字符串的字符序列和一个固定属性length[4]。-
键(keys):
包含字符索引(如0,1,2,3)和length:let strObj = new String("text"); console.log(Object.keys(strObj)); // ["0", "1", "2", "3", "length"](不同环境中可能仅包含 "length")[^6] -
值(value):
- 索引对应字符:
console.log(strObj[0]); // "t" console.log(strObj[1]); // "e" -
length表示字符长度:console.log(strObj.length); // 4 [^6] -
valueOf()返回原始字符串:console.log(strObj.valueOf()); // "text"(原始值)[^5]
- 索引对应字符:
原理总结
| 类型 | new Boolean(false) |
new String("text") |
|---|---|---|
| 本质上 | 对象(存储 false) |
对象(存储字符序列和 length) |
| 显式键(keys) | 无[5] | 字符索引和 length[4]
|
| 实际值 |
false(需用 valueOf()) |
"text"(通过 valueOf()) |
| 行为差异 | 逻辑运算中视为 true[3]
|
字符和长度可直接访问[4] |
目录:总目录
上篇文章:第三讲:JavaScript 操作符与流程控制详解
-
动态属性操作能力,文件 《JavaScript高级程序设计(第5版)》 第4章 ↩
-
变量复制机制,文件 《JavaScript高级程序设计(第5版)》 第4章 ↩ ↩
-
布尔值与包装对象的行为陷阱,文件 《JavaScript高级程序设计(第5版)》 第4章 ↩ ↩ ↩
-
包装对象的valueOf方法,文件 《JavaScript高级程序设计(第5版)》 第5章 ↩