Skip to main content

浅拷贝和深拷贝

在 JavaScript 中拷贝一个对象意味着创建一个与原始对象具有相同属性的新对象。 JavaScript 中的对象是通过引用存储的,这意味着两个变量可以指向内存中的同一个对象。 修改一个对象变量会影响其他变量。

let person1 = {
name: '张三',
age: 18,
sex: '男'
}
let person2 = person1;
person2.name = '李四';
console.log(person1.name); // 李四
console.log(person2.name); // 李四

显然上面的做法并不是拷贝,因为拷贝的目的是复制数据,以便我们可以在不影响原始对象的情况下修改对象。那么我们应该如何将一个对象中的属性复制到另外一个对象。答案是我们可以使用循环遍历,把原始对象的值一个一个放入到目标对象中。

let person1 = {
name: '张三',
age: 18,
sex: '男'
}
let person2 = {};
for (let key in person1) {
person2[key] = person1[key];
}
person2.name = '李四';
console.log(person1); // {name: '张三', age: 18, sex: '男'}
console.log(person2); // {name: '李四', age: 18, sex: '男'}

但是上面的方法会有一个问题是,如果person1的原型链上有个属性的话就会把这个属性也遍历到person2中

let person1 = {
name: '张三',
age: 18,
sex: '男'
}
Object.prototype.son = '小明';
let person2 = {};
for (let key in person1) {
person2[key] = person1[key];
}
person2.name = '李四';
console.log(person1); // {name: '张三', age: 18, sex: '男'}
console.log(person2); // {name: '李四', age: 18, sex: '男', son: '小明'}

所以我们在遍历的时候要做个判断,只能遍历原始对象自己的属性。

let person1 = {
name: '张三',
age: 18,
sex: '男'
}
Object.prototype.son = '小明';
let person2 = {};
for (let key in person1) {
if (person1.hasOwnProperty(key)) { // 判断是不是对象自己的属性
person2[key] = person1[key];
}
}
person2.name = '李四';
console.log(person1); // {name: '张三', age: 18, sex: '男'}
console.log(person2); // {name: '李四', age: 18, sex: '男'}

以上就是浅拷贝的一种方法。

那么以上的拷贝方法只能处理对象的一层属性,万一对象的属性有嵌套又会出现什么情况呢?我们可以把上面的方法封装成一个函数来调用试试看。

浅拷贝(ShallowClone)方法1:循环遍历法

let person1 = {
name: '张三',
age: 18,
sex: '男',
children: {
son1: {
name: '张小1',
age: 5,
},
son2: {
name: '张小2',
age: 7,
},
},
car: ['Benz', 'Mazda']
}

let clone = (origin, target) => {
let tar = target || {};
for (let key in origin) {
if (origin.hasOwnProperty(key)) {
tar[key] = origin[key];
}
}
return target;
}

let person2 = clone(person1);
person2.children.son2.name = '张小二';
console.log(person1); // {"name":"张三","age":18,"sex":"男","children":{"son1":{"name":"张小1","age":5},"son2":{"name":"张小二","age":7}},"car":["Benz","Mazda"]}
console.log(person2); // {"name":"张三","age":18,"sex":"男","children":{"son1":{"name":"张小1","age":5},"son2":{"name":"张小二","age":7}},"car":["Benz","Mazda"]}

我们可以发现一旦我们修改了person2嵌套对象内的属性时,person1里面对应的值也会跟着修改。这就是浅拷贝导致的问题,只拷贝了原始对象的一层属性。所以我们要使用深拷贝解决问题。

浅拷贝(ShallowClone)Spread Operator or Object.assign()

// Spread Operator
let obj1 = {a: 1, b: 2}
let obj2 = {...obj1, c: 3};
console.log(obj2); // {a: 1, b:2, c:3}

// Object.assign()
let obj1 = {a: 1, b: 2}
let obj2 = Object.assign(obj1, {c: 3});
console.log(obj2); // {a: 1, b:2, c:3}

深拷贝(DeepClone)方法1:递归调用法

let person1 = {
name: '张三',
age: 18,
sex: '男',
children: {
son1: {
name: '张小1',
age: 5,
},
son2: {
name: '张小2',
age: 7,
},
},
car: ['Benz', 'Mazda']
}

let deepClone = (origin, target) => {
let tar = target || {};
for (let key in origin) {
let toStr = Object.prototype.toString;
let arrayType = '[object Array]';
if (origin.hasOwnProperty(key)) {
if (typeof origin[key] === 'object') {
if (toStr.call(origin[key]) === arrayType && origin[key] !== null) {
tar[key] = []
} else {
tar[key] = {}
}
deepClone(origin[key], tar[key])
} else {
tar[key] = origin[key];
}
}
}
return tar;
}

let person2 = deepClone(person1);
person2.children.son2.name = '张小二';
person2.car.push('BMW');
console.log(person1); // {"name":"张三","age":18,"sex":"男","children":{"son1":{"name":"张小1","age":5},"son2":{"name":"张小2","age":7}},"car":["Benz","Mazda"]}
console.log(person2); // {"name":"张三","age":18,"sex":"男","children":{"son1":{"name":"张小1","age":5},"son2":{"name":"张小二","age":7}},"car":["Benz","Mazda","BMW"]}

深拷贝(DeepClone)方法2:JSON.stringify();

虽然 JSON.parse(JSON.stringify()) 很简单,但也有很多注意事项。 如果您的对象仅包含原始值、对象和数组,则使用此模式可以正常工作。 但是一旦你引入了像 Date 这样的类,这种模式实际上就无法克隆对象,因为 JSON.stringify() 将日期转换为字符串。更多注意事项JSON.stringify()

let person1 = {
name: '张三',
age: 18,
sex: '男',
children: {
son1: {
name: '张小1',
age: 5,
},
son2: {
name: '张小2',
age: 7,
},
},
car: ['Benz', 'Mazda']
}

let person2 = JSON.parse(JSON.stringify(person1));
person2.children.son2.name = '张小二';
person2.car.push('BMW');
console.log(person1); // 输出同上个示例
console.log(person2); // 输出同上个示例