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)整体函数结构
注:为方便阅读,先把深拷贝的逻辑用省略号代替,从第二小点开始讲解省略号中的逻辑。
先判断若 val
为 null
、undefined
、空字符串
等,则直接返回,否则进行拷贝操作。
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 来遍历,新增的方法则有所不同,Set
用 add(key, value)
,而 Map
用 set(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;