浅拷贝
浅拷贝很容易,只需要使用赋值运算符(=)即可
var obj = { age: '12' };
var newObj = obj;
//此时将obj中的age属性值改变,会发现newObj中的age属性值跟着改变
obj.age = '13'; //obj: { age: '13' } newObj: { age: '13'}
原因:浅拷贝只是对对象的引用地址进行了拷贝,并没有开辟新的堆栈,拷贝后,两个对象指向的是同一个引用地址,所以修改其中一个对象的属性,则另一个对象的属性也会改变。
网上有流传Object.assign
能够实现对象的深拷贝、slice
和concat
能够实现数组的深拷贝,经过测试,这些都只是披着深拷贝的外衣的浅拷贝,如何理解这句话呢,如果该对象属性的值并没有引用类型时,这三个方法均能实现深拷贝。一旦对象属性的值为引用类型时,只会对该值的引用地址进行拷贝,这就回到的我上边所说的浅拷贝的问题。
tips: slice和concat只能实现数组, Object.assign实现对象
var obj = { age: '11', parent: { father: 'XX', mother: 'XX' } };
var newObj = Object.assign({}, obj); //将obj中的参数复制到{}中,故参数顺序不能反
obj.age = '12';
obj.parent['father'] = 'YY';
console.log(obj, newObj); //obj: { age: '12', parent: { father: 'YY', mother: 'XX' } } newObj: { age: '11', parent: { father: 'YY', mother: 'XX' }
深拷贝
那么如何实现真正的深拷贝呢?
1、JSON.parse方法(参照作者:寻找海蓝96,来源:https://juejin.im/post/5abb55ee6fb9a028e33b7e0a)
网上流传的一个黑科技,使用var newObj = JSON.parse(JSON.stringify(obj))
可以实现对象的深拷贝,但是该方法存在一些问题:
1、无法作用于函数、RegExp等特殊对象,不过对函数进行深拷贝,我觉得并不需要,如果有需要对函数进行深拷贝用途的地方,希望大家不吝赐教,在评论区点出,让我这个萌新也能多了解了解。
2.会抛弃对象的constructor,所有的构造函数会指向Object.
3.对象有循环引用,会报错
主要的坑就是以上几点,我们一一测试下.
// 构造函数
function person(pname) {
this.name = pname;
}
const Messi = new person('Messi');
// 函数
function say() {
console.log('hi');
};
const oldObj = {
a: say,
b: new Array(1),
c: new RegExp('ab+c', 'i'),
d: Messi
};
const newObj = JSON.parse(JSON.stringify(oldObj));
// 无法复制函数
console.log(newObj.a, oldObj.a); // undefined [Function: say]
// 稀疏数组复制错误
console.log(newObj.b[0], oldObj.b[0]); // null undefined
// 无法复制正则对象
console.log(newObj.c, oldObj.c); // {} /ab+c/i
// 构造函数指向错误
console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: Object] [Function: person]
2、自定义方式实现深拷贝
/*利用递归来实现深拷贝,如果对象属性的值是引用类型(Array,Object),那么对该属性进行深拷贝,直到遍历到属性的值是基本类型为止。 */
function deepClone(obj){
//因为null是基础类型,但是用typeof判断时,显示为object
//原理是因为是使用二进制判断,前三位为0则为object,而null,所有位数均为0,故显示为object
if(obj === null) return null;
if(typeof obj !== 'object') return obj;
if(obj.constructor===Date) return new Date(obj);
if(obj.constructor === RegExp) return new RegExp(obj);
var newObj = new obj.constructor (); //保持继承链
for (var key in obj) {
if (obj.hasOwnProperty(key)) { //不遍历其原型链上的属性
var val = obj[key];
newObj[key] = typeof val === 'object' ? arguments.callee(val) : val; // 使用arguments.callee解除与函数名的耦合
}
}
return newObj;
}
tips: 该方法无法实现function的深拷贝!!!
3、更精确的自定义方式实现深拷贝
const isType = (obj, type) => {
if (typeof obj !== 'object') return false;
const typeString = Object.prototype.toString.call(obj);
let flag;
switch (type) {
case 'Array':
flag = typeString === '[object Array]';
break;
case 'Date':
flag = typeString === '[object Date]';
break;
case 'RegExp':
flag = typeString === '[object RegExp]';
break;
default:
flag = false;
}
return flag;
};
/**
* deep clone
* @param {[type]} parent object 需要进行克隆的对象
* @return {[type]} 深克隆后的对象
*/
const clone = parent => {
// 维护两个储存循环引用的数组
const parents = [];
const children = [];
const _clone = parent => {
if (parent === null) return null;
if (typeof parent !== 'object') return parent;
let child, proto;
if (isType(parent, 'Array')) {
// 对数组做特殊处理
child = [];
} else if (isType(parent, 'RegExp')) {
// 对正则对象做特殊处理
child = new RegExp(parent);
if (parent.lastIndex) child.lastIndex = parent.lastIndex;
} else if (isType(parent, 'Date')) {
// 对Date对象做特殊处理
child = new Date(parent.getTime());
} else {
// 处理对象原型
proto = Object.getPrototypeOf(parent);
// 利用Object.create切断原型链
child = Object.create(proto);
}
// 处理循环引用
const index = parents.indexOf(parent);
if (index != -1) {
// 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象
return children[index];
}
parents.push(parent);
children.push(child);
for (let i in parent) {
// 递归
child[i] = _clone(parent[i]);
}
return child;
};
return _clone(parent);
};
tips: 对Buffer对象、Promise、Set、Map这些对象还未做特殊处理!!!