浅拷贝、深拷贝

发布时间 2023-08-25 10:30:18作者: 禺心

理解

1、浅拷贝:是对目标的简单复制。只能复制目标对象的值引用,并没有重新开辟新的存储空间,这导致只要其中一方值被修改,另一个也跟着被修改了值。
2、深拷贝:内存地址是自主分配的,两个数据指向了不同的地址,数据元素发生改变时不会相互影响。
不过,拷贝一般只针对Array和Object

先来说说在JavaScript中,存在浅拷贝的现象

1、赋值
var arr = [1, 2,3];
var arr2;

//把arr赋值给arr2
arr2 = arr;
console.log("arr2:", arr2);  // 输出: [1, 2,3]
console.log("---改变arr2中数组元素的值后---");
arr2[0] = 3;
console.log("arr:", arr);   // 输出: [3, 2,3]
console.log("arr2:", arr2); // 输出: [3, 2,3]


2、拓展运算符
const t = [[1,[5,[6]]], 2, "3"]
const s = [...t]
t.push(9)
t[0][1][1] = 9
console.log('t===',t)  // t=== [ [ 1, [ 5, 9 ] ], 2, '3', 9 ]
console.log('s===',s)  // s=== [ [ 1, [ 5, 9 ] ], 2, '3' ]
console.log(s[0][1][1])  // 9
//这个例子 可以看出,当数组为三维数组时,拓展运算符并不能隔离变量 t 的变化

常用的简易浅拷贝方案

1:
 Object.assign(目标对象,需要拷贝的对象,需要拷贝的对象2,...)  
//如:
let a = {a:1,b:2,c:()=>{}}
let d = Object.assign({},a,{u:8})
// 输出:{a: 1, b: 2, uu: 8, c: ƒ},修改a或者d内的属性不会对对方产生影响

2:JSON.Parse(JSON.stringify(obj))  

【此方法比深拷贝浅,比浅拷贝深。原因是除了对象是函数类型,对于其他深度的基本类型子对象都满足深拷贝定义。
使用时的注意事项:https://www.cnblogs.com/fanqshun/p/16928676.html】

let f = JSON.parse(JSON.stringify(a))
 // 输出,{a: 1, b: 2},  注意:此处,原来对象a 
//->中的子对象c被噶掉了,所以此方法使用的时候需要注意这一点
JSON.parse(JSON.stringify({h:[1,2],p:90})) 
//输出:{h: Array(2), p: 90},数组类型对象不被影响  
//----------------一下方式仅适用于数组----------------------------
3.concat()
//用于连接两个或多个数组。该方法不会改变现有数组,仅仅只会返回被连接数组的一个副本。只有在数组元素是一维的时候,是深拷贝,一维以上是对值的引用 
let  a = [1,2,3]
let b = a.concat()
b[1] = 999
console.log(b) // 输出:[1,999,3]

4. slice()
//1)没有参数的时候,是拷贝数组
//2)一个参数的时候,拷贝从起始位置到数组末尾的元素
//3)两个参数的时候,拷贝从起始位置到 结束位置的元素(不包含结束位置的元素,含头不含尾)
//一维数组元素是深拷贝,数组元素二维以上是值的引用
c = a.slice();
b[2] = 100
console.log(c) // 输出:[1,2,100],并不影响a、b的值

let b = [[[3],12],1,3]
let c = b.slice();
c[0][0]=90
console.log(c) // 输出:[[90,12],1,3]
console.log(b) // 输出:[[90,12],1,3] ,修改后被影响

手写一个浅拷贝

 const shallowCopy = (obj) => {
    if (obj == null) return obj; // 不是对象时返回自身
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
    let target = Object.prototype.toString.call(obj) == '[object Array]' ? [] : {}
    if (obj.constructor === Object) {
        for (let key in obj) {
            if (Object.prototype.hasOwnProperty.call(obj, key)) {
                target[key] = obj[key]
            }
        }
    }
    if (obj.constructor === Array) {
        target = [...obj]
    }

    return target

}
var a = {
    a: 1,
    b: {
        b: 2
    },
    c: [1, 2, 3, [4, 5]],
    op: () => { }
}
const v = shallowCopy(a)
v.c[3] = [4, 5, 6]
v.op = [4, 5, 6]
console.log('v===', v);  
//v=== { a: 1, b: { b: 2 }, c: [ 1, 2, 3, [ 4, 5, 6 ] ], op: [ 4, 5, 6 ] }
console.log('a===', a);  
//a=== { a: 1, b: { b: 2 }, c: [ 1, 2, 3, [ 4, 5, 6 ] ], op: [Function: op] }

由上可以看出,当使用shallowCopy 拷贝目标对象内的属性值层级过深,修改新拷贝的对象会影响原目标的值

简易深拷贝

const deepCopy = (obj) => {
    if (obj == null) return obj; // 不是对象时返回自身
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    if(typeof obj !== 'object') return obj
    // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
    let target = (obj.constructor) == Array ? [] : {}

    for (let key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            if (typeof obj[key] == 'object') {//引用类型
                target[key] = deepCopy(obj[key])
            } else {// 基本类型走这里

                target[key] = obj[key]
            }
        }
    }


    return target

}
var a = {
    a: 1,
    b: {
        b: 2
    },
    c: [1, 2, [4,5]],
    op: () => { }
}
const v = deepCopy(a)
v.c[2] = 200
console.log('v===', v);
console.log('a===', a);


由上可以看出此方法拷贝出来的新对象就算被修改也不会影响原对象值