likes
comments
collection
share

了解js中的“复印机”:深浅拷贝

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

前言

在JavaScript中,拷贝(copy) 是指创建一个数据结构的副本,这个副本可以是一个变量、数组或对象等。根据拷贝的深度,可以分为浅拷贝(shallow copy)和深拷贝(deep copy)。简而言之就是将对象再复制一份,但是,复制的方法不同将会得到不同的结果。

数据存储

在了解深浅拷贝前我们得先了解一下两种数据类型是怎么存储的。首先是基本数据类型(如数字、字符串、布尔等),它们都是直接存储在栈内存中,每个变量都包含实际的值。这意味着,当一个基本类型的变量被赋予另一个变量时,会创建该值的一个副本,两个变量分别独立存储各自的值。然后就是引用数据类型(如数组、对象、函数等)、他们都是存储在堆内存中,而变量中存储的是指向堆中对象的引用(内存地址)。当一个引用类型的变量被赋予另一个变量时,实际上是复制了引用,两个变量最终指向的是同一块堆内存中的对象,因此修改其中一个变量会影响到另一个。例如:

let a = 2
let obj={
    b:1
}

上面代码我们通过下图来观察一下他们的存储位置都在哪:

了解js中的“复印机”:深浅拷贝

上述代码通过预编译a放入全局的变量环境赋值为2,obj也在全局的变量环境里但他的赋值相当于是堆里的一个编号,通过这个编号才能从堆里访问到obj里的b:1。通过上述介绍深浅拷贝这个我们基本不考虑基本数据类型,因为基本数据类型没有引用地址一说。

浅拷贝

介绍完数据是如何存储的了,现在回到我们的主题:首先来了解一下浅拷贝。浅拷贝是创建一个新对象,该对象与原始对象共享相同的内存地址,因此对新对象的修改也会影响原始对象。浅拷贝通常只复制对象的基本属性,而不复制对象的引用类型属性。通俗讲就是拷贝后的对象受原对象的影响。

常用的浅拷贝方法

1. Object.create(obj)

Object.create(obj) 的作用是创建一个新的对象 obj2,并将其原型 (__proto__) 设置为 obj。这意味着 obj2 会通过原型链查找访问 obj 上的属性和方法。

let obj = {
    a:1
}
let obj2 = Object.create(obj)
console.log(obj2.a);
obj.a=2
console.log(obj2.a);

2. Object.assign({}, obj)

Object.assign({}, obj)用来创建一个新的obj2的空对象,再将obj的所有可枚举属性复制给obj2;也可以用Object.assign({}, obj)b中的属性复制给a,相当于合并。

let obj = {
    a:1
}
let obj2 = Object.assign({}, obj)
obj.a=2
console.log(obj2.a);
console.log(obj2);

let a={n:1}
let b={m:2}
console.log(Object.assign(a,b));

3. [].concat(arr)

[].concat(arr)用于合并数组,创建一个空数组[],然后将arr的属性复制给空数组,从而创建一个新数组arr2

let arr=[1,2,3,{a:1}]
let arr2=[].concat(arr)
arr.push(4)
arr[3].a=2
console.log(arr2);
console.log(arr2[3]);

4. 数组解构[...arr]

[...arr] 在此代码中的作用是创建了一个空数组arr2,然后将arr的原始复制给arr2

let arr=[1,2,3,{a:1}]
let arr2=[...arr]
arr.push(4)
arr[3].a=2
console.log(arr2);
console.log(arr2[3]);

5.arr.slice(0)

创建一个arr2空数组,然后通过arr.slice(0)将数组arr的全部元素切下来复制给数组arr2

let arr=[1,2,3,{a:1}]
let arr2=arr.slice(0)
arr.push(4)
arr[3].a=2
console.log(arr2);
console.log(arr2[3]);

6.arr.toReversed().reverse()

通过toReversed()反转且生成一个新数组,再通过reverse()将生成的新数组再反转,得到与arr一样的数组arr2

let arr=[1,2,3,{a:1}]
let arr2=arr.toReversed().reverse()
arr.push(4)
arr[3].a=2
console.log(arr2);
console.log(arr2[3]);

手动实现浅拷贝

讲完上述六种方法,突然面试官来一句让你自己手动实现浅拷贝的方法,别慌,看完下面代码就有办法了

function shallowCopy(obj){
    let newObj={}
    for(let key in obj){
        // 判断key 是不是obj显示具有的
        if(obj.hasOwnProperty(key)){
            newObj[key]=obj[key]
        }
    }
    return newObj;
}
Object.prototype.c=3
let obj ={
    a:1,
    b:{n:2}
}
console.log(shallowCopy(obj));
console.log(obj.c);

看完了来介绍一下:先创建一个空对象newObj,再通过for..in 遍历原对象上的属性,然后借助hasOwnProperty规避原对象隐式具有的属性,接着将原对象obj的属性复制给newObj,最后返回新创建的对象newObj。看完是不是心里有点底了。

深拷贝

讲完了浅拷贝再来聊一聊深拷贝吧。如果需要复制一个对象的所有属性,包括嵌套的对象和数组,我们需要使用深拷贝(Deep Copy)。 关于深拷贝与浅拷贝的差别就是拷贝后的对象不受原对象的影响。

常用深拷贝方法

1.JSON.parse(JSON.stringify(obj))

JSON.stringify() 方法可以将 JavaScript 对象转换成一个 JSON 字符串,而 JSON.parse() 则能将 JSON 字符串解析回 JavaScript 对象。但这个方法有几点要注意:

  1. 无法识别BigInt类型
  2. 无法拷贝 undefined,function,Symbol
  3. 无法处理循环引用
let obj ={
    a:1,
    b:{n:2},
    c:'cc',
    d:true,
    e:undefined,
    f:null,
    g:function(){},
    h:Symbol(1),
    // i:123n
}
let newObj=JSON.parse(JSON.stringify(obj))
obj.a=2
console.log(newObj);

2. structuredClone()

structuredClone() 是JavaScript中的一个函数,用于创建一个深度拷贝的对象或值。这意味着原始对象和拷贝对象之间没有任何关联,修改其中一个对象不会影响到另一个。它也有几个注意点:

  1. 无法识别函数和Symbol类型
  2. 无法处理循环引用
let obj ={
    a:1,
    b:{n:2},
    c:'cc',
    d:true,
    e:undefined,
    f:null,
    // g:function(){},
    // h:Symbol(1),
    i:123n
}
const newObj=structuredClone(obj)
obj.b.n=3
console.log(newObj);

手动实现深拷贝

了解一下上述两种方法,现在我们来手动实现一下深拷贝吧。手动实现深拷贝与浅拷贝大致方法差不多,但深拷贝和浅拷贝的区别是是否受原对象的影响,浅拷贝初略讲就是把引用类型在堆中的地址编号给复制给了新对象,而深拷贝是要将引用类型的在堆中的数据给复制到新对象。而要实现深拷贝最主要就是引用一个递归的方法。接下来看下面代码如何实现的吧:

let obj={
    a:1,
    b:{n:2}
}
function deepCopy(obj){
    let newObj={}
    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            if(obj[key] instanceof Object){
                newObj[key]=deepCopy(obj[key])     // 递归
            }else{
                newObj[key]=obj[key]
            }
        }
    }
    return newObj
}
let obj2=deepCopy(obj)
obj.b.n=20
console.log(obj2);

这段代码与浅拷贝的代码的区别就是多了一个if判断obj[key]是否还是引用类型,然后调用了 deepCopy 函数自身,对 obj[key] 进行递归拷贝。

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