JS对象的浅拷贝与深拷贝

一. 浅拷贝

  • 定义浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

  • 图示
    JS对象的浅拷贝与深拷贝_第1张图片

(1)普通对象

1.Object.assign

语法:

Object.assign(target, …sources),第一个参数是拷贝的目标对象,后面的参数是拷贝的来源对象(也可以是多个来源)。

        let a = { x: 99, arr: [1, 2] };
        let b = Object.assign({},a);
        console.log(b == a);//true
        b.x = 365;
        b.arr[0] = 100;
        console.log(b); //{x: 365, arr: [100, 2]}
        console.log(a); //{x: 99, arr: [100, 2]}

2.扩展运算符

语法:

let cloneObj = { …obj };

/* 对象的拷贝 */

const obj = {
  a: 1,
  b: {
    c: 1
  }
}

const 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}}

(2)数组

1.concat 拷贝数组

        const arr = [1, 2, 3, { a: 1 }];

        const newArr = arr.concat();

        newArr[1] = 0;

        newArr[3].a = 4;

        console.log(arr); // [ 1, 2, 3, {a: 4} ]

        console.log(newArr); // [ 1, 0, 3, {a: 4} ]

2.slice 拷贝数组

语法:

arr.slice(begin, end);

        const arr = [1, 2, { val: 4 }];

        const newArr = arr.slice();

        newArr[2].val = 5;
        newArr[1] = 3;

        console.log(arr);  //[ 1, 2, { val: 5 } ]
        console.log(newArr); //[ 1, 3, { val: 5 } ]

3.扩展运算符


let arr = [1, 2, 3, { a: 66 }];

let newArr = [...arr]; //跟arr.slice()是一样的效果

newArr[0] = 99;
newArr[3].a = 88;

console.log(arr); //[1, 2, 3, { a: 88 }]
console.log(newArr); //[99, 2, 3, { a: 88 }]

(3)手写浅拷贝

1.for in 循环

        const obj = {
            x: 'abc',
            y: {
                m: 1
            }
        };

        const result = clone2(obj);

        function clone2(target) {
            // 类型判断:{} [] null
            if (typeof target === 'object' && target !== null) {
                const res = Array.isArray(target) ? [] : {};
                // 遍历target数据
                for (let key in target) {
                    // 判断当前对象上是否包含该属性
                    if (target.hasOwnProperty(key)) {
                        // 将属性设置到result结果数据中
                        res[key] = target[key];
                    }
                }
                return res;
            } else {
                return target;
            }
        }
        result.x = 'xyz';
        result.y.m = 2;
        console.log(obj); //{x: 'abc', y: {m: 2}}
        console.log(result); //{x: 'xyz', y: {m: 2}}

2.扩展运算符

        const obj = {
            x: 'abc',
            y: {
                m: 1
            }
        };
        const result = clone1(obj);

        function clone1(target) {
            // 类型判断:{} [] null
            if (typeof target === 'object' && target !== null) {
                // 判断如果是数组
                if (Array.isArray(target)) {
                    return [...target];
                } else {
                    return { ...target };
                }
            } else {
                return target;
            }
        }
        result.x = 'xyz';
        result.y.m = 2;
        // 原对象obj也会受到影响
        console.log(obj); //{x: 'abc', y: {m: 2}}
        console.log(result); //{x: 'xyz', y: {m: 2}}

二. 深拷贝

  • 定义将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,并且在修改新对象不会影响原对象。
  • 图示
    JS对象的浅拷贝与深拷贝_第2张图片

1. JSON.stringify

步骤:把一个对象序列化成为 JSON 的字符串,将对象里面的内容转换成字符串,再用 JSON.parse() 的方法将JSON字符串生成一个新的对象。

注:此方法适用于普通对象和数组的拷贝。

  • 普通对象
        const obj1 = { a: 1, b: [1, 2, 3] }
        const str = JSON.stringify(obj1);
        // console.log(str); //{"a":1,"b":[1,2,3]}

        const obj2 = JSON.parse(str);
        // console.log(obj2);   //{a:1,b:[1,2,3]} 

        obj1.a = 2;

        obj1.b[0] = 111;

        console.log(obj1);   //{a:2,b:[111, 2, 3]}

        console.log(obj2);   //{a:1,b:[1,2,3]}
  • 数组
        const obj = [1, 2, 3, {x:99}];
        const str = JSON.stringify(obj);
        const obj2 = JSON.parse(str);

        obj2[0] = 100;
        obj2[3].x = 1000; 
        
        console.log(obj); //[1, 2, 3, {x: 99}]
        console.log(obj2); //[100, 2, 3, {x: 1000}]
  • JSON.stringify存在弊端:此处列举2个
        const obj = {
            x: 666,
            y: {
                m: 1
            },
            z: ['e', 'f', 'g'],
            // 弊端1:不能克隆方法
            d: function () { }
        };

        // 弊端2:不能循环引用
        // obj.z.push(obj.y);
        // obj.y.j = obj.z;

        const result = deepClone1(obj);

        function deepClone1(target) {
            // 通过数据创建JSON格式的字符串
            let str = JSON.stringify(target);
            // 将JSON字符串创建为js数据
            let data = JSON.parse(str);
            return data;
        }
        result.y.m = 1000;
        console.log(obj);//{x: 666, y: {m: 1}, z: ["e", "f", "g"], d: ƒ}
        console.log(result);//{x: 666, y: {m: 1000}, z: ["e", "f", "g"]}

2.手写深拷贝

初级方法:递归

注:【代码为初级版,不能解决循环引用的问题】

const obj = {
    x: 666,
    y: {
        m: 1
    },
    z: ['e', 'f', 'g'],
    d: function() {}
};

// 弊端:不能循环引用
// obj.z.push(obj.y);
// obj.y.j = obj.z;

const result = deepClone2(obj);

function deepClone2(target) {
    // 检测数据的类型
    if (typeof target === 'object' && target !== null) {
        const res = Array.isArray(target) ? [] : {};
        for (let key in target) {
            if (target.hasOwnProperty(key)) {
                res[key] = deepClone2(target[key]);
            }
        }
        return res;
    } else {
        return target;
    }

}
result.x = 999;
result.y.m = 1000;
console.log(obj);//{x: 666, y: {m: 1}, z: ["e", "f", "g"], d: ƒ}
console.log(result);//{x: 999, y: {m: 1000}, z: ["e", "f", "g"], d: ƒ}

【初级版】改进后的写法:更简洁

const obj = {
    x: 666,
    y: {
        m: 1
    },
    z: ['e', 'f', 'g'],
    d: function() {}
};

const result = deepClone2(obj);

function deepClone2(target) {
    if(typeof target !=='object' || target === null) return target;
    const res = Array.isArray(target) ? [] : {};
    for (let key in target) {
        res[key] = deepClone2(target[key]);
    }
    return res;
}
result.x = 999;
result.y.m = 1000;
console.log(obj);//{x: 666, y: {m: 1}, z: ["e", "f", "g"], d: ƒ}
console.log(result);//{x: 999, y: {m: 1000}, z: ["e", "f", "g"], d: ƒ}
高级方法:递归+ map

注:【可以解决循环引用的问题】

const obj = {
    x: 666,
    y: {
        m: 1
    },
    z: ['e', 'f', 'g'],
    d: function() {}
};

// 弊端:不能循环引用
obj.z.push(obj.y);
obj.y.j = obj.z;

const result = deepClone3(obj);

function deepClone3(target, map = new Map()) {
    // 检测数据的类型
    if (typeof target === 'object' && target !== null) {
        let cache = map.get(target);
        if (cache) {
            return cache;
        }

        const res = Array.isArray(target) ? [] : {};
        // 将新的结果存入到集合中
        map.set(target, res);
        for (let key in target) {
            if (target.hasOwnProperty(key)) {
                res[key] = deepClone3(target[key], map);
            }
        }
        return res;
    } else {
        return target;
    }

}
result.y.m = 1000;
console.log(obj);
console.log(result);

三、直接赋值和拷贝的区别

1.普通对象赋值

        let obj1 = {
            name: 'zhangsan',
            age: 18,
            data: {x: 1},
            arr: [1, 2]
        }
        let obj2 = obj1;
        obj2.name = 'lisi';
        obj2.data.x = 99;
        obj2.arr[0] = 100;
        console.log('obj1:',obj1); //{name: 'lisi', age: 18, data: {x: 99}, arr: [100, 2]}
        console.log('obj2:',obj2); //{name: 'lisi', age: 18, data: {x: 99}, arr: [100, 2]}

2.数组赋值

        let arr = [1, 2, 3, {x:10}];
        let arr2 = arr;
        arr2[0] = 99;
        arr2[3].x = 1000;
        console.log(arr); //[99, 2, 3, {x: 1000}]
        console.log(arr2); //[99, 2, 3, {x: 1000}]

总结:对数组或者普通对象直接赋值,不管是对象中元素是基本类型值,还是引用类型值,改变任意一个值,都会对源对象产生影响。

四、总结

  • 赋值和深浅拷贝的对比:
    JS对象的浅拷贝与深拷贝_第3张图片

你可能感兴趣的:(javascript,前端,开发语言)