Skip to content

深拷贝

乞丐版

js
JSON.parse(JSON.stringfy(data))

解决不能循环引用,除 Object、Array 外的类型

普通版

js
function deepClone(data){
	if(data && data instanceof Object){
		const obj = Array.isArray(data) ? [] : {}
		for(const key in data){
			obj[key] = deepClone(data[key]);
		}
		return obj
	} else {
		return data;
	}
}

解决循环引用

额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝

js
function deepClone(data, map = new Map()) {
	if(data && data instanceof Object){
		const obj = Array.isArray(data) ? [] : {}

		// 解决循环引用
		if (map.has(data)) return map.get(data)
		map.set(data, obj)

		for(const key in data){
			obj[key] = deepClone(data[key], map);
		}
		return obj
	}else{
		return data;
	}
}

因为递归中的某一个函数结束了,就会释放函数中的变量,但如果使用 Map,就会一直引用着,导致释放不掉,可以使用 WeakMap 优化内存

js
function deepClone(data, map = new WeakMap()) {}

识别 Map、Set、Function

首先定一个方法,识别类型

js
function getType(target) {
  return Object.prototype.toString.call(target);
}

抽离要识别的类型

js
const deepTag = ['[object Object]', '[object Array]', '[object Map]', '[object Set]']

获取构造方法

js
function getInit(target) {
  const Ctor = target.constructor;
  return new Ctor();
}

深拷贝

js
function deepClone(data, map = new WeakMap()) {
  if (!deepTag.includes(getType(data))) {
    // 函数也无需深拷贝,直接返回即可(lodash 就是如此做的)
    return data
  }
  
  const type = getType(target);
  const obj = getInit(target);
  
  // 防止循环引用
  if (map.has(data)) return map.get(data);
  map.set(target, obj);
  
  if (type === '[object Set]') {
    data.forEach(value => {
      obj.add(deepClone(value, map))
    })
    return obj
  }
  
  if (type === '[object Map]') {
    data.forEach((value, key) => {
      obj.set(key, deepClone(value, map))
    })
    return obj
  }
  
  for (const key in data) {
    obj[key] = deepClone(data[key], map)
  }
  return obj
}