对深拷贝挺用心的一次总结

理解浅拷贝和深拷贝的

所谓拷贝就是复制,JS中数据类型分为基本数据类型(按值传递)和引用数据类型(按引用传递),所以在复制过程会有所不同。 为了方便理解,我们可以把复制数据类比为效仿别人开宾馆

基本数据类型复制图解(桌椅板凳可以照搬)

我们现在想开一家和宾馆A一样的宾馆B(复制数据),我们首先需要把宾馆A基本设施桌椅板凳(基本数据类型)拿过来。

引用数据类型(房间照搬不了,只能拿个房卡)

好了,复制工程的大头来了,房间照搬不可能,为了省劲儿,直接把宾馆A的房卡(引用数据类型在栈中的引用)照搬了一份。

勉强看来,开宾馆B的任务算是完成了。开业把!但是这时候客人来了拿到的房卡,但是这房卡是宾馆A的,开门进去万一看到一些不可描述的事情(数据的改动)...岂不是尴尬!

我们发现照搬房卡(浅拷贝)行不通,于是只能自己建房间,从宾馆A的房间一个个挨着进去,一个个的桌椅板凳的搬(复制),这样才能建立自己宾馆B的房间(深拷贝)。

深拷贝的定义 - 背下来应付面试官

深拷贝的过程就是指创建一个和原数据内容和结构一模一样的新对象,新对象内的所有值的指针地址都是新的地址

代码层面看浅、深拷贝

检测深浅拷贝:改变新数据, 都不改变数据

/**
* 基本数据类型拷贝
**/ 
var num1 = 5;           // 数据a
var num2 = num1;        // 把a的值复制给b
console.log(num2)       // 5

// 当我们改变num2,看是否会改变原数据a
num2 = 2;
console.log(num1)  // 5

复制代码

结论:基本数据类型都是深拷贝(直接复制即可)


/**
* 引用数据类型拷贝
**/ 
var obj = {               // 原始数据obj
    name: 'nike',
    age: 18,
    sex: '男'
}
// ** 浅拷贝 **
var shallowCopyObj = obj;         // 把原始对象直接复制给新对象shallowCopyObj,此处完成引用复制,也就是浅拷贝
console.log(shallowCopyObj.name);  // nike
shallowCopyObj.name = 'jack';     // 改变新对象的name属性
console.log(obj.name);            // 'jack'  (原数据的name属性也被改变)

// ** 深拷贝 **
var deepCopyObj = {};              // 深拷贝的思路:拆分原数据,把原数据的每个属性单独复制
for(let item in obj){
    deepCopyObj[item] = obj[item];
}
console.log(deepCopyobj.name);     // nike
deepCopyobj.name = 'mary';         // 复制完成后,改变deepCopyObj的name属性
console.log(obj.name);            // 'nike'  (原数据的name属性没有被改变)

复制代码

结论:引用数据类型不能直接复制,需要遍历属性,分别复制。而对于属性值有引用数据类型的多层嵌套对象,需要进行属性值数据类型检测,如果是引用数据类型,再继续遍历该属性值,进行复制。

JS实现深拷贝

为了深拷贝多层嵌套对象,使用递归复制原数据

/**
* @param {Object} initData 准备要复制的对象
* @param {Object} copyData 复制的载体对象,可以为空,为空的话表示要复制到一个空对象或者空数组上
***/
function deepClone(initData,copyData){
    var copyData = copyData || {};          // copyData初始化

    for(let item in initData){ // 遍历
        if(typeof initData[item] === 'object'){
            copyData[item] = (initData[item].constructor == Object) ? {} : [];
            arguments.callee(initData[item],copyData[item]);
        }else{
            copyData[item] = initData[item];
        }
    }
    
    return copyData
}
复制代码

以上方法就实现了对多层嵌套对象的深拷贝,为了增加方法的健壮性,我们可以增加对参数类型的检测,以及对复制ES6中map,set对象的支持

/**
* @param {Object} initData 准备要复制的对象
* @param {Object} copyData 复制的载体对象,可以为空,为空的话表示要复制到一个空对象或者空数组上
***/
function deepClone(initData, copyData) {
    if (typeof initData !== 'object') { return initData }; // 如果要复制的数据是基本数据类型,则直接返回

    var copyData = copyData || {};          // copyData初始化
    
    switch (initData.constructor) {
        case Array:
            copyData = [];
        case Object:
            for (var property in initData) {
                copyData[property] = typeof initData[property] === 'object' ? arguments.callee(initData[property]) : initData[property];
            }
            break;
        case Map:
            copyData = new Map();
            initData.forEach((value, key) => {
                copyData.set(key, typeof value === 'object' ? arguments.callee(value) : value);
            });
            break;
        case Set:
            copyData = new Set();
            initData.forEach(value => {
                copyData.add(typeof value === 'object' ? arguments.callee(value) : value);
            });
            break;
    }
    
    return copyData
}
复制代码

实现深拷贝的奇技淫巧

以下记录几种可以实现深拷贝但是使用场景有所限制的方法,在某些场景还是可以简化我们的工作

1.数组只进行一层深拷贝(子元素都是基本数据类型)

  • slice() :截取数组返回一个新的数组,包含从 start 到 end (不包括该元素)的元素。思路就是截取整个数组。
var arr = [1,2,3,4];
var newArr = arr.slice();   // 当slice()不带任何参数的时候,默认返回一个长度和原数组相同的新数组
newArr[0] = 9;
console.log(arr);           // [1,2,3,4]
复制代码
  • concat(arr):合并arr返回一个新的数组。该数组是通过把所有 arr 参数添加到 原数据 中生成的。所以我们只需要合并和空数组,就能实现对原数组的拷贝。
var arr = [1,2,3,4];
var newArr = arr.concat();   // concat()不带任何参数的时候,相当于合并和空数组
newArr[0] = 9;
console.log(arr);           // [1,2,3,4]
复制代码

2.对象只进行一层拷贝 (子元素都是基本数据类型) 以下两个方法都是es6,慎用

  • Object.assign(target, ...sources):该方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象target。它将返回目标对象。所以我们可以把目标对象target设置成空对象,完成对对象的拷贝
var person = {
    name:'nike',
    age:18
}

var newPerson = Object.assign({},person);
newPerson.age = 20;
console.log(person.age);    // 18
复制代码
  • 拓展运算符(...):作用就是把数组或类数组对象展开成一系列用逗号隔开的值
var person = {
    name:'nike',
    age:18
}

var newPerson = {...person};
newPerson.age = 20;
console.log(person.age);    // 18
复制代码

3.用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。

用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。可以脱离原对象的引用,也可以实现对多层嵌套对象的深拷贝。

缺点:这种方法能正确处理的对象只有 Number, String, Boolean, Array等能够被 json 直接表示的数据结构。而如function、正则对象等都无法正确完成转换。

var person = {
    name:'nike',
    age:18
}

var newPerson = JSON.parse(JSON.stringify(person));
newPerson.age = 20;
console.log(person.age);    // 18

// 当属性中存在function等
var people = {
    name:'mary',
    age:18,
    study:function(){
        console.log(1)
    }
}

var newPeople = JSON.parse(JSON.stringify(people));
console.log(newPeople.study);    // undefined
复制代码

结语

加入掘金也有两年多了,但是也只是看别人的文章,自己也没动手总结过!在这次找工作面试的过程中,感触也挺多。

  • 有些你认为很简单东西,别人问你的时候你还真不一定说得清

  • 有些工作中碰到的问题,不总结记录下,着实容易忘记,面试的时候被问住了,反倒显得自己是编的

  • 基础真的很重要,即使在你工作绝大部分的时间你用不到,但是它决定你成长的高度

所以我也想着借着掘金这个优秀的平台,一方面记录下自己学习和工作经验,另一方面也为以后的职业发展打点基础。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值