传值 vs 传引用
Pass by Value vs Pass by Reference
一句话(这句要说准):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 = 2inside 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.