碎碎念
准备面试的一开始,就被深拷贝折磨了一番。倒不是说很难,只是不同人写的不一样,考虑的方面也不尽相同,所以我按照自己的理解写了一下。
考虑类型
深拷贝的难点就在于你需要考虑你复制的数据的类型是非常多的,我这里只考虑自己常用到的类型
- 基本类型
- 引用类型-函数、Object、Array、Symbol、Map、Get、Reg、Date
判断是否是引用类型使用工具函数isObject
//需要注意这里判断null,typeof null也是object
function isObject(target){
return typeof target === 'object' && (target !== null)
}
获取引用类型的具体类型使用getType
//获取类型基本就无脑用这种方法就行
function getType(target){
return Object.prototype.toString.call(target)
}
非引用类型
一个if,没什么好说的,不过这里注意的是function类型也一并当做非引用类型直接返回了,并不做特殊处理。
function deepClone(obj,map=new WeakMap()){
if(isObject(obj)){
}else return obj
}
引用类型
引用类型也大体分为两类,一类是像Date、Reg这种不需要遍历的,一类是像Array、Object这种需要遍历的。
将需要遍历的单独拿出来放入deepTag数组里
let deepTag = ['[object Map]','[object Set]','[object Array]','[object Object]']
不需要遍历的
Symbol
例如Object(Symbol(1))
因为Symbol的值是独一无二的,所以symbol对象的复制其实就是用同一个symbol创建一个Object,也即:
if(type==='[object Symbol]'){
return Object(Symbol.prototype.valueOf.call(obj));
}
RegExp
克隆正则分为三部分,一部分时克隆源,一部分是克隆标志符,一部分是克隆lastIndex。
function cloneReg(obj){
const result = new RegExp(obj.source,/\w*$/.exec(obj))
result.lastIndex = obj.lastIndex
return result
}
/\w*$/.exec(obj)的原理是对于正则串/xzc/ig可以匹配到标志符ig
Date/String/Number等
这一类都可以直接使用return new obj.constructor(obj)直接生成一个新对象返回
需要遍历的
对于所有需要遍历的,都需要使用cloneObj = new obj.constructor()来保留原型链,同时这样也不用特意去区分数组与对象了。
Map、Set
也不难,Map用set,Set用add即可
if(type==='[object Map]'){
obj.forEach((value,key)=>{
cloneObj.set(key,deepClone(value))
})
}
if(type==='[object Set]'){
obj.forEach((value)=>{
cloneObj.add(deepClone(value))
})
}
对象、数组
对象和数组也是和前面一样的遍历赋值,但是有一点需要注意的是,不能直接用for in这类语法遍历,因为对象里可能会有不可枚举型变量。而应该使用Reflect.ownKeys(obj)
for (let key of Reflect.ownKeys(obj)) {
cloneObj[key] = deepClone(obj[key])
}
循环调用
即a={} a.b=a这种情况,也很好解决,使用WeakMap就行。之所以使用WeakMap而不是Map是因为WeakMap对键是弱引用的
let a = {x: 12};
let b = {y: 13};
let map = new Map();
let weakMap = new WeakMap();
map.set(a, '14');
weakMap.set(b, '15');
a = null;
b = null;
上面代码中a,b设置为null以后,map不会被回收,而weakMap会被回收。
解决循环调用只需要两三行代码即可
if(map.has(obj)){
return map.get(obj)
}
map.set(obj,cloneObj)
最后
至此,深拷贝也就写完了。测试用例如下
const map = new Map();
map.set('key', 'value');
map.set('violet', 'erergarden');
const set = new Set();
set.add('violet');
set.add('erergarden');
let obj = {
num: 0,
str: '',
boolean: true,
unf: undefined,
nul: null,
obj: { name: '我是一个对象', id: 1 },
arr: [0, 1, 2],
func: function () { console.log('我是一个函数') },
date: new Date(0),
reg: new RegExp(/\d+/ig) ,
[Symbol('1')]: 1,
symbol: Object(Symbol(1)),
map,
set
};
Object.defineProperty(obj, 'innumerable', {
enumerable: false, value: '不可枚举属性' }
);
obj.loop = obj
完整代码如下
function getType(target){
return Object.prototype.toString.call(target)
}
function isObject(target){
return typeof target === 'object' && (target !== null)
}
function cloneReg(target){
const result = new RegExp(target.source,/\w*$/.exec(target))
result.lastIndex = target.lastIndex
return result
}
let a = {
reg:'/xz/w*cz/gim'
}
let deepTag = ['[object Map]','[object Set]','[object Array]','[object Object]']
function deepClone(obj,map=new WeakMap()){
if(isObject(obj)){
let type = getType(obj)
let cloneObj
if(deepTag.includes(type)){
cloneObj = new obj.constructor()
}else{
if(type==='[object Date]'){
return new obj.constructor(obj)
}
if(type==='[object RegExp]'){
return cloneReg(obj)
}
if(type==='[object Symbol]'){
return Object(Symbol.prototype.valueOf.call(obj));
}
return new obj.constructor(obj)
}
if(type==='[object Map]'){
obj.forEach((value,key)=>{
cloneObj.set(key,deepClone(value,map))
})
}
if(type==='[object Set]'){
obj.forEach((value)=>{
cloneObj.add(deepClone(value,map))
})
}
if(map.has(obj)){
return map.get(obj)
}
map.set(obj,cloneObj)
for (let key of Reflect.ownKeys(obj)) {
cloneObj[key] = deepClone(obj[key],map)
}
return cloneObj
}else return obj
}