面试官:给我讲讲深拷贝和浅拷贝

219 阅读6分钟

首先我们需要知道的是只有引用对象才需要拷贝,基本类型不需要,当你将一个基本类型的值赋给另一个变量时,实际上是将值进行了复制。

拷贝分为浅拷贝和深拷贝,我们先来讲讲浅拷贝

🐟浅拷贝

浅拷贝是创建一个新的对象,这个对象具有原始对象属性值的一份精确拷贝,如果属性是基本类型数据,拷贝的就是基本类型数据的值,如果是应用类型,呢拷贝的就是引用类型的地址

这意味着对基本数据类型来说,原始对象的和拷贝后的新对象的是各自独立的,但对于引用类型的属性,新对象和原始对象共享这些引用的地址

🐟实现方法

Object.create()

Object.create()是一个用于创建新对象的方法,这个新对象的原型可以指定为另一个对象。 一、语法

Object.create(proto[, propertiesObject])

  • proto:新创建对象的原型对象。
  • propertiesObject(可选):一个包含属性描述符的对象,用于定义新对象的属性。。
const original = {
  num: 10,
  str: 'hello',
  innerObj: { key: 'value' }
};

const copied = Object.create(Object.getPrototypeOf(original),//保持原型链的完整
Object.getOwnPropertyDescriptors(original));

original.num = 20;
original.str = 'world';
original.innerObj.key = 'new value';

console.log(copied.num); // 10(基本类型不受影响)
console.log(copied.str); // 'hello'(基本类型不受影响)
console.log(copied.innerObj.key); // 'new value'(引用类型共享变化)
Object.assign()

Object.assign()是一个用于将一个或多个源对象的可枚举属性复制到目标对象的方法。

一、语法

Object.assign(target,...sources)

  • target:目标对象,即属性将被复制到的对象。
  • sources:一个或多个源对象,其属性将被复制到目标对象。
const target = {};
const source = { obj: { prop: 1 } };
const result = Object.assign(target, source);
result.obj.prop = 2;
console.log(source.obj.prop); // 2

Array.slice(),Array.contact(),展开运算符,这三种方法都是实现数组浅拷贝的有效方式,它们都能快速创建一个新的数组,其中包含与原始数组相同的元素。然而,对于数组中的引用类型元素,它们都只是复制了引用,而不是创建深层副本

Array.slice()
  • slice方法提取一个数组的一部分,返回一个新的数组。如果只传入一个参数 start,则从该索引开始提取到数组末尾;如果传入两个参数 start 和 end,则提取从 start 到 end(不包括 end)的部分。
  • arr.slice(0)实际上是提取了整个数组,相当于创建了一个数组的副本。
   const originalArray = [1, 2, { value: 3 }];
   const copiedArray = originalArray.slice(0);
   originalArray[2].value = 4;
    originalArray[1].value = 4;
   console.log(copiedArray); // [1, 2, { value: 4 }]
Array.contact()
  • concat方法将一个或多个数组或值与调用它的数组连接起来,创建一个新的数组。当传入一个现有数组时,它会将该数组的元素逐个添加到新数组中。
  • 例如,[].concat(arr)中,空数组与给定的数组 arr 连接,实际上是复制了 arr 的元素到一个新的数组中。
const originalArray = [1, 2, { value: 3 }];
const copiedArray = [].concat(originalArray);
originalArray[2].value = 4;
originalArray[1].value = 4;
console.log(copiedArray); // [1, 2, { value: 4 }]
解构赋值(如 [...arr]
  • 展开运算符 ... 将数组展开为单个元素,然后通过解构赋值将这些元素重新分配到一个新的数组中。本质上是复制了原数组的元素到新数组中。
   const originalArray = [1, 2, { value: 3 }];
   const copiedArray = [...originalArray];
   originalArray[2].value = 4;
   originalArray[1].value = 4;
   console.log(copiedArray); // [1, 2, { value: 4 }]
手搓一个浅拷贝

这种手搓题是面试官最喜欢问的

function shallowCopy(oldObj) {
    let newObj = {}
    for (let k in oldObj) {
        if (oldobj.hasownProperty(k)) {
            newObj[k] = oldObj[k]   
        }
    }
    return newObj
}

为啥要写oldobj.hasownProperty(k)作为判断条件,我们先了解一下 hasOwnProperty() hasOwnProperty()是一个用于对象的方法。它用于检查一个对象自身(而不是其原型链上)是否具有指定的属性。所以可以用来 区分自身属性和继承属性:在 JavaScript 中,对象可以通过原型链继承属性。当你只想处理对象自身的属性而不是从原型继承的属性时,hasOwnProperty()就非常有用。

Array.toReversed().reverse() :

最后再补充一个Array有趣的浅拷贝方法,就是进行两次反转,一个是toReversed(),一个是reverse()

  • toReversed()方法是 ES2023 新增的。它会返回一个新的数组,这个新数组是原数组的浅拷贝并且元素顺序是反转的。
  • reverse()方法会直接反转原数组的元素顺序(直接修改原数组),它不会返回一个新的浅拷贝后的数组。
let arr = [1, 2, 3, { a: 1 }]; 
let arr2 = arr.toReversed().reverse(); 
arr[3].a = 2; 
console.log(arr2);//输出[ 1, 2, 3, { a: 2 } ]

🐟深拷贝

深拷贝是指在拷贝一个对象时,不仅仅是复制对象的引用,而是递归地复制对象的所有属性和子属性,创建一个全新的、与原对象完全独立的对象副本。这样,对新对象的任何修改都不会影响到原对象,反之亦然。

JSON.parse (JSON.stringify ()) 方法实现深拷贝

  • 工作原理

    • 首先,JSON.stringify()将一个 JavaScript 对象转换为 JSON 字符串。这个过程会把对象的所有属性都序列化为字符串格式,包括嵌套的对象和数组。
    • 然后,JSON.parse()把这个 JSON 字符串再转换回 JavaScript 对象。这样就创建了一个新的对象,这个新对象和原对象是完全独立的,因为它是通过解析字符串重新构建的。
    const originalObject = {
       name: 'John',
       age: 30,
       hobbies: ['reading', 'painting'],
       address: {
         city: 'New York',
         country: 'USA'
       }
     };
     const copiedObject = JSON.parse(JSON.stringify(originalObject));
     originalObject.age = 31;
     originalObject.hobbies.push('swimming');
     originalObject.address.city = 'Los Angeles';
     console.log(originalObject);
     console.log(copiedObject);
    

    **LadAsh库中的cloneDeep方法实现深拷贝

    • 工作原理

    • cloneDeep是 Lodash 库中的一个非常有用的函数,用于创建一个对象的深拷贝。它会递归地复制对象的所有属性,包括嵌套的对象和数组,使得新的拷贝与原始对象完全独立。

    const _ = require('lodash');
       const originalObject = {
         name: 'John',
         age: 30,
         hobbies: ['reading', 'painting'],
         address: {
           city: 'New York',
           country: 'USA'
         }
       };
       const copiedObject = _.cloneDeep(originalObject);
       originalObject.age = 31;
       originalObject.hobbies.push('swimming');
       originalObject.address.city = 'Los Angeles';
       console.log(originalObject);
       console.log(copiedObject);
    
    
手搓一个深拷贝

他和浅拷贝很相似,但是需要加上一些判断条件

  function deepcopy(newobj, oldobj) {
    for (let k in obj) {
        if (oldobj.hasownProperty(k)) {
            if (oldobj[k] instanceof Object) {
                newobj[k] = {}
                deepcopy(newobj[k],oldobj[k])
            }
            else {
                newobj[k] = oldobj[k]
            }
        }
    }
}

我们定义了一个名为deepcopy的函数,用于实现对象的深拷贝。它遍历源对象(oldobj)的属性,如果属性值是对象类型,则为新对象(newobj)对应属性创建一个空对象并递归调用自身进行深度拷贝;如果属性值不是对象类型,则直接将值复制到新对象中

🐟END

  1. 定义区别

    • 浅拷贝:新对象复制原始对象基本类型属性值,引用类型属性则共享引用。
    • 深拷贝:新对象完全独立于原始对象,递归复制所有属性,包括引用类型。
  2. 实现方法

    • 浅拷贝:使用展开运算符、Object.assign()Array.slice()Array.concat()
    • 深拷贝:JSON.parse(JSON.stringify())、自定义递归函数、第三方库(如 Lodash 的cloneDeep)。
  3. 应用场景和注意点

    • 浅拷贝用于基本结构复制,部分独立修改。注意引用类型共享修改的情况。

    • 深拷贝用于完全独立的副本,注意性能和特殊数据类型处理。