DrillLab
第 21 / 105 道21 / 105 · #284

传值 vs 传引用

Pass by Value vs Pass by Reference

先自己答,再往下看Answer it yourself first

一句话(这句要说准):JavaScript 永远是按值传递—— 只是当值是对象时,传的那个「值」是一个地址。 严格说叫 pass by sharing

为什么这个说法重要?因为它一句话解释了两个看似矛盾的现象:

  • 函数里 obj.n = 2外面会变—— 因为两个变量指着同一个对象。
  • 函数里 obj = { n: 2 }外面不变—— 因为你只是让函数内部那个参数变量指向了新对象, 外面的变量还指着旧的。

如果真是「传引用」,第二种情况外面也会变。所以是传值。

会追问:「怎么避免改到外面?」—— 先复制。{ ...obj } /arr.slice()浅拷贝(只复制一层,嵌套对象还是共享); 深拷贝用 structuredClone(obj)(现代浏览器和 Node 17+ 原生支持, 比 JSON.parse(JSON.stringify()) 强 —— 后者会丢掉 undefined、函数、Date 会变字符串、还处理不了循环引用)。

这题和 React 直接相关:React 判断 state 变没变是比引用, 所以「改了对象属性但界面不动」就是这个原理 —— 必须造新对象。

In one line — get this sentence exactly right: JavaScript is always pass by value. It is just that when the value happens to be an object, the “value” being passed is an address. The precise name for this is pass by sharing.

Why does the wording matter? Because it explains two things that look like a contradiction:

  • obj.n = 2 inside the function does show up outside — both variables point at the same object.
  • obj = { n: 2 } inside the function does not show up outside — all you did was point the parameter variable inside the function at a new object; the outer variable still points at the old one.

If this really were pass by reference, the second case would change the outside too. So it is pass by value.

Follow-up: “How do you avoid mutating the caller’s data?” — copy it first. { ...obj } and arr.slice() are shallow copies (one level only; nested objects are still shared). For a deep copy use structuredClone(obj) — native in modern browsers and Node 17+, and better than JSON.parse(JSON.stringify()), which drops undefined and functions, turns Date into a string, and cannot handle circular references at all.

This one ties straight into React: React compares references to decide whether state changed, which is exactly why “I changed a property and the UI did not move” happens — you have to build a new object.

JavaScript改属性 vs 换指向Changing a property vs pointing somewhere else示意Illustrative
1function mutate(o) { o.n = 2; } // 改属性
2function reassign(o) { o = { n: 3 }; } // 换指向
3
4const obj = { n: 1 };
5mutate(obj); console.log(obj.n); // 2 ← 外面变了
6reassign(obj); console.log(obj.n); // 2 ← 外面没变(不是 3)
7
8// 浅拷贝只管一层
9const a = { x: 1, inner: { y: 2 } };
10const b = { ...a };
11b.x = 9; console.log(a.x); // 1 ✓ 独立
12b.inner.y = 9; console.log(a.inner.y); // 9 ✗ 还是共享的
13
14const c = structuredClone(a); // 深拷贝,嵌套也独立
1function mutate(o) { o.n = 2; } // change a property
2function reassign(o) { o = { n: 3 }; } // point at something else
3
4const obj = { n: 1 };
5mutate(obj); console.log(obj.n); // 2 ← the outside changed
6reassign(obj); console.log(obj.n); // 2 ← the outside did not change (not 3)
7
8// A shallow copy only covers one level
9const a = { x: 1, inner: { y: 2 } };
10const b = { ...a };
11b.x = 9; console.log(a.x); // 1 ✓ independent
12b.inner.y = 9; console.log(a.inner.y); // 9 ✗ still shared
13
14const c = structuredClone(a); // a deep copy, so nested objects are separate too