手写深拷贝函数详细剖析

204 阅读4分钟

👉🏼了解深拷贝的之前先了解一下数据类型

JavaScript中存在两大数据类型:

  • 基本类型 Number String null Undefined Boolean symbol
  • 引用类型 array object function

基本类型数据保存在在栈内存中

引用类型数据保存在堆内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中

深拷贝 && 浅拷贝

  • 浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象

  • 深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象

👇🏼借助这张图了解一下深拷贝和浅拷贝的区别

image.png

💁🏼‍♀️下面来看看怎么一步一步实现深拷贝函数吧:

1.普通赋值对象的过程

const oldObj = {
        name:'哈默',
        age:20,
        colors:['orange','green','blue'],
        friend:{
            name:'小夏'
        }
    }

    const newObj = oldObj;
    newObj.name = '小野';
    console.log('oldObj',oldObj);
    console.log('newObj',newObj);

可以看出原对象的数据被改变,这是一次浅拷贝的过程

截屏2022-04-26 下午2.27.49.png

2.🙇🏼‍♀️深拷贝函数的初成型

const oldObj = {
        name:'哈默',
        age:20,
        colors:['orange','green','blue'],
        friend:{
            name:'小夏'
        }
    }

    function deepClone(obj = {}){
        // 如果是基本数据类型
        if(typeof obj !== 'object' || obj == null){
            return obj;
        }
            let result;
            // 拷贝过程1.定义格式
            // 两种情况,传入的是数组,传入的是对象
            if(obj instanceof Array)
                result = [];
            else
                result = {};

            // 2.循环拷贝属性
            for(let key in obj){
                // 核心,属性拷贝
               result[key] = obj [key];
            }
            return result;
        
    }

    const newObj2 = deepClone(oldObj);
    newObj2.name = '小野';
    console.log('oldObj',oldObj);
    console.log('newObj2',newObj2);

以上的代码只是实现第一层是深拷贝 ,改变name属性时会得到如图结果,那么如果将oldObj中的friend对象中的name属性改变会发生什么呢?

截屏2022-04-26 下午3.02.15.png

如图,如果使用上面的代码,我们的friend对象的name属性改变,原对象也改变。说明,此时的深拷贝并没有完善,需要再完善完成多层深拷贝,那么如何做到呢?

截屏2022-04-26 下午3.04.34.png

我们可以利用递归的方法完成多层的深拷贝,仔细阅读如下代码,我们的小小改动就可以是实现多层深拷贝啦~

2.🙇🏼‍♀️深拷贝函数实现多层深拷贝

function deepClone(obj){
        // 如果是基本数据类型
        if(typeof obj !== 'object' || obj == null){
            return obj;
        }
            let result;
            // 拷贝过程1.定义格式
            // 两种情况,传入的是数组,传入的是对象
            if(obj instanceof Array)
                result = [];
            else
                result = {};

            // 2.循环拷贝属性
            for(let key in obj){
                // 核心,属性拷贝
                //利用递归多层拷贝
               result[key] = deepClone(obj[key]);
            }
            return result;
        
    }

    const newObj2 = deepClone(oldObj);
    newObj2.friend.name = '小李';
    console.log('oldObj',oldObj);
    console.log('newObj2',newObj2);

此时可见代码中利用了递归的方式,将实现了多层深度拷贝,此时的friend对象里的name值被改变,原对象中的数据却不变如图:

截屏2022-04-26 下午3.05.58.png

你以为这就大功告成了吗,不不不,我们还要注意,深拷贝我们只拷贝对象的属性,对象的原型链上的不可以被拷贝哦,所以要多加一层判断,防止原型链被查找并拷贝。

我们使用的是hasOwnProperty方法。

hasOwnProperty表示是否有自己的属性。这个方法会查找一个对象是否有某个属性,但是不会去查找它的原型链。

3.🙇🏼‍♀️深拷贝函数完成~

 function deepClone(obj){
        // 如果是基本数据类型
        if(typeof obj !== 'object' || obj == null){
            return obj;
        }
            let result;
            // 拷贝过程
            
            // 1.定义格式
            // 两种情况,传入的是数组,传入的是对象
            if(obj instanceof Array)
                result = [];
            else
                result = {};

            // 2.循环拷贝属性
            for(let key in obj){
                // 3.防止原型属性被拷贝
                if(obj.hasOwnProperty(key)){
                // 核心,属性拷贝
                result[key] = deepClone(obj[key]);
                }
                
            }
            return result;
        
    }

好啦,深拷贝我们完成了~~

🤓是不是很简单,接下来我们来总结一下防止面试时紧张忘记了。

总结】深拷贝的实现有三个重要步骤:
1.首先判断传入的数据类型
2.拷贝的过程中要判断数据格式(用来保存返回数据的)
3.通过循环 for...in...(遍历原型链)进行拷贝,拷贝的过程中不要忘记避免拷贝原型链上的属性