JS深克隆浅克隆

1,503 阅读1分钟

深克隆浅克隆

浅度克隆:原始类型为值传递,对象类型为引用传递。浅克隆的对象的引用值是拷贝对象里的引用,这两个对象里面的引用(如对象里的数组或者内嵌对象)指向的地方是一致的。

深度克隆:所有对象的属性和值均全部复制,即所有新对象的修改不会影响原对象。

有一个数组,包括基本类型,对象和正则。现在对数组进行克隆。

 let arr1 = [10, 20, {name: '小宝贝'}, /^\d+$/, function () {}]; 

浅克隆

  1. 浅克隆: 只把第一级克隆一份过来,第二级及以后和原始对象公用相同的堆地址。
  2. 比较常见的方法:...(展开运算符)sliceObject.assign({}, obj)
  3. 函数的克隆通过浅克隆就可以实现,并不影响原函数。因为函数克隆会在内存中单独开辟一片空间,互不影响。
let arr2 = [...arr1];
console.log(arr1,arr2);
arr1 === arr2   //false
arr1[2] === arr2[2]   //true
let arr2 = arr1.slice(0);
function shallowCopy(target, origin){ 
    return Object.assign(target, origin); 
}

深克隆

常见方法

1. JSON.parse(JSON.stringify())

  1. 基于JSON方法,先把原始对象转换为字符串,再把字符串重新定义为对象,此时实现了内容的深度克隆。
  2. 问题:如果对象中的某一项值是正则或者函数,基于JSON.stringifyJSON.parse(首先将对象序列化为JSON字符串,再将JSON字符串反序列化为对象)处理后就不在是正则(变为空对象)或者函数(变为null)了。
  3. 能正确处理的对象只有NumberStringArray等能够被json表示的数据结构,因此函数这种不能被json表示的类型将不能被正确处理。
let arr2 = JSON.parse(JSON.stringify(arr1));
arr1 === arr2       //false
arr1[2] === arr2[2]         //false

2. 自己写一个深克隆

  1. 思路:

    1. 把浅克隆的框架先写出来,然后再在上面添加条件判断。
    2. 接下来判断属性是否null, 基本类型,function,正则,日期,做相应的处理。
    3. 判断源对象的上的属性是否属于对象上,而不是原型上,因为我们只是要克隆对象上的属性。
    4. 然后用递归的方式,把该属性作为源对象调用deep函数。
    5. 最后一步要记得返回newObj对象
  2. 要点

    1. 递归
    2. 判断类型
    3. 检查环(也叫循环引用)
    4. 需要忽略原型
function deep(obj){

//判断类型
    // 如果是null直接返回null
    if (obj === null) return null;

    // 如果是基本数据值或者函数,也直接返回即可(函数无需克隆处理)
    // typeof [] => 'object'
    // typeof {} => 'object'
    if (typeof obj !== 'object') return obj;

    // 如果是正则, 把当前正则复制(克隆)一份
    if (_type(obj) === '[object RegExp]') return new RegExp(obj); // 创建一个正则
    // 如果是日期格式的数据
    if (_type(obj) === '[object Date]') return new Date(obj);

    // 如果是数组或者对象
    // 构造一个新的obj
                        
    // obj.constructor:找到的是所属类原型上的constructor,而原型上的constructor指向的是当前类本身
    // =>保证传递进来什么类型的值,我们最后创建的newObj也是对应类型的
    let newObj = new obj.constructor;
    for (let key in obj) {
        console.log(key)
        if (obj.hasOwnProperty(key)) { 
        //in 不知会判断当前对象中是否包含此属性 还会向原型中查找是否包含此属性
        //hasOwnProperty 只会判断当前对象中是否包含此属性

        // 如果某一项的值是引用值吗,我们还需要进一步迭代循环,把引用值中的每一项也进一步克隆 =>深度克隆
            newObj[key] = deep(obj[key]);
        }
    }
    return newObj;

}

function _type(value) { // 返回数据类型
    return Object.prototype.toString.call(value);
}

然后测试一下

let arr1 = [10, 20, {
    name: '小红'
}, /^\d+$/, function () {console.log(123)}];

let arr2 = deepe(arr1);
console.log(arr2);
arr2[1]=30
arr2[2].name='test'
arr2[3]='string'
console.log(arr1); // 还是原来的
console.log(arr2);

另外一个例子

let obj1 = {
			name: '小明',
			ke: ['语文', '数学', '英语'],
			color: {
				n: 'red',
				m: 'blue'
			}
		};
let obj2 = deep(obj1);
console.log(obj2);

obj2.ke.push('物理')
obj2.color['n']='yellow'
console.log(obj1); // 不会被影响,还是原来的
console.log(obj2); 

另外一种写法:先用循环读进来,再进行类型判断,只有object需要二次处理。前一种是先判断,再循环。

function _type(value) {
        return Object.prototype.toString.call(value);
}

function _deepClone(obj) {	// 必须保证OBJ是数组或者对象等
    let newObj = new obj.constructor;
    for (let key in obj) {
        if (!obj.hasOwnProperty(key)) {
            let item = obj[key],
            itemType = _type(item);
            if (item !== null && typeof item === "object") { // 不为空,且不是基本类型或函数
                if (/(RegExp|Date)/.test(itemType)) { //  只要包含RegExp,Date中的一个
                    newObj[key] = new item.constructor(item);
                    continue;
                 }
                // 只有对象才需要深层次克隆处理
                newObj[key] = _deepClone(item);
                continue;
            }
            newObj[key] = item;
        }
    }
    return newObj;
}