如何手写去实现一个深/浅拷贝

87 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情

前言

阅读本文需要的前置知识

  • 栈和堆的概念
  • 数据类型

数据类型

在js中数据类型主要有两种,一个是基本数据类型,一个是引用数据类型

简单说一下这两个数据类型的区别,有助于理解深浅拷贝

基本数据类型存储在栈当中,而引用数据类型存放在堆当中,堆的地址存放在栈当中。

因此

使用基本数据类型,使用的是它本身

使用引用数据类型,使用的是它的地址指针

这也就造成了引用数据类型去修改数据的时候,会影响其它引用了同一个地址的数据。

所以,我们需要使用深拷贝去解决这种情况

浅拷贝

对基本数据进行精准的拷贝,对引用数据类型进行堆地址的拷贝

我们先复习一下我们可以使用哪些方法去浅拷贝

1. 使用ES6的扩展运算符

对于拷贝这个话题来说,就是就是将其展开,其中进行了浅拷贝。

    let obj1 = {a: 1, b: {c: 2, d: 3}}
    let obj2 = {...obj1}
    console.log(obj2);  // {a: 1, b: {c: 2, d: 3}}

2. Object.assig()

将后面的参数拷贝到第一个参数中

    let target = {a: 1};
    let object2 = {b: 2};
    let object3 = {c: 3};
    Object.assign(target,object2,object3);  
    console.log(target);  // {a: 1, b: 2, c: 3}

3. 自己手写一个浅拷贝(重要)

思路:

  • 判断需要浅拷贝的是不是Object类型,同时要排除是null的情况
  • 判断是一个数组还是对象
  • 循环赋值

接下来我们一步一步开始实现

1.写出函数框架

    function sholldowCopy(object) {  // 传入需要复制的对象 
    
    let newObject = null;            // 新对象
    
    return newObject;                // 将复制完成的函数返回出去
    }

2. 判断需要浅拷贝的是不是Object类型,同时要排除是null的情况

在这里,我们使用typeof方法来判断参数的类型,但是 null会被 typeof判断为Object

因此我们需要特殊设置一下

    function sholldowCopy(object) {  // 传入需要复制的对象 
    
    if (typeof object !== "object" || object == null) return; // 判断类型,只拷贝Object类型
    
    let newObject = null;            // 新对象
    
    return c;                // 将复制完成的函数返回出去
    }

3. 判断object是一个数组还是对象,用于决定newObject的类型

保证复制出来的newObjectobject的类型相同

    function sholldowCopy(object) {  // 传入需要复制的对象 
    
    if (typeof object !== "object" || object == null) return; // 判断类型,只拷贝Object类型
    
    let newObject =  Array.isArray(object) ? [] : {};          // 新对象类型判断
    
    return c;                // 将复制完成的函数返回出去
    }

4. 循环赋值

使用for...in进行赋值

需要注意的是,for...in遍历的是key,并且回到原型链上去查找。

因此需要限制在原型链上去查找,这里我们使用hasOwnProperty方法来限制,只在自身查找

    function sholldowCopy(object) {  // 传入需要复制的对象 
    
    if (typeof object !== "object" || object == null) return; // 判断类型,只拷贝Object类型
    
    let newObject =  Array.isArray(object) ? [] : {};          // 新对象类型判断
    
    for (let key in object) {
        if (object.hasOwnProperty(key)){
            newObject[key] = object[key];                         // 循环赋值
        }
    }
    
    return c;                // 将复制完成的函数返回出去
    }

到这里,一个手写的浅拷贝就基本完成了 务必先理解了浅拷贝,再去学习深拷贝

深拷贝

对数据进行精确的拷贝,而不是只拷贝堆地址

还是一样,先介绍几种方法,再实现一个手写的深拷贝

1. JSON.stringfy()配合JSON.parse()

  • JSON.parse(JSON.stringify(obj))是目前比较常用的深拷贝方法之一,它的原理就是利用JSON.stringifyjs对象序列化(JSON字符串),再使用JSON.parse来反序列化(还原)js对象。
  • 这个方法可以简单粗暴的实现深拷贝,但是还存在问题,拷贝的对象中如果有函数,undefined,symbol,当使用过JSON.stringify()进行处理之后,都会消失。
    let obj1 = {  a: 0, b: { c: 0} };
    let obj2 = JSON.parse(JSON.stringify(obj1));

2. 引入第三方库lodash的_.cloneDeep方法

引入lodash的方法在这里就不多解释了,大家可以自行了解

3. 自己手写一个深拷贝(重要)

务必先理解浅拷贝

深拷贝的实现和浅拷贝相似,但是不同的是,深拷贝需要用到递归去赋值

因此我们在浅拷贝的基础上去修改代码即可

现在我们需要思考如何去进行一个循环递归

那么判断是否还要递归的关键是它是不是一个引用数据类型

    function deepCopy(object) {
        if (typeof object !== "object" || object == null) return;
        
        let newObject = Array.isArray(object) ? [] : {};
        
        for (let key in object) {
            if (object.hasOwnProperty(key)) {
                newObject[key] = typeof object[key] === "object" ? deepCopy(object[key]) : typeof object[key];
            }
        }
    }

最后

文中有不足之处希望大家能够指正