1. 背景:为什么有必要掌握深拷贝?
在业务中经常会遇到需要对模板数据/初始数据进行加工处理,而且,该数据可能能需要在多处被复用。
简单粗暴更改原始数据的做法,会污染其他依赖项,所以我们需要对原始数据进行一份深拷贝,保持各个依赖项数据的独立性,做到既可以复用,又不互相污染。
2. JS中没有自带的深拷贝API
也许我们用到过Object.assign()进行对象合并,亦或者用数组的slice(), concat()方法对数组进行复制,但这几种方法都不是深拷贝,是浅拷贝。
简言之,深拷贝就是每个对象都是独立的,独立意味着拥有各自的独立内存空间;而浅拷贝意味着拥有公共的引用空间。
所以,值类型的数据不存在浅拷贝的问题,只有引用数据类型才存在。愿更深入的了解深拷贝和浅拷贝的,可以参考文章最末尾的「参考文章」。
3. 对于数组的深拷贝,一个很简单的做法:
let recursiveClone = val => Array.isArray(val) ? Array.from(val, recursiveClone) : val;
4. 封装:对于任意一个JS数据类型的深拷贝
概述JS的数据类型,从大致上分为两种:
- 基本数据类型:string, number, boolean, undefined, null, symbol
- 引用数据类型: object(这是一个统称,包含object, array, function 以及js单体内置对象如Date, RegExp, Error等)
对于基本数据类型,不存在深拷贝的问题,因为它们是值类型的数据。值类型的数据存放在栈内存中,重新赋值就是独立的。
而对于众多的引用数据类型,需要分别进行处理,集中处理object和array,这也是我们在业务中遇到最多的情况。
5.上代码:
const deepCloneTypes = ['Object', 'Array', 'Map', 'Set'];
function isObject(source) {
const type = typeof source;
return source !== null && (type === 'object' || type === 'function')
}
function getType(source) {
return (Object.prototype.toString.call(source)).split(' ')[1].slice(0, -1)
}
function processOtherType(source) {
const Ctor = source.constructor;
return new Ctor(source);
}
function processFunctionType (source) {
let _source = source.toString();
// 区分是否是箭头函数
if (source.prototype) {
// 如果有prototype就是普通函数
let argsReg = /function\s*\w*\(([^\)]*)\)/;
let bodyReg = /\{([\s\S]*)\}/;
let fnArgs = (argsReg.exec(source))[1];
let fnBody = (bodyReg.exec(source))[1];
console.log(fnArgs, fnBody);
return new Function (fnArgs, fnBody)
} else {
// 箭头函数没有prototype
return eval(_source)
}
}
function deepClone (source, map = new WeakMap()) {
// 首先用typeof来筛选是否是引用数据类型,如果连引用数据类型都不是,那么就判断是基本数据类型,直接返回即可
if (!isObject(source)) {
return source
}
const type = getType(source);
let cloneTarget;
// 防止循环引用
if (map.get(source)) {
return map.get(source);
}
map.set(source, cloneTarget);
// 接下来判断是否是需要进行循环拷贝的引用数据类型,诸如new Boolean, new Number这样的,也不需要循环拷贝
if (!deepCloneTypes.includes(type)) {
cloneTarget = processOtherType(source);
} else {
cloneTarget = new source.constructor();
// return Object.create(source.constructor.prototype) //不能这样,这样是创造了一个对象
if (type === 'Object' || type === 'Array') {
let keys = type === 'Object' ? Object.keys(source) : undefined; // 如果支持optional chaining的话可以写成?.
(keys || source).forEach((val, key) => {
if (keys) {
key = val;
}
cloneTarget[key] = deepClone(source[key], map); //在这里进行递归调用
})
}
if (type === 'Function') {
cloneTarget = processFunctionType(source)
}
if (type === 'Map') {
source.forEach((val, key) => {
cloneTarget.set(key, deepClone(val, map))
})
}
if (type === 'Set') {
source.forEach((val, key) => {
cloneTarget.add(deepClone(val, map))
})
}
}
return cloneTarget
}
参考:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。