js对象克隆

153 阅读6分钟

在js中对象克隆就是把对象值重新赋给一个新对象。克隆也存在深克隆于浅克隆的区别。引用类型的值都会被保存在堆内存中,在栈内存中会存在一个指针指向堆内存中的值。这时,如果只复制了指针,则可以说这个克隆为浅克隆,如果时根据指针找到具体的值,复制值,就可以称之为深克隆。

2、对象克隆的几种方法:

1、JSON

JSON多用于前后台的数据交互,后台传递字符串类型的JSON数据,前端通过JSON.parse() ,可以转化为一个json对象。 现有一个对象,可以通过JSON.stringify() 转为json字符串。字符串类型的值保存在栈内存当中,所以可以直接赋值达到深度克隆的结果。最后通过JSON.parse(),就可以实现深度克隆了。

var obj = {
  x: 1, 
  y: { 
    a: 1, 
    b: 0, 
    c: [1, 2, 3] 
    } 
    };
  // 直接等于号赋值,如果是基本类型的数据,这样足够达到深度克隆
var obj2 = obj;
console.log(obj2 == obj); //true 直接复制只是复制对象的指针,还指向同一个对象

// 转化为了字符串
var obj3 = JSON.parse(JSON.stringify(obj));
console.log(obj3 == obj) //false  通过json方法复制后的地址不一样
console.log(obj3); // 达到深度克隆
复制代码

2、Object.create()

Object.create()用于把一个对象用于另一个对象的原型,这种以原型的方式来设置一个对象在某个方面也算的上是一种克隆,也可以间接的达到目的。但是不建议,我认为某些方面来说算不上克隆。

var obj = {
    x: 1,
    y: {
        a: 1,
        b: 0,
        c: [1, 2, 3]
    }
};

var obj2 = Object.create(obj);
console.log(obj2 == obj); //false
console.log(obj === obj2.__proto__); // true
console.log(obj2); // 输出{}
复制代码

*因为只是把对象设置为新对象的原型,所以新对象不会和原来的对象相等,但是原对象会与新对象的原型相等。一般来说对克隆出来的对象作修改,被克隆的对象没有被修改,就可以判定是一个深克隆出来的对象。但是要是去修改克隆出来的对象的原型中的属性,肯定会影响被克隆的对象,这样来看,又不像深克隆。所以是不是深克隆,界限不是很明确。不通过 _ _ proto_ _ 去访问原型上的对象进行修改也不会改变被克隆对象。

3、…运算符

let obj1 = {
    a : 1,
    b : '2',
    c : true,
    d : {
        x : 'd'
    }
}
let obj2 = {...obj1};
console.log(obj1 === obj2); // false

obj2.d.x = null; // 修改克隆出来的对象的属性d的属性x
console.log(obj1, obj2);
复制代码

如果对象的属性还是对象,那么就会出现意外,对象的属性都是基本类型的值的话,还是深度克隆

4、assign

assign为ES7中的方法。方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。 这个方法就是一个纯浅克隆。

let obj1 = {
    a : 1,
    b : '2',
    c : true,
    d : {
        x : 'd'
    }
}
let obj2 = Object.assign(obj1);
console.log(obj1 === obj2); // true
复制代码

5、通用

Function.prototype.clone = function(obj, newObj = {}){// newObj是声明对象保存克隆对象

    // 对传进来的数据进行限制
    if(!(typeof obj == "function" || typeof obj == "object")){
        throw new Error(`obj must be a function or object`);
    }
    // 如果传进来的是个空对象或者空数组  则直接返回
    if(!(obj == undefined || obj == null)){
        if(Object.keys(obj).length == 0) return obj instanceof Array ? [] : {};
    } else {
        return;
    }
    

    // 遍历传进来的obj
    for (const key in obj) {
        // 判断当前属性是否是对象的属性(也就是不克隆原型上的属性)
        if (obj.hasOwnProperty(key)) { 
            const element = obj[key];

            // 判断当前属性值是引用数据类型还是基本数据类型
            if(typeof element == "function" || typeof element == "object"){

                // 进一步判断是那种引用类型然后进行赋值
                // 使用toString()
                let tostring = Object.prototype.toString;
                if(toString.call(obj[key]) == "[object Object]"){ // object类型
                    newObj[key] = new Object();
                } else if (tostring.call(obj[key]) == "[object RegExp]"){ // 正则类型
                    newObj[key] = new RegExp();
                } else if (tostring.call(obj[key]) == "[object Array]"){ // 数组类型
                    newObj[key] = new Array();
                }

                // 递归,当有数组或者对象的时候继续往里面深入,重复调用当前的函数
                Function.clone(obj[key], newObj[key]);

            } else {
                // 原始数据类型直接赋值即可
                newObj[key] = element;
            }

        }
    }

    return newObj;

}

let obj = {
    a : 1,
    1 : 'string',
    2 : true,
    b : null,
    c : undefined,
    innerobj : {
        innera : 1,
        inner1 : 'string',
        inner2 : true,
        innerb : null,
        innerc : undefined,
    },
    arr : [1,2,3,4,{arra : 1}],
    reg : new RegExp('a'),
    fun : function(){
        let funa = 1;
        let funarr = [1,2,3];
        let reg = /^a$/;
    }
}

复制代码

3、深度克隆和浅克隆

1、深度克隆:

对象的深克隆就不会出现浅克隆的那种情况,它会递归(完全)的克隆整个对象,当对象的属性为一个对象或则数组时它会对改属性实现克隆而不是像浅克隆那样赋值引用。所以当一个对象被深克隆之后它是完全独立于被克隆对象的。即他们二者的不存在任何关联。

const obj = {
    name: '张三',
    age: 24,
    job: '前端开发',
    hobbies: ['basketball', 'music', 'movies', 'cooking'],
    dog: {
        name: 'wangcai'
    },
    test: () => {
        console.log('obj');
    },
    a: undefined
}

// 深克隆
const deepClone = (origin, target = {}) => {
    // 用对象的toString方法判断属性的类型
    const toString = Object.prototype.toString;
    const arrType = '[object Array]';
    for (let key in origin) {
        if (origin.hasOwnProperty(key)) { //只克隆对象本身上的属性
            // 判断对象的属性是否为引用值:Object
            if (typeof origin[key] === 'object' && origin[key] !== null) {
                target[key] = toString.call(origin[key]) === arrType ? [] : {};
                // 递归。继续克隆该属性指直到该属性的值不再是引用值
                deepClone(origin[key], target[key]);
            } else {
                target[key] = origin[key];
            }
        }
    }
    return target;
}
const deepObj = deepClone(obj);
obj.dog.name = 'xiaohua';
console.log(obj.dog.name) // xiaohua
console.log(deepObj.dog.name); // wangcai

复制代码

2、浅克隆

浅克隆之所以被称为浅克隆,是因为对象只会被克隆最外部的一层,至于更深层的对象,则依然是通过引用指向同一块堆内存. 对象的浅克隆只是会克隆对象的第一层属性。当被克隆对象的属性为一个对象或则数组时。浅克隆是不会对该属性进一步克隆而是会赋值改属性的引用。即当修改了其中一个对象的属性的值时另外的那个对象的对应值也会跟着发生变化。

const obj = {
    name: '李四',
    age: 24,
    job: '前端开发',
    hobbies: ['basketball', 'music', 'movies', 'cooking'],
    dog: {
        name: 'wangcai'
    },
    test: () => {
        console.log('obj');
    },
    a: undefined
}

// 浅克隆
const shallowClone = (origin, tar = {}) => {
    for (let key in origin) {
        if (origin.hasOwnProperty(key)) { // 克隆对象只克隆对象本身的属性,忽略对象原型上的属性
            tar[key] = origin[key];
        }
    }
    return tar;
}

const newObj = shallowClone(obj);

obj.dog.name = 'xiaohua'; // 更改obj对象中dog中的name属性
console.log(newObj.dog.name); // xiaohua


作者:咖啡l
链接:juejin.cn/post/702841… 来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。