JavaScript 深浅拷贝(详细步骤说明)

本文围绕JavaScript中对象的深拷贝和浅拷贝展开。介绍了深拷贝和浅拷贝的概念,深拷贝修改新变量不影响原变量,浅拷贝则会影响。还探讨了实现对象深拷贝的方法,指出部分方法存在弊端,并提及可通过自定义方法实现真正深拷贝,最后有相关扩展知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是深拷贝、什么是浅拷贝?

  • 深拷贝
    修改新变量的值不会影响原有变量的值
    默认情况下基本数据类型都是深拷贝

  • 浅拷贝
    修改新变量的值会影响原有变量的值
    默认情况下引用类型都是浅拷贝

深拷贝

深拷贝中新对象会开辟新的内存,保存的是上一个对象中的值,修改时只会修改自身的值,对原对象没有影响
在这里插入图片描述

	// 深拷贝
	let num1 = 123;
    let num2 = num1;
    
    num2 = 456;
    
    console.log(`num1: ${num1}  num2: ${num2}`); // num1: 123  num2: 456

浅拷贝

浅拷贝中新对象会开辟新的内存,保存的不是上一个对象中的值,而是上一个对象的内存地址,修改时会根据内存地址修改对象
在这里插入图片描述

	// 浅拷贝
	class Person {
        name = 'Tom';
        age = 18;
    }

    let p1 = new Person();
    let p2 = p1;

    p2.name = '李雷';

    console.log(`p1: ${p1.name}  p2: ${p2.name}`); // p1: 李雷  p2: 李雷

如何实现对象的深拷贝?

对象的深拷贝就是在拷贝对象的时候,只拷贝对象的值,而不是拷贝对象的内存地址,等于重新开辟一个内存来存放拷贝的内容,这样修改拷贝对象的值时,不会对原对象产生影响
在这里插入图片描述

	// 对象的深拷贝
    class Person {
        name = 'Tom';
        age = 18;
    }

    let p1 = new Person();

    // 方法一
    let p2 = new Object();
    p2.name = p1.name;
    p2.age = p1.age;
    p2.name = '李雷';

    console.log(`p1: ${p1.name}  p2: ${p2.name}`); // p1: Tom  p2: 李雷

	// 方法二
    for (let key in p1) {
        p2[key] = p1[key];
    }

    p2.name = '韩梅梅';

    console.log(`p1: ${p1.name}  p2: ${p2.name}`); // p1: Tom  p2: 韩梅梅

	// 方法三
    Object.assign(p2, p1);
    p2.name = '李雷';

    console.log(`p1: ${p1.name}  p2: ${p2.name}`); // p1: Tom  p2: 李雷
弊端

上面三种深拷贝方法有弊端,当对象中存在引用数据类型时就不能完全深拷贝了
在这里插入图片描述

class Person {
        name = 'Tom';
        dog = { name: '小柴'};
        scores = [ 78, 69, 88 ];
    }

    let p1 = new Person();
    let p2 = new Object();

    p2.name = p1.name;
    p2.name = '李雷';
    console.log(`p1: ${p1.name}  p2: ${p2.name}`); // p1: Tom  p2: 李雷

    // 修改引用类型字段的值,会改变原数据
    p2.dog = p1.dog;
    p2.dog.name = '富贵';
    console.log(`p1: ${p1.dog.name}  p2: ${p2.dog.name}`); // p1: 富贵  p2: 富贵

    // 修改引用类型字段的值,会改变原数据
    p2.scores = p1.scores;
    p2.scores[0] = 0;
    console.log(`p1: ${p1.scores}  p2: ${p2.scores}`); // p1: 0,69,88  p2: 0,69,88
通过自定义方法实现真正的深拷贝
	// 通过自定义方法实现真正的深拷贝
    class Person {
        name = 'Tom';
        dog = { name: '小柴'};
        scores = [ 78, 69, 88 ];
    }

    let p1 = new Person();
    let p2 = new Object();

    deepClone(p2, p1);

    p2.scores[0] = 0;
    console.log(`p1: ${p1.scores}  p2: ${p2.scores}`); // p1: 78,69,88  p2: 0,69,88
    
    function deepClone(target, source) {
        // 通过遍历拿到source中所有属性
        for (let key in source) {
            // 取出当前遍历到的属性的取值
            let sourceValue = source[key];

            // 判断当前取值是否为引用数据类型
            if (sourceValue instanceof Object) {
                // console.log(sourceValue.constructor);
                // console.log(new sourceValue.constructor);
                let subTarget = new sourceValue.constructor;
                target[key] = subTarget;                    
                deepClone(subTarget, sourceValue)           
            } else {
                target[key] = sourceValue
            }
        }
    }

	// 优化一下,只要传一个源对象就行了,兼容对象和数组
    function deepCopy(inObj, outObj) {
        outObj = outObj || Array.isArray(inObj) ? [] : {};

		// 通过遍历拿到source中所有属性
        for (let key in inObj) {
        	// 取出当前遍历到的属性的取值
            let currentValue = inObj[key];

			// 判断当前取值是否为引用数据类型
            if (currentValue instanceof Object) {
                let subOutObj = new currentValue.constructor;
                outObj[key] = subOutObj;
                deepCopy(currentValue, subOutObj);
            }
            else {
                outObj[key] = currentValue;
            }
        }

        return outObj;
    }

	// 简化一下代码
	function deepCopy(inObj) {
        let outObj = Array.isArray(inObj) ? [] : {};

        for (let key in inObj) {
            let currentValue = inObj[key];

            if (currentValue instanceof Object) {
                outObj[key] = deepCopy(currentValue);
            }
            else {
                outObj[key] = currentValue;
            }
        }

        return outObj;
    }

	// 如果你不想复制继承过来的属性的话,上面的方法还不够完美
   class Person {
       name = 'Tom';
       dog = { name: '小柴'};
       scores = [ 78, 69, 88 ];
    }

    let p = new Person();

    // 给原型对象增加属性
    p.__proto__.sister = '小花';
    Person.prototype.brother = '小明';

    // 属性会继承到实例对象中
    console.log(p.sister);  //  '小花'
    console.log(p.brother); //  '小明'

	// 用上面的方法克隆会把继承属性克隆下来
	let cloneObj = deepCopy(p);
    console.log(cloneObj);
    /*
    {
        brother: "小明"
        dog: {name: "李白"}
        name: "Tom"
        scores: (3) [78, 69, 88]
        sister: "小花
    }
    */

	// 这里对上面的方法优化一下,在遍历属性时,用hasOwnProperty判断是自身属性还是继承属性
	function deepCopy(inObj) {
        let outObj = Array.isArray(inObj) ? [] : {};

        for (let key in inObj) {
            if (inObj.hasOwnProperty(key)) {
                let currentValue = inObj[key];

                if (currentValue instanceof Object) {
                    outObj[key] = deepCopy(currentValue);
                }
                else {
                    outObj[key] = currentValue;
                }
            }
        }

        return outObj;
    }

	let cloneObj = deepCopy(p);
    console.log(cloneObj);
    /*
    {
        dog: {name: "李白"}
        name: "Tom"
        scores: (3) [78, 69, 88]
    }
    */

	// 这里会有一个很特殊的情况,JavaScript 并没有保护 hasOwnProperty 这个属性名,所以你可以在构造函数里自己建一个私有的hasOwnProperty属性,这时就不能用hasOwnProperty来判断是否继承属性了
	class Person {
        name = 'Tom';
        dog = { name: '小柴'};
        scores = [ 78, 69, 88 ];
        hasOwnProperty =  function() {
            return true;
        }
    }

	// 对上面的方法再一次优化,使用Object 原型上的 hasOwnProperty 属性
    function deepCopy(inObj) {
       let outObj = Array.isArray(inObj) ? [] : {};

       for (let key in inObj) {
           if (Object.prototype.hasOwnProperty.call(inObj, key)) {
               let currentValue = inObj[key];

               if (currentValue instanceof Object) {
                   outObj[key] = deepCopy(currentValue);
               }
               else {
                   outObj[key] = currentValue;
               }
           }
       }

       return outObj;
   }

	// 这里还有弊端是,没有考虑到Function、Date、RegExp这些情况,会返回{},如果明确数据只有基本类型,Object,Array用这种比较方便
	
	// 更全面的类型判断,支持多种类型(Date、Map、Map、Function等)
	function deepCopy(inObj) {
        const type = Object.prototype.toString.call(inObj).slice(8, -1);

        if (type === 'Object') {
            let outObj = {};

            for (let key in inObj) {
                if (Object.prototype.hasOwnProperty.call(inObj, key)) {
                    let currentValue = inObj[key];

                    outObj[key] = deepCopy(currentValue);
                }
            }

            return outObj;
        }
        else if (type === 'Array') {
            return inObj.map(item => deepCopy(item));
        }
        else if (type === 'Function') {
            return new Function(`return ${inObj.toString()}`).call(this);
        }
        else if (type === 'Date') {
            return new Date(inObj.valueOf());
        }
        else if (type === 'RegExp') {
            return new RegExp(inObj);
        }
        else if (type === 'Map') {
            let map = new Map();

            inObj.forEach((value, key) => {
                map.set(key, deepCopy(value))
            });

            return map;
        }
        else if (type === 'Set') {
            let set = new Set();
            
			inObj.forEach((item) => {
                set.add(item)
            });

            return set;
        }

        return inObj;
    }

	let person = {
        name: 'Tom',
        dog: { name: '小柴'},
        scores: [ 78, 69, 88 ],
       	map: new Map([["key1", "value1"], ["key2", "value2"]]),
        set: new Set([1, 2, 3, 4, 5]),
        say: function(word) {
            console.log(`${this.name}: ${word}`)
        },
    };
    
    let newPerson = deepCopy(person);
    console.log(newPerson);

    person.say('吃饭了吗?');
    newPerson.say('吃了')

Object.prototype.toString

	class Person {
        name = 'Tom';
        dog = { name: '小柴'};
    }

    let p = new Person();

    console.log(Object.prototype.toString.call({}));                // [object Object]
    console.log(Object.prototype.toString.call(p));                 // [object Object]
    console.log(Object.prototype.toString.call('123'));             // [object String]
    console.log(Object.prototype.toString.call(new Date));          // [object Date]
    console.log(Object.prototype.toString.call(Math));              // [object Math]
    console.log(Object.prototype.toString.call(undefined));         // [object Undefined]
    console.log(Object.prototype.toString.call(new Map()));         // [object Map]
    console.log(Object.prototype.toString.call(new Set()));         // [object Map]
    console.log(Object.prototype.toString.call(/[a-z]/));           // [object RegExp]
    console.log(Object.prototype.toString.call(new Function()));    // [object Function]
    console.log(Object.prototype.toString.call(null));              // [object Null]
    console.log(Object.prototype.toString.call(null).slice(8, -1)); // Null
扩展知识
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值