JavaScript中的引用赋值、浅拷贝和深拷贝
一、核心概念对比
| 特性 |
引用赋值 |
浅拷贝 |
深拷贝 |
|---|
| 定义 |
变量指向同一内存地址 |
复制第一层属性,嵌套对象仍共享引用 |
完全独立的副本,所有层级都复制 |
| 修改原对象 |
影响新变量 |
第一层属性不影响,嵌套对象影响 |
完全不影响 |
| 内存地址 |
相同 |
不同(但嵌套对象相同) |
完全不同 |
| 适用场景 |
需要共享数据 |
简单对象,无嵌套或嵌套不变 |
复杂嵌套对象,需要完全独立 |
二、引用赋值 (Reference Assignment)
// 引用赋值示例
let obj1 = { name: 'Alice', hobbies: ['reading', 'coding'] };
let obj2 = obj1; // 引用赋值
obj2.name = 'Bob';
console.log(obj1.name); // 'Bob' - 原对象也被修改
obj2.hobbies.push('gaming');
console.log(obj1.hobbies); // ['reading', 'coding', 'gaming'] - 嵌套对象也被修改
console.log(obj1 === obj2); // true - 指向同一个内存地址
关键特点:
- 两个变量指向同一个内存地址
- 修改任何一个都会影响另一个
三、浅拷贝 (Shallow Copy)
// 浅拷贝的几种方法
// 1. 扩展运算符
let original = { a: 1, b: { nested: 2 } };
let shallowCopy1 = { ...original };
// 2. Object.assign()
let shallowCopy2 = Object.assign({}, original);
// 3. 数组的浅拷贝方法
let arr = [1, 2, { nested: 3 }];
let arrCopy1 = arr.slice();
let arrCopy2 = arr.concat();
let arrCopy3 = [...arr];
// 浅拷贝的特点
original.a = 100;
console.log(shallowCopy1.a); // 1 - 第一层属性不受影响
original.b.nested = 200;
console.log(shallowCopy1.b.nested); // 200 - 嵌套对象受影响(共享引用)
console.log(original === shallowCopy1); // false
console.log(original.b === shallowCopy1.b); // true - 嵌套对象引用相同
四、深拷贝 (Deep Copy)
1. 手动实现深拷贝
// 递归实现深拷贝
function deepClone(obj, hash = new WeakMap()) {
// 处理基本类型和null
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理循环引用
if (hash.has(obj)) {
return hash.get(obj);
}
// 处理日期
if (obj instanceof Date) {
return new Date(obj);
}
// 处理正则表达式
if (obj instanceof RegExp) {
return new RegExp(obj);
}
// 处理数组
if (Array.isArray(obj)) {
const cloneArr = [];
hash.set(obj, cloneArr);
obj.forEach(item => {
cloneArr.push(deepClone(item, hash));
});
return cloneArr;
}
// 处理对象
const cloneObj = Object.create(Object.getPrototypeOf(obj));
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
2. 使用JSON方法(有限制)
let original = {
name: 'Alice',
age: 25,
hobbies: ['reading', 'coding'],
address: { city: 'Beijing' }
};
let deepCopy = JSON.parse(JSON.stringify(original));
original.address.city = 'Shanghai';
console.log(deepCopy.address.city); // 'Beijing' - 不受影响
// JSON方法的限制:
let problemObj = {
date: new Date(), // 会被转换为字符串
func: function() {}, // 会被忽略
undefined: undefined, // 会被忽略
infinity: Infinity, // 会被转换为null
regex: /pattern/g, // 会变成空对象
symbol: Symbol('foo'), // 会被忽略
bigint: BigInt(123) // 会报错
};
let jsonCopy = JSON.parse(JSON.stringify(problemObj));
console.log(jsonCopy);
3. 使用第三方库
// Lodash的_.cloneDeep()
// const _ = require('lodash');
// let deepCopy = _.cloneDeep(original);
// Structured Clone API(现代浏览器/Node.js)
let original2 = { a: 1, b: { nested: 2 } };
let structuredCloneCopy = structuredClone(original2);
五、不同数据类型的处理
// 测试各种数据类型的拷贝行为
const testData = {
// 基本类型 - 所有拷贝方式都安全
string: 'hello',
number: 123,
boolean: true,
null: null,
undefined: undefined,
symbol: Symbol('test'),
bigint: BigInt(123),
// 引用类型 - 需要注意
array: [1, 2, 3],
object: { a: 1, b: 2 },
date: new Date(),
regex: /pattern/g,
set: new Set([1, 2, 3]),
map: new Map([['key', 'value']]),
function: function() { return 'test'; },
// 特殊引用
circularRef: null, // 循环引用
typedArray: new Uint8Array([1, 2, 3])
};
// 创建循环引用
testData.circularRef = testData;
六、性能比较
// 性能测试函数
function performanceTest(method, data, iterations = 10000) {
const start = performance.now();
for (let i = 0; i < iterations; i++) {
method(data);
}
const end = performance.now();
return end - start;
}
const testObj = { /* 复杂嵌套对象 */ };
// 测试不同方法的性能
console.log('JSON方法:', performanceTest(JSON.parse.bind(null, JSON.stringify(testObj)), testObj));
console.log('递归深拷贝:', performanceTest(deepClone, testObj));
console.log('浅拷贝:', performanceTest(obj => ({ ...obj }), testObj));
七、实战应用场景
场景1:状态管理(React/Vue)
// React中错误的做法 - 引用赋值
const [state, setState] = useState({ user: { name: 'Alice' } });
const updateUser = () => {
const newState = state; // 错误!引用赋值
newState.user.name = 'Bob';
setState(newState); // 不会触发重新渲染
};
// 正确的做法 - 浅拷贝或深拷贝
const updateUserCorrectly = () => {
// 浅拷贝(适合简单对象)
setState({ ...state, user: { ...state.user, name: 'Bob' } });
// 或者使用深拷贝
// setState(JSON.parse(JSON.stringify(state)));
};
场景2:函数参数处理
// 避免副作用
function processData(data) {
// 创建副本,避免修改原数据
const dataCopy = deepClone(data);
// 对dataCopy进行操作...
return dataCopy;
}
场景3:缓存/撤销功能
class HistoryManager {
constructor(initialState) {
this.history = [deepClone(initialState)];
this.currentIndex = 0;
}
pushState(state) {
// 保存状态的深拷贝
this.history = this.history.slice(0, this.currentIndex + 1);
this.history.push(deepClone(state));
this.currentIndex++;
}
undo() {
if (this.currentIndex > 0) {
this.currentIndex--;
return deepClone(this.history[this.currentIndex]);
}
return null;
}
}
八、选择策略指南
什么时候用引用赋值?
- 需要多个变量指向同一数据
- 数据共享且允许相互影响
- 性能要求极高,内存有限
什么时候用浅拷贝?
- 对象没有嵌套或嵌套层数固定
- 嵌套对象不需要独立修改
- 性能要求较高
什么时候用深拷贝?
- 需要完全独立的数据副本
- 数据有复杂的嵌套结构
- 需要避免副作用
- 实现撤销/重做功能
九、最佳实践
优先使用浅拷贝:除非确需深拷贝,否则使用浅拷贝更高效
使用const声明引用类型:防止意外重新赋值
考虑使用不可变数据结构:如Immutable.js
注意循环引用:深拷贝时要处理循环引用
性能优化:对于大数据量,考虑增量更新
十、总结
理解JavaScript中的拷贝机制是成为高级开发者的关键:
- 引用赋值是简单的指针复制
- 浅拷贝复制第一层,共享嵌套引用
- 深拷贝创建完全独立的副本
根据具体场景选择合适的拷贝策略,平衡性能与数据安全性的需求。