深拷贝与浅拷贝的区别
前文我们讲到,JavaScript中存在两大数据类型:
- 基本类型
- 引用类型
基本类型数据保存在在栈内存中
引用类型数据保存在堆内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中
浅拷贝
浅拷贝仅复制对象的第一层属性,对于嵌套的对象和数组,只复制其引用。修改浅拷贝后的嵌套对象会影响原始对象。
浅拷贝的简单实现:
function shallowClone(obj) {
const newObj = {}; //创建一个空对象newObj,用于存储拷贝的属性。
for (let prop in obj) {//循环遍历对象obj的所有可枚举属性
if (obj.hasOwnProperty(prop)) {//检查属性是否是对象obj自己的属性,而不是从原型链继承的属性
newObj[prop] = obj[prop];
}
}
return newObj;
}
让我们看一个具体的示例来演示 shallowClone 的工作原理:
const original = {
name: 'Alice',
age: 30,
address: {
city: 'Wonderland',
street: 'Rabbit Hole'
}
};
const cloned = shallowClone(original);
console.log(cloned);
// 输出: { name: 'Alice', age: 30, address: { city: 'Wonderland', street: 'Rabbit Hole' } }
// 修改克隆对象的属性
cloned.name = 'Bob';
cloned.address.city = 'Looking Glass';
console.log(original.name); // 输出: 'Alice',原始对象未受影响
console.log(original.address.city); // 输出: 'Looking Glass',原始对象受影响
更简洁的浅拷贝方法
在现代 JavaScript 中,可以使用 Object.assign 或展开运算符 ... 来实现浅拷贝:
// 使用 Object.assign
const cloned1 = Object.assign({}, original);
// 使用展开运算符
const cloned2 = { ...original };
数组的浅拷贝还有一下几种方式:
const originalArray = [1, 2, 3, { a: 4 }];
//使用slice方法
const shallowCopy = originalArray.slice();
//使用展开运算符 (`...`)
const shallowCopy = [...originalArray];
//使用Array.from方法
const shallowCopy = Array.from(originalArray);
//使用concat方法
const shallowCopy = originalArray.concat();
深拷贝
深拷贝递归地复制对象的所有层级,对于嵌套对象和数组,也会创建新的实例。修改深拷贝后的嵌套对象不会影响原始对象。
const original = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.b.c = 3;
console.log(original.b.c); // 2
深拷贝的简单实现
function deepClone(obj, hash = new WeakMap()) {
// 如果是 null 或者非对象类型,直接返回
if (obj === null || typeof obj !== 'object') return obj;
// 处理日期对象
if (obj instanceof Date) return new Date(obj);
// 处理正则表达式对象
if (obj instanceof RegExp) return new RegExp(obj);
// 处理循环引用
if (hash.get(obj)) return hash.get(obj);
// 创建一个新的对象实例
const cloneObj = new obj.constructor();
// 将新对象存入 hash 中,处理循环引用
hash.set(obj, cloneObj);
// 遍历对象的所有属性,包括不可枚举属性和符号属性
Reflect.ownKeys(obj).forEach(key => {
// 递归拷贝属性值
cloneObj[key] = deepClone(obj[key], hash);
});
return cloneObj;
}
React 中的应用
在 React 中,处理状态时需要注意对象和数组的拷贝方式。React 依赖于不可变数据的概念,即状态的修改应该创建新的对象或数组实例,而不是直接修改原来的状态。这样可以确保 React 能够正确地检测到状态变化,并触发重新渲染。
浅拷贝的应用
浅拷贝通常用于简单的状态更新,特别是当状态对象或数组没有嵌套结构时。 示例:
import React, { useState } from 'react';
const App = () => {
const [state, setState] = useState({ count: 0 });
const increment = () => {
setState(prevState => ({
...prevState,
count: prevState.count + 1
}));
};
return (
<div>
<p>Count: {state.count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
export default App;
在这个例子中,state 对象没有嵌套结构,使用浅拷贝(扩展运算符)来更新状态是安全的。
深拷贝的应用
深拷贝用于处理嵌套结构的状态,确保更新不会影响原始状态对象。
import React, { useState } from 'react';
const App = () => {
const [state, setState] = useState({ user: { name: 'John', age: 30 } });
const updateAge = () => {
setState(prevState => {
const newState = JSON.parse(JSON.stringify(prevState));
newState.user.age = 31;
return newState;
});
};
return (
<div>
<p>Name: {state.user.name}</p>
<p>Age: {state.user.age}</p>
<button onClick={updateAge}>Update Age</button>
</div>
);
};
export default App;
在这个例子中,state 对象具有嵌套结构,因此使用深拷贝来确保更新不会影响原始状态。
使用第三方库进行深拷贝
使用 JSON.parse(JSON.stringify()) 进行深拷贝有其局限性(例如不支持函数、undefined、symbol、Date、函数等特殊值)。
const obj = {
name: 'A',
name1: undefined,
name3: function() {},
name4: Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}
可以使用第三方库如 lodash 的 cloneDeep 方法来进行更可靠的深拷贝。
import React, { useState } from 'react';
import cloneDeep from 'lodash/cloneDeep';
const App = () => {
const [state, setState] = useState({ user: { name: 'John', age: 30 } });
const updateAge = () => {
setState(prevState => {
const newState = cloneDeep(prevState);
newState.user.age = 31;
return newState;
});
};
return (
<div>
<p>Name: {state.user.name}</p>
<p>Age: {state.user.age}</p>
<button onClick={updateAge}>Update Age</button>
</div>
);
};
export default App;
总结
- 浅拷贝:适用于没有嵌套结构的状态更新,常用方法包括扩展运算符(
...)和Object.assign。 - 深拷贝:适用于嵌套结构的状态更新,确保更新不会影响原始状态。可以使用
JSON.parse(JSON.stringify())或lodash的cloneDeep方法。 - React 中的状态更新:始终确保创建新对象或数组实例来触发正确的重新渲染。
理解深拷贝和浅拷贝的区别以及如何在 React 中正确地更新状态,对于编写健壮和高效的 React 应用非常重要。