likes
comments
collection
share

为什么对象中的值总是拷贝不下来

作者站长头像
站长
· 阅读数 21

前言

想必大家在编程中总会遇到这样一个问题,对象复制完后里面的值总是不稳定的,当我们试图拷贝一个对象的值时,经常会发现原以为的深拷贝实际上变成了浅拷贝,那么这到底是为什么呢?

对象的本质

首先,我们得弄清楚对象的本质。在JavaScript中,对象是一种引用类型。当你声明一个对象时,实际上是创建了一个指向内存中某处数据的引用,通俗来说就是创建了一个地址,然后js把你在对象里面声明的值放在了这个地址里面。例如:

let obj = { name: "apple" };
let objclone = obj;

这里的obj并不是直接保存了对象本身,而是保存了对象在内存中的地址。所以当你尝试将obj赋值给objclone时,实际上是将这个引用传递给了新的变量:

objclone.name = "bigapple";
console.log(obj.name); // 输出:"bigapple"

可见,当我们更改objclone中name的值时,obj的name也随之改变,因为它俩其实就是一个东西。对象的赋值操作并没有真正创建一个新的对象,而是共享了相同的引用,这也就是我们所说的浅拷贝。

还有哪些操作属于浅拷贝

其实我们平常用到的绝大多数拷贝都属于浅拷贝:

let obj = {
    name:'apple'
}

obj1=Object.create(obj)  // 通过原型创建一个新对象

obj2=Object.assign({},obj)  //创建一个新对象并复制对象的顶层属性

let arr = [1,2,obj]   //创建一个包含该对象的数组

let arr1 = [...arr]   //解构再重组数组

let arr2 = arr.toReversed().reverse()  //翻转再翻转

let arr3 = [].concat(arr)  //与空数组合并

let arr4 = arr.slice(0)  //从下标为零开始切割

obj.name = 'oneapple' //修改初始对象中的name

console.log(obj1.name); // 输出:'oneapple'
console.log(obj2.name); // 输出:'apple'
console.log(arr1); // 输出:[ 1, 2, { name: 'oneapple' } ]
console.log(arr2); // 输出:[ 1, 2, { name: 'oneapple' } ]
console.log(arr3); // 输出:[ 1, 2, { name: 'oneapple' } ]
console.log(arr4); // 输出:[ 1, 2, { name: 'oneapple' } ]

那么肯定有人会问了,Object.assign({},obj)拷贝的值不是没改变吗?那么我们就探讨一下它为什么属于浅拷贝。它会创造一个新对象,但它只会复制对象的顶层属性,而不会复制嵌套的属性或对象。

举个例子来说明

let obj = {
    name: "apple",
    age: 18,
    address: {
        city: "shanghai",
        country: "China"
    }
};

let objclone = Object.assign({}, obj);
obj.address.city = "shenzhen"

console.log(objclone);
// 输出:{name: 'apple',age: 18,address: { city: 'shenzhen', country: 'China' }}

apple还是变成深圳的了,所以说,这也只能算是浅拷贝,obj.addressobjclone.address 还是指向内存中的同一位置

浅拷贝的局限性

那么了解了这些后,浅拷贝的局限性显而易见,即使你认为自己已经拷贝了对象,实际上仍然共享着内部对象的引用,导致任何在原对象的修改都会反映在该对象上。

深拷贝的实现

那么深拷贝该如何实现呢?怎样才能使新创建的对象与原来的对象完全断绝“血缘关系”。

使用JSON序列化与反序列化:

利用JSON.stringify()将对象转换为字符串,再使用JSON.parse()将其解析回对象:

let obj = {
    b:{n:2},
    g:function(){},
    h:Symbol(1)
}
let newObj = JSON.parse(JSON.stringify(obj))
obj.b.n = 3
console.log(newObj); //输出:{ b: { n: 2 } }

可见,这种方法的缺陷就是不能正确处理函数和Symbol类型的属性

递归函数实现:

自定义一个递归函数,检查每个属性是否是对象或数组,如果是,就递归地进行深拷贝:

let obj = {
    a:1,
    b:{n:2}
}
function deepCopy(obj){
    let newObj = {}
    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            // obj[key] 是不是对象
            if(obj[key] instanceof Object){
                newObj[key] = deepCopy(obj[key])
            }else{
                newObj[key] = obj[key]
            }
        }
    }
    return newObj
}
let newObj = deepCopy(obj)
obj.b.n=3
console.log(newObj); //输出:{ a: 1, b: { n: 2 } }

这便完成了一个实实在在的深拷贝!!!

结语

那么,学会深拷贝,妈妈再也不用担心我们拷贝的对象会因为原对象的改变而改变了。摆脱各种编程陷阱,从我做起,咱自个儿创建一个实打实的对象。

newobj = obj

转载自:https://juejin.cn/post/7387801635328032777
评论
请登录