【源码阅读 | xe-utils源码 | 10】clone 深拷贝(考虑多种数据类型)

1. 背景

  前面的源码阅读中,已经提到过:对象是引用类型,如果给一个变量直接赋值对象,相当于赋值的是内存地址(即为浅拷贝),会导致修改该变量属性时,影响到原对象

let oldObj = {
	name: 'Jokerls'
}

// 浅拷贝
let shallowCopyObj = oldObj

shallowCopyObj.name = '乐乐' // 该修改会导致 oldObj.name 输出为 '乐乐'

  因此我们希望有一个函数,可以真正地 复制一份新的对象出来,任何修改都不影响原对象的属性(即深拷贝)。这个功能对应的函数,在 xe-utils 源码中称为 clone 函数。

let oldObj = {
	name: 'Jokerls'
}

// 深拷贝
let shallowCopyObj = clone(oldObj)

shallowCopyObj.name = '乐乐' // 该修改后,oldObj.name 输出仍然为 'Jokerls'

2. 源码解析

源码较长,因此我会拆解成多个部分进行讲解,会让你更容易理解
深拷贝的实现思路为:
  1)沿着对象或数组深层遍历,直到子节点为值类型,即可直接返回
  2)若子节点仍为引用类型,则继续递归遍历,重复第1-2步


2.1 clone 函数

可以看到,clone 函数做了两件事

  • obj 存在,则调用拷贝方法
  • obj 不存在,则直接返回值(如 null、undefined、空字符串等)

注:尽管 clone 函数可以传参数 deep 来控制是否执行深拷贝,但本章节只讨论深拷贝

/**
 * 浅拷贝/深拷贝
 *
 * @param {Object} obj 对象/数组
 * @param {Boolean} deep 是否深拷贝
 * @return {Object}
 */
function clone(obj, deep) {
  if (obj) {
    return copyValue(obj, deep);
  }
  return obj;
}

2.2 copyValue 函数

顺着思路来看下 copyValue 函数的实现,详细注释放在代码片段中

1)整体函数结构
  注:为方便阅读,先把深拷贝的逻辑用省略号代替,从第二小点开始讲解省略号中的逻辑。
  先判断若 valnullundefined空字符串等,则直接返回,否则进行拷贝操作。

function copyValue(val, isDeep) {
  if (val) {
   	// ... 
  }
  return val
}

2)Object & Arguments 类型的深拷贝

function copyValue(val, isDeep) {
  if (val) {
    // 1.关于 objectToString.call,第2期源码【判断Array】类型中有讲解,这里不再赘述
    //   简言之,可以将参数的类型显示出来,类如 '[object Array]'
    switch (objectToString.call(val)) {
      case "[object Object]":
      case "[object Arguments]": {
        // 2.根据当前实例的构造函数,生成一个新的实例并返回,getCtorObject 在下方会进行讲解
        var restObj = getCtorObject(val);
        
        // 3.遍历对象,并对其中的属性进行递归遍历
        //   handleValueClone 下方会讲解,暂时可理解为递归调用了 copyValue() 函数
        objectEach(val, function (item, key) {
          restObj[key] = handleValueClone(item, isDeep);
        });
        return restObj;
      }
      ...
    }
  }
  return val;
}

3)Date & RegExp 类型的深拷贝
  return getCtorObject(val, val.valueOf( )) 的含义,举个例子:
  有日期为 '2022-02-16',通过 new Date('2022-02-16') 重新复制一份并返回,这里的 Date( ) 就是 构造函数,而 '2022-02-16' 就是 val.valudOf 之后得到的原始值

function copyValue(val, isDeep) {
  if (val) {
    switch (objectToString.call(val)) {
      ...
      case "[object Date]":
      case "[object RegExp]": {
        // 1.相当于获取到原来的值和构造函数,然后用构造函数重新生成一个新的值作为返回值
        return getCtorObject(val, val.valueOf());
      }
      ...
    }
  }
  return val;
}

4)Array 类型的深拷贝

function copyValue(val, isDeep) {
  if (val) {
    switch (objectToString.call(val)) {
      ...
      case "[object Array]": {
        var restArr = [];
        arrayEach(val, function (item) {
          // 1.通过递归遍历旧数组中的每一项,赋值给新的数组,达到深拷贝的目的
          //   注意!如果通过 let newArr = oldArr 直接赋值的方式,则仍然是浅拷贝
          restArr.push(handleValueClone(item, isDeep));
        });
        return restArr;
      }
     	...
    }
  }
  return val;
}

5)Set & Map 类型的深拷贝
  注:Set 类型和 Map 类型都可以用 forEach 来遍历,新增的方法则有所不同,Setadd(key, value),而 Mapset(key, value)

function copyValue(val, isDeep) {
  if (val) {
    switch (objectToString.call(val)) {
      ...
      case "[object Set]": {
        var restSet = getCtorObject(val);
        restSet.forEach(function (item) {
          restSet.add(handleValueClone(item, isDeep));
        });
        return restSet;
      }
      case "[object Map]": {
        var restMap = getCtorObject(val);
        restMap.forEach(function (item, key) {
          restMap.set(handleValueClone(item, isDeep));
        });
        return restMap;
      }
     	...
    }
  }
  return val;
}

6)getCtorObject 函数
该函数做了两件事:

  • 通过 val 实例的 原型链,找到其对应的构造函数。如 日期类型 会找到 Date( ),字符串类型会找到 String( )。
  • 通过 构造函数,重新生成一个新的实例并返回(即复制了一份新的内容
function getCtorObject(val, args) {
	var Ctor = val.__proto__.constructor;
	return args ? new Ctor(args) : new Ctor();
}

7)handleValueClone 函数
  该函数做了一件事:判断当前执行的是浅拷贝还是深拷贝。若为浅拷贝,则直接返回(相当于直接赋值语句);若为深拷贝,则递归遍历拷贝函数,直到拷贝完成。

function handleValueClone(item, isDeep) {
  return isDeep ? copyValue(item, isDeep) : item;
}

3. 完整源码

var objectToString = require("./staticObjectToString");
var objectEach = require("./objectEach");
var arrayEach = require("./arrayEach");

function getCtorObject(val, args) {
  var Ctor = val.__proto__.constructor;
  return args ? new Ctor(args) : new Ctor();
}

function handleValueClone(item, isDeep) {
  return isDeep ? copyValue(item, isDeep) : item;
}

function copyValue(val, isDeep) {
  if (val) {
    switch (objectToString.call(val)) {
      case "[object Object]":
      case "[object Arguments]": {
        var restObj = getCtorObject(val);
        objectEach(val, function (item, key) {
          restObj[key] = handleValueClone(item, isDeep);
        });
        return restObj;
      }
      case "[object Date]":
      case "[object RegExp]": {
        return getCtorObject(val, val.valueOf());
      }
      case "[object Array]": {
        var restArr = [];
        arrayEach(val, function (item) {
          restArr.push(handleValueClone(item, isDeep));
        });
        return restArr;
      }
      case "[object Set]": {
        var restSet = getCtorObject(val);
        restSet.forEach(function (item) {
          restSet.add(handleValueClone(item, isDeep));
        });
        return restSet;
      }
      case "[object Map]": {
        var restMap = getCtorObject(val);
        restMap.forEach(function (item, key) {
          restMap.set(handleValueClone(item, isDeep));
        });
        return restMap;
      }
    }
  }
  return val;
}

/**
 * 浅拷贝/深拷贝
 *
 * @param {Object} obj 对象/数组
 * @param {Boolean} deep 是否深拷贝
 * @return {Object}
 */
function clone(obj, deep) {
  if (obj) {
    return copyValue(obj, deep);
  }
  return obj;
}

module.exports = clone;
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值