前端面试 P003:原始值、对象共享与赋值行为
目前顯示中文(中國)原文
来源:方格前端讲面试 · P3:值类型 vs 引用类型,15 分 16 秒。
回看定位:03:21 原始值赋值 · 05:31 对象共享 · 08:51 函数传参 · 11:43 const · 12:45 相等比较
这集解决的三个问题
let b = a后,为什么有时互不影响,有时又能看到相同的变化?- 为什么函数内改对象属性能影响外部,给形参重新赋值却不能?
- 为什么 const 声明的对象还能修改属性?
区分“变量的绑定”和“被访问的对象”,比只背“值类型、引用类型”更能预测代码结果。
原始值赋值:两个变量各自独立
let a = 100;
let b = a;
a = 200;
console.log(a); // 200
console.log(b); // 100
b 得到赋值发生时 a 的值,也就是 100。之后给 a 重新赋值,不会回头修改 b。
原始值本身不可变。例如给一个字符串变量赋新字符串,变化的是变量的绑定,不是在原字符串内部修改字符。
对象赋值:两个变量可以共享一个对象
let first = { name: 'Alice' };
let second = first;
first.name = 'Bob';
console.log(first.name); // Bob
console.log(second.name); // Bob
console.log(first === second); // true
second = first 没有克隆对象。它让两个独立变量访问同一个对象,所以通过其中一个变量改对象属性,另一个变量也能看到变化。
first ──→ 对象 A { name: 'Bob' }
second ──→ 对象 A(同一个对象)
重新赋值与修改属性是两种操作
let first = { name: 'Alice' };
let second = first;
first = { name: 'Carol' };
second.name = 'Bob';
console.log(first.name); // Carol
console.log(second.name); // Bob
console.log(first === second); // false
first = ... 让 first 改为指向新对象 B。second 仍然指向原来的对象 A,两个变量之间并没有持续的“同步赋值”机制。
first ──→ 对象 B { name: 'Carol' }
second ──→ 对象 A { name: 'Bob' }
这个例子的两个对象没有嵌套共享数据。如果新对象又引用了原对象的某个子对象,那部分仍可能共享,不能只凭顶层对象不同就推断所有内部数据完全独立。
函数传参仍然按值传递
视频中的 modify(obj) 示例,就是之前讨论过的题目:改 obj.name 后外部看得到,obj = { ... } 后外部变量仍指向原对象。
完整代码和逐步解析保存在 JavaScript 函数传参:值传递与对象引用,避免把同一道题重复保存成两篇相同笔记。
“传入对象引用”描述传入的内容;“按引用传递”描述形参是否成为外部变量的别名。JavaScript 中形参是独立变量,所以即使共享对象,仍不属于严格意义上的按引用传递。
const 约束变量绑定
const user = { name: 'John' };
user.name = 'Jane';
console.log(user.name); // Jane
try {
user = { name: 'Doe' };
} catch (error) {
console.log(error.name); // TypeError
}
const 不允许给 user 重新赋值,但不会自动冻结它所指向的对象。声明方式的整体区别见 前端面试 P001:var、let、const 与暂时性死区。
对课件中“栈、堆、地址”模型的精确化
课件用“原始值存在栈上、对象存在堆上、变量保存地址”帮助解释共享行为。这个图可以辅助理解示例,但不应当被当成 JavaScript 规范对所有引擎的内存分配要求。
更稳妥的语言层面结论是:
- 原始值不可变;重新赋值只改变相应变量。
- 对象有身份;多个变量可以访问同一个对象。
- 对象赋值不会自动复制对象,函数参数也不会自动克隆对象。
- JavaScript 不向普通代码直接暴露对象的实际内存地址。
这是对教学模型适用范围的说明;课件中赋值与函数传参示例的输出是正确的。
自测
const a = { count: 1 };
const b = a;
b.count++;
console.log(a.count); // 2
console.log(a === b); // true
console.log(a === { count: 2 }); // false
最后一行虽然属性值相同,但右侧创建了另一个对象。对象的严格相等比较关注是否为同一个对象,而不是自动比较所有属性。