什么是深拷贝、什么是浅拷贝?
-
深拷贝
修改新变量的值不会影响原有变量的值
默认情况下基本数据类型都是深拷贝 -
浅拷贝
修改新变量的值会影响原有变量的值
默认情况下引用类型都是浅拷贝
深拷贝
深拷贝中新对象会开辟新的内存,保存的是上一个对象中的值,修改时只会修改自身的值,对原对象没有影响
// 深拷贝
let num1 = 123;
let num2 = num1;
num2 = 456;
console.log(`num1: ${num1} num2: ${num2}`); // num1: 123 num2: 456
浅拷贝
浅拷贝中新对象会开辟新的内存,保存的不是上一个对象中的值,而是上一个对象的内存地址,修改时会根据内存地址修改对象
// 浅拷贝
class Person {
name = 'Tom';
age = 18;
}
let p1 = new Person();
let p2 = p1;
p2.name = '李雷';
console.log(`p1: ${p1.name} p2: ${p2.name}`); // p1: 李雷 p2: 李雷
如何实现对象的深拷贝?
对象的深拷贝就是在拷贝对象的时候,只拷贝对象的值,而不是拷贝对象的内存地址,等于重新开辟一个内存来存放拷贝的内容,这样修改拷贝对象的值时,不会对原对象产生影响
// 对象的深拷贝
class Person {
name = 'Tom';
age = 18;
}
let p1 = new Person();
// 方法一
let p2 = new Object();
p2.name = p1.name;
p2.age = p1.age;
p2.name = '李雷';
console.log(`p1: ${p1.name} p2: ${p2.name}`); // p1: Tom p2: 李雷
// 方法二
for (let key in p1) {
p2[key] = p1[key];
}
p2.name = '韩梅梅';
console.log(`p1: ${p1.name} p2: ${p2.name}`); // p1: Tom p2: 韩梅梅
// 方法三
Object.assign(p2, p1);
p2.name = '李雷';
console.log(`p1: ${p1.name} p2: ${p2.name}`); // p1: Tom p2: 李雷
弊端
上面三种深拷贝方法有弊端,当对象中存在引用数据类型时就不能完全深拷贝了
class Person {
name = 'Tom';
dog = { name: '小柴'};
scores = [ 78, 69, 88 ];
}
let p1 = new Person();
let p2 = new Object();
p2.name = p1.name;
p2.name = '李雷';
console.log(`p1: ${p1.name} p2: ${p2.name}`); // p1: Tom p2: 李雷
// 修改引用类型字段的值,会改变原数据
p2.dog = p1.dog;
p2.dog.name = '富贵';
console.log(`p1: ${p1.dog.name} p2: ${p2.dog.name}`); // p1: 富贵 p2: 富贵
// 修改引用类型字段的值,会改变原数据
p2.scores = p1.scores;
p2.scores[0] = 0;
console.log(`p1: ${p1.scores} p2: ${p2.scores}`); // p1: 0,69,88 p2: 0,69,88
通过自定义方法实现真正的深拷贝
// 通过自定义方法实现真正的深拷贝
class Person {
name = 'Tom';
dog = { name: '小柴'};
scores = [ 78, 69, 88 ];
}
let p1 = new Person();
let p2 = new Object();
deepClone(p2, p1);
p2.scores[0] = 0;
console.log(`p1: ${p1.scores} p2: ${p2.scores}`); // p1: 78,69,88 p2: 0,69,88
function deepClone(target, source) {
// 通过遍历拿到source中所有属性
for (let key in source) {
// 取出当前遍历到的属性的取值
let sourceValue = source[key];
// 判断当前取值是否为引用数据类型
if (sourceValue instanceof Object) {
// console.log(sourceValue.constructor);
// console.log(new sourceValue.constructor);
let subTarget = new sourceValue.constructor;
target[key] = subTarget;
deepClone(subTarget, sourceValue)
} else {
target[key] = sourceValue
}
}
}
// 优化一下,只要传一个源对象就行了,兼容对象和数组
function deepCopy(inObj, outObj) {
outObj = outObj || Array.isArray(inObj) ? [] : {};
// 通过遍历拿到source中所有属性
for (let key in inObj) {
// 取出当前遍历到的属性的取值
let currentValue = inObj[key];
// 判断当前取值是否为引用数据类型
if (currentValue instanceof Object) {
let subOutObj = new currentValue.constructor;
outObj[key] = subOutObj;
deepCopy(currentValue, subOutObj);
}
else {
outObj[key] = currentValue;
}
}
return outObj;
}
// 简化一下代码
function deepCopy(inObj) {
let outObj = Array.isArray(inObj) ? [] : {};
for (let key in inObj) {
let currentValue = inObj[key];
if (currentValue instanceof Object) {
outObj[key] = deepCopy(currentValue);
}
else {
outObj[key] = currentValue;
}
}
return outObj;
}
// 如果你不想复制继承过来的属性的话,上面的方法还不够完美
class Person {
name = 'Tom';
dog = { name: '小柴'};
scores = [ 78, 69, 88 ];
}
let p = new Person();
// 给原型对象增加属性
p.__proto__.sister = '小花';
Person.prototype.brother = '小明';
// 属性会继承到实例对象中
console.log(p.sister); // '小花'
console.log(p.brother); // '小明'
// 用上面的方法克隆会把继承属性克隆下来
let cloneObj = deepCopy(p);
console.log(cloneObj);
/*
{
brother: "小明"
dog: {name: "李白"}
name: "Tom"
scores: (3) [78, 69, 88]
sister: "小花
}
*/
// 这里对上面的方法优化一下,在遍历属性时,用hasOwnProperty判断是自身属性还是继承属性
function deepCopy(inObj) {
let outObj = Array.isArray(inObj) ? [] : {};
for (let key in inObj) {
if (inObj.hasOwnProperty(key)) {
let currentValue = inObj[key];
if (currentValue instanceof Object) {
outObj[key] = deepCopy(currentValue);
}
else {
outObj[key] = currentValue;
}
}
}
return outObj;
}
let cloneObj = deepCopy(p);
console.log(cloneObj);
/*
{
dog: {name: "李白"}
name: "Tom"
scores: (3) [78, 69, 88]
}
*/
// 这里会有一个很特殊的情况,JavaScript 并没有保护 hasOwnProperty 这个属性名,所以你可以在构造函数里自己建一个私有的hasOwnProperty属性,这时就不能用hasOwnProperty来判断是否继承属性了
class Person {
name = 'Tom';
dog = { name: '小柴'};
scores = [ 78, 69, 88 ];
hasOwnProperty = function() {
return true;
}
}
// 对上面的方法再一次优化,使用Object 原型上的 hasOwnProperty 属性
function deepCopy(inObj) {
let outObj = Array.isArray(inObj) ? [] : {};
for (let key in inObj) {
if (Object.prototype.hasOwnProperty.call(inObj, key)) {
let currentValue = inObj[key];
if (currentValue instanceof Object) {
outObj[key] = deepCopy(currentValue);
}
else {
outObj[key] = currentValue;
}
}
}
return outObj;
}
// 这里还有弊端是,没有考虑到Function、Date、RegExp这些情况,会返回{},如果明确数据只有基本类型,Object,Array用这种比较方便
// 更全面的类型判断,支持多种类型(Date、Map、Map、Function等)
function deepCopy(inObj) {
const type = Object.prototype.toString.call(inObj).slice(8, -1);
if (type === 'Object') {
let outObj = {};
for (let key in inObj) {
if (Object.prototype.hasOwnProperty.call(inObj, key)) {
let currentValue = inObj[key];
outObj[key] = deepCopy(currentValue);
}
}
return outObj;
}
else if (type === 'Array') {
return inObj.map(item => deepCopy(item));
}
else if (type === 'Function') {
return new Function(`return ${inObj.toString()}`).call(this);
}
else if (type === 'Date') {
return new Date(inObj.valueOf());
}
else if (type === 'RegExp') {
return new RegExp(inObj);
}
else if (type === 'Map') {
let map = new Map();
inObj.forEach((value, key) => {
map.set(key, deepCopy(value))
});
return map;
}
else if (type === 'Set') {
let set = new Set();
inObj.forEach((item) => {
set.add(item)
});
return set;
}
return inObj;
}
let person = {
name: 'Tom',
dog: { name: '小柴'},
scores: [ 78, 69, 88 ],
map: new Map([["key1", "value1"], ["key2", "value2"]]),
set: new Set([1, 2, 3, 4, 5]),
say: function(word) {
console.log(`${this.name}: ${word}`)
},
};
let newPerson = deepCopy(person);
console.log(newPerson);
person.say('吃饭了吗?');
newPerson.say('吃了')
Object.prototype.toString
class Person {
name = 'Tom';
dog = { name: '小柴'};
}
let p = new Person();
console.log(Object.prototype.toString.call({})); // [object Object]
console.log(Object.prototype.toString.call(p)); // [object Object]
console.log(Object.prototype.toString.call('123')); // [object String]
console.log(Object.prototype.toString.call(new Date)); // [object Date]
console.log(Object.prototype.toString.call(Math)); // [object Math]
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
console.log(Object.prototype.toString.call(new Map())); // [object Map]
console.log(Object.prototype.toString.call(new Set())); // [object Map]
console.log(Object.prototype.toString.call(/[a-z]/)); // [object RegExp]
console.log(Object.prototype.toString.call(new Function())); // [object Function]
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call(null).slice(8, -1)); // Null