JS中对象/数组的深浅拷贝
// 对象(数组)的深克隆和浅克隆
var obj = {
a: 100,
b: [10,20,30],
c: {
x:10
},
d: /^\d+s/,
e: null,
f: undefined,
g: function(){},
h: new Date(),
i: Symbol('A'),
};
let arr = [10, [100,200], {x: 10, y: 20}];
// 浅克隆:只是最外面的对象obj本身被克隆了一份,但是对象里面的引用数据类型值,还是共用的,一个改了,另一个也会跟着变
// 方案一:使用ES6中的展开运算符
let newObj = {...obj};
// 方案二:使用 Object.assgin来实现浅克隆
// Object.assgin(obj1, obj2):将obj2合并到obj1上,并返回obj1
let newObj2 = Object.assign({}, obj);
// 方案三:用 for in 循环
// for in循环在遍历对象的时候,遍历的是当前对象课枚举(列举)的属性
// + 私有属性(出一下特殊的内置属性是不可枚举的,比如数组的length属性)
// + 公有属性(大部分都是不可枚举的,但是自己在原型上扩展的一般是可枚举的)
// + ...
// 也说明了在遍历的过程中,很有可能会遍历到公有的属性/方法,所以使用for in 循环的时候需要判断属性是否为私有
let newObj3 = {};
for(let key in obj) {
if(!obj.hasOwnProperty(key)) break;
newObj3[key] = obj[key];
}
// console.log(obj, newObj); // 两个对象长得一样
// console.log(obj === newObj); // false 是两个不同的堆地址
// console.log(obj.b === newObj.b); // true 对象中的引用类型堆地址一样
// newObj.b.push(40);
// console.log(obj.b); // [10,20,30,40]
// 数组浅克隆
// 方法一:展开运算符
let newArr = [...arr];
// 方案二:使用Object.assgin
let newArr2 = Object.assign([], arr);
// 方案三:各种循环的迭代
let newArr3 = arr.map(item => item);
newArr3 = arr.slice();
newArr3 = arr.concat([]);
// console.log(arr, newArr); // 长得一样
// console.log(arr === newArr); // false
// console.log(arr[1] === newArr[1]); // true
// 深克隆:不仅把最外面的对象克隆一份,同时还把对象中的引用数据类型的值也克隆一份,即所有引用类型值克隆之后是独立的(堆地址不同),改了其中一个,不会影响另外一个
// 方案一:先把对象/数组整体变为字符串,再把字符串变为对象/数组
// 对象变为字符串:JSON.stringify([obj])
// 把json字符串转为对象:JSON.parse([string])
let newArr = JSON.parse(JSON.stringify(arr)); // 深拷贝数组
console.log(arr, newArr); // 长得一样
console.log(arr === newArr); // false
console.log(arr[1] === newArr[1]); // false
newArr[1].push(99);
console.log(arr[1], newArr[1]); // [100, 200] [100,200,99]
// 深拷贝对象
let newObj = JSON.parse(JSON.stringify(obj));
console.log(obj, newObj);
/*
* 进行拷贝时有问题:
这种办法存在BUG:把对象中的某些属性值变为字符串的时候,会存在问题
+ 正则变为 {}
+ 日期对象变为日期字符串
+ 属性值为 function/undefined/Symbol/BigInt 的属性会消失(直接不拷贝)
所以这种方法适用于数据值为 “number/null/boolean/普通对象/数组对象”等内容的对象/数组
*/
// 方案二:深层遍历克隆每一层(自实现)
function cloneDeep(obj) {
// 判断obj的类型:如果不是object类型,且不为null的,则直接返回
if(typeof obj !=='object') return obj;
if(obj === null) return null;
// 否则获取其构造函数 constructor
let constructor = obj.constructor;
if(constructor.name === 'RegExp') { // 如果是正则就创建一个正则实例
return new constructor(obj);
}
let clone = new constructor;
for(let key in obj) {
if(!obj.hasOwnProperty(key)) break; // 判断是不是私有属性
// 为了避免对象中的某个属性用的还是这个对象,导致的循环嵌套(死递归)
if(obj === obj[key]) {
clone[key] = obj[key];
break;
}
clone[key] = cloneDeep(obj[key]);
}
return clone;
}
let newObj = cloneDeep(obj)
console.log(obj, newObj);
console.log(obj.g === newObj.g); // true 因为没有对函数进行处理(感觉没必要单独克隆函数)
console.log(typeof obj.h, typeof newObj, obj.h === newObj.h); // 'object' 'object' false