如何实现拷贝

2019-08-04  本文已影响0人  你喜欢吃青椒吗_c744

没理解深拷贝与浅拷贝的点这里

实现浅拷贝

Object.assign

ES6中拷贝对象的方法,第一个参数是新对象,剩下的参数是拷贝的源对象(可以是多个)

语法:Object.assign(target, ...sources)

var target = {};
var source = {a:1};
Object.assign(target ,source);
console.log(target); //{a:1}
source.a = 2;
console.log(source); //{a:2}
console.log(target); //{a:1}

看到这里是不是有些疑问。Object.assign不是浅拷贝吗,为什么
source.a = 2;我改变了原对象的数据,新对象的值却没改变呢?新旧对象指向的不是同一个内存地址吗?其实,在浅拷贝中,如果第一层的属性是基本类型,新值原值互不影响(就像深拷贝一样)。如果第一层的属性是复杂类型,那么拷贝的是它的栈内存的地址。在上面的例子中,对象的第一层为a:1,是基本数据类型,所以新旧对象不会互相影响。

扩展运算符

利用扩展运算符可以在构造字面量对象时,进行克隆或者属性拷贝

语法:var newObj= { ...Oldobj };

var obj = {a:1,b:{c:1}}
var obj2 = {...obj};
obj.a=2;
console.log(obj); //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}

obj.b.c = 2;
console.log(obj); //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}

原理和Object.assign()一样。这里不再赘述。但是如果属性都是基本类型的值的话,使用扩展运算符更加方便。

slice

语法: newarr = oldarr.slice();

var arr1 = [1, 2], arr2 = arr1.slice();
console.log(arr1); //[1, 2]
console.log(arr2); //[1, 2]

arr2[0] = 3; //修改arr2
console.log(arr1); //[1, 2]
console.log(arr2); //[3, 2]

concat

连接数组

let arr = [{a:1},{a:1},{a:1}]
let arr2 = [{b:1},{b:1},{b:1}]
let arr3 = arr.concat(arr2)
arr2[0].b = 123
console.log(arr3) //[{"a":1},{"a":1},{"a":1},{"b":123},{"b":1},{"b":1}]

第一层属性为复杂数据类型,所以拷贝的是栈内存的地址。原对象的值改变,新对象的值也会发生改变。

实现深拷贝

简单提一下什么是深拷贝:深拷贝则是对于复杂数据类型在堆内存中开辟了一块内存地址用于存放复制的对象并且把原有的对象复制过来,这2个对象是相互独立的,也就是2个不同的地址。

一个简单的深拷贝

var obj1 = {
    a: {
        b: 1
    },
    c: 1
};
var obj2 = {};

obj2.a = {}
obj2.c = obj1.c
obj2.a.b = obj1.a.b;
console.log(obj1); //{a:{b:1},c:1};
console.log(obj2); //{a:{b:1},c:1};
obj1.a.b = 2;
console.log(obj1); //{a:{b:2},c:1};
console.log(obj2); //{a:{b:1},c:1};

JSON.stringify

JSON.stringify()是目前前端开发过程中最常用的深拷贝方式,原理是把一个对象序列化成为一个JSON字符串,将对象的内容转换成字符串的形式再保存在磁盘上,再用JSON.parse()反序列化将JSON字符串变成一个新的对象。

var obj1 = {
    a:1,
    b:[1,2,3]
}
var str = JSON.stringify(obj1)
console.log(str) //{"a":1,"b":[1,2,3]}
var obj2 = JSON.parse(str)
console.log(obj2); //{a:1,b:[1,2,3]}
obj1.a=2
obj1.b.push(4);
console.log(obj1); //{a:2,b:[1,2,3,4]}
console.log(obj2); //{a:1,b:[1,2,3]}

通过JSON.stringify实现深拷贝有几点要注意

别人总结的深拷贝的方法

const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)

const deepClone = function (obj, hash = new WeakMap()) {

    if (obj.constructor === Date) return new Date(obj);   //日期对象就返回一个新的日期对象
    if (obj.constructor === RegExp) return new RegExp(obj);  //正则对象就返回一个新的正则对象

    //如果成环了,参数obj = obj.loop = 最初的obj 会在WeakMap中找到第一次放入的obj提前返回第一次放入WeakMap的cloneObj
    if (hash.has(obj)) return hash.get(obj)

    let allDesc = Object.getOwnPropertyDescriptors(obj);     //遍历传入参数所有键的特性
    let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc); //继承原型链

    hash.set(obj, cloneObj)

    for (let key of Reflect.ownKeys(obj)) {   //Reflect.ownKeys(obj)可以拷贝不可枚举属性和符号类型
        // 如果值是引用类型(非函数)则递归调用deepClone
        cloneObj[key] =
            (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ?
                deepClone(obj[key], hash) : obj[key];
    }
    return cloneObj;
};


let obj = {
    num: 0,
    str: '',
    boolean: true,
    unf: undefined,
    nul: null,
    obj: {
        name: '我是一个对象',
        id: 1
    },
    arr: [0, 1, 2],
    func: function () {
        console.log('我是一个函数')
    },
    date: new Date(0),
    reg: new RegExp('/我是一个正则/ig'),
    [Symbol('1')]: 1,
};

Object.defineProperty(obj, 'innumerable', {
    enumerable: false,
    value: '不可枚举属性'
});

obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))

obj.loop = obj

let cloneObj = deepClone(obj);

console.log('obj', obj);
console.log('cloneObj', cloneObj);


for (let key of Object.keys(cloneObj)) {
    if (typeof cloneObj[key] === 'object' || typeof cloneObj[key] === 'function') {
        console.log(`${key}相同吗? `, cloneObj[key] === obj[key])
    }
}

第三方库实现对象深拷贝

参考文章

对象深拷贝和浅拷贝

低门槛彻底理解JavaScript中的深拷贝和浅拷贝

上一篇 下一篇

猜你喜欢

热点阅读