您现在的位置是:网站首页 > 对象拷贝与比较文章详情
对象拷贝与比较
陈川
【
JavaScript
】
64181人已围观
4699字
浅拷贝与深拷贝
JavaScript中对象拷贝分为浅拷贝和深拷贝两种方式。浅拷贝只复制对象的第一层属性,而深拷贝会递归复制对象的所有层级。
浅拷贝的常见实现方式:
const obj = { a: 1, b: { c: 2 } };
// 1. Object.assign
const shallowCopy1 = Object.assign({}, obj);
// 2. 展开运算符
const shallowCopy2 = { ...obj };
// 3. 数组的slice方法
const arr = [1, 2, { a: 3 }];
const shallowCopy3 = arr.slice();
深拷贝的实现方式:
// 1. JSON方法(有局限性,不能处理函数和循环引用)
const deepCopy1 = JSON.parse(JSON.stringify(obj));
// 2. 递归实现
function deepClone(source) {
if (source === null || typeof source !== 'object') {
return source;
}
const target = Array.isArray(source) ? [] : {};
for (let key in source) {
if (source.hasOwnProperty(key)) {
target[key] = deepClone(source[key]);
}
}
return target;
}
// 3. 使用第三方库如lodash的_.cloneDeep
const deepCopy3 = _.cloneDeep(obj);
对象比较的几种方式
JavaScript中比较对象有几种不同的方法,每种方法都有其适用场景。
严格相等比较
const obj1 = { a: 1 };
const obj2 = { a: 1 };
const obj3 = obj1;
console.log(obj1 === obj2); // false
console.log(obj1 === obj3); // true
浅比较
只比较第一层属性:
function shallowEqual(objA, objB) {
if (objA === objB) {
return true;
}
if (typeof objA !== 'object' || objA === null ||
typeof objB !== 'object' || objB === null) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
for (let i = 0; i < keysA.length; i++) {
if (objA[keysA[i]] !== objB[keysA[i]]) {
return false;
}
}
return true;
}
深比较
递归比较所有属性:
function deepEqual(objA, objB) {
if (objA === objB) {
return true;
}
if (typeof objA !== 'object' || objA === null ||
typeof objB !== 'object' || objB === null) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
for (let key of keysA) {
if (!keysB.includes(key) || !deepEqual(objA[key], objB[key])) {
return false;
}
}
return true;
}
特殊场景处理
循环引用处理
function deepCloneWithCircular(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (hash.has(obj)) {
return hash.get(obj);
}
const cloneObj = Array.isArray(obj) ? [] : {};
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepCloneWithCircular(obj[key], hash);
}
}
return cloneObj;
}
const obj = { a: 1 };
obj.self = obj;
const cloned = deepCloneWithCircular(obj);
函数和特殊对象的拷贝
function cloneFunction(func) {
const body = func.toString();
if (body.match(/^function/)) {
return new Function('return ' + body)();
} else {
return new Function('return function ' + body)();
}
}
function cloneRegExp(regexp) {
const flags = '';
flags += regexp.global ? 'g' : '';
flags += regexp.ignoreCase ? 'i' : '';
flags += regexp.multiline ? 'm' : '';
return new RegExp(regexp.source, flags);
}
性能考量
不同拷贝方式的性能差异:
// 性能测试
const largeObj = {};
for (let i = 0; i < 10000; i++) {
largeObj['key' + i] = { value: Math.random() };
}
console.time('JSON方法');
const copy1 = JSON.parse(JSON.stringify(largeObj));
console.timeEnd('JSON方法');
console.time('递归深拷贝');
const copy2 = deepClone(largeObj);
console.timeEnd('递归深拷贝');
console.time('lodash深拷贝');
const copy3 = _.cloneDeep(largeObj);
console.timeEnd('lodash深拷贝');
实际应用场景
React中的浅比较
React的PureComponent和React.memo使用浅比较来优化性能:
class MyComponent extends React.PureComponent {
render() {
return <div>{this.props.value}</div>;
}
}
// 函数组件
const MemoizedComponent = React.memo(function MyComponent(props) {
return <div>{props.value}</div>;
});
Redux中的不可变更新
Redux要求状态不可变,需要使用正确的拷贝方式:
// 错误的方式 - 直接修改
state.someProperty.someValue = newValue;
// 正确的方式 - 创建新对象
return {
...state,
someProperty: {
...state.someProperty,
someValue: newValue
}
};
深度比较的应用
在复杂状态比较时可能需要深度比较:
// 自定义比较函数
const areEqual = (prevProps, nextProps) => {
return deepEqual(prevProps.data, nextProps.data);
};
const MemoizedDataComponent = React.memo(DataComponent, areEqual);
浏览器API中的拷贝
现代浏览器提供了一些新的拷贝API:
// 结构化克隆算法
const original = { a: 1, date: new Date() };
const cloned = structuredClone(original);
// MessageChannel也可以用于深拷贝
function deepCloneUsingMessageChannel(obj) {
return new Promise(resolve => {
const { port1, port2 } = new MessageChannel();
port2.onmessage = ev => resolve(ev.data);
port1.postMessage(obj);
});
}