JS等语言引用类型参数传递本质是传地址副本,形参实参指向同一堆对象,故改属性影响外部,但重赋值不影响;避免副作用需浅/深拷贝或设计纯函数。

修改引用类型形参的内部属性会改变外部对象,不是因为“传了引用”,而是因为形参和实参指向堆中同一个对象——它们持有的是同一块内存地址的副本。
关键不在“传什么”,而在“改什么”
函数调用时,实参(比如一个对象变量)的值被复制给形参。这个“值”对引用类型来说,就是对象在堆内存中的地址(例如 0x1234)。栈中两个变量(实参和形参)各自存着这个相同的地址,因此都指向堆里那个唯一的对象实例。
此时操作分两类:
- 修改对象内部属性:如 obj.name = "Alice" 或 list.add(1) —— 实际是在堆上操作那个共享对象,所以外部可见变化
- 修改形参本身指向:如 obj = new User() 或 list = [] —— 只是让形参栈空间换存另一个地址,实参仍指着原来的地方,互不影响
所有主流语言其实都一样
Java、JavaScript、Python、C# 等语言中,引用类型的参数传递机制本质一致:都是传引用值的副本,不是传对象本身,也不是传“引用的引用”。ECMAScript 规范、Java 语言规范均明确写为“按值传递”(call by value)。
所谓“像引用传递”,只是因共享堆对象产生的表象。真正决定是否影响外部的,从来不是语言叫它什么,而是你代码里动的是对象的内容,还是变量的指向。
怎么避免意外修改?看拷贝层级
如果函数不该改动原始数据,就得主动隔离:
- 浅拷贝适用于一层结构:Object.assign({}, obj)、{...obj}、Array.from(arr) —— 新对象第一层字段独立,但嵌套对象仍共用引用
- 深拷贝适合多层嵌套:structuredClone(obj)(推荐)、JSON.parse(JSON.stringify(obj))(注意 Date、Function、undefined 会丢失)
- 更稳妥的做法是函数文档注明是否修改入参,或设计为纯函数(不产生副作用)
一句话记住核心逻辑
实参和形参就像两个人拿着同一把钥匙——打开的是同一扇门(堆中对象)。他们都能进门改屋里的摆设(属性),但谁换了一把新钥匙(重新赋值),都不会影响对方手里的那把。

















