面题系列:深浅拷贝

161 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

题目描述

我们经常在操作数据的时候遇到数据拷贝问题,你知道浅拷贝与深拷贝的区别吗?日常中可以通过哪些方式来实现深浅拷贝的?你还能通过手写来实现他们吗?

考察点

  1. 对浅拷贝与深度拷贝的理解
  2. 项目开发数据操作技巧的经验
  3. for...in,hasOwnProperty,数据类型以及递归的使用状况

解题思路

let pokemon = {
    name: "皮卡丘",
    hp: 48,
    skill: {
        a: {
            name: "电光一闪",
            power: 40
        },
        b: {
            name: "十万伏特",
            power: 90
        },
        c: null,
        d: null
    }
}

// 浅拷贝
let obj1 = Object.assign(pokemon)
obj1.skill.a.power = 0;
console.log(pokemon.skill.a.power)  // 0
console.log(pokemon.skill == obj1.skill) // true

// 深拷贝
let obj2 = deepClone(pokemon)
obj2.skill.b.power = 0;
console.log(pokemon.skill.b.power)  // 90
console.log(pokemon.skill == obj2.skill) // false

浅拷贝

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

我们平时实现方式很多如:

// 1
let array = [...array]
let obj1 = {...obj}
// 2
Object.assign(obj)
// 3
Array.prototype.concat()
Array.prototype.slice()

深拷贝

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

我们平时如果遇到非正则等特殊类型的数据,仅仅是普通对象或者数组的话,可以用 JSON.parse(JSON.stringify())来完成,也就是说,把对象转成字符串后,再把字符串转成对象,那么本身又开辟了一个新地址了。当然也可以通过递归手写实现。

实现

  1. 浅拷贝
Object.assign = Object.assign || function () {
    if (arguments.length == 0) throw new TypeError('Cannot convert undefined or null to object');

    var target = arguments[0],
        args = Array.prototype.slice.call(arguments, 1),
        key
    args.forEach(function (item) {
        for (key in item) {
            item.hasOwnProperty(key) && (target[key] = item[key])
        }
    })
    return target
}

因为ES6就有了Object.assign,如果手写的话最好判断是否存在Object.assign,至于浅拷贝这个问题就是把第一层的内存地址,生成新对象返回回来。

  1. 深拷贝
function deepClone(obj1, obj2) {
    var obj2 = obj2 || {};
    var toStr = Object.prototype.toString;
    var arrStr = toStr.call([]);
    for (var prop in obj1) {
        if (obj1.hasOwnProperty(prop)) {
            if (obj1[prop] !== null && typeof (obj1[prop]) == "object") {
                obj2[prop] = toStr.call(obj1[prop]) == arrStr ? [] : {};
                deepClone(obj1[prop], obj2[prop]);
            }
            else {
                obj2[prop] = obj1[prop];
            }
        }
    }
    return obj2;
}

这里我们用递归的写法反复判断其类型然后进行不同的拷贝任务,值得注意的是,普通对象和数组要区分出来。