克隆

31 阅读2分钟

种类

深拷贝数组

  1. 直接遍历
  2. slice()
  3. concat() 该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。

深拷贝对象

  1. ES6的Object.assign
  2. 1.JSON.parse(JSON.stringify(XXXX))
  3. 递归

方法一

const getType = (obj)=> {
	var toString = Object.prototype.toString;
	var map = {
	    '[object Boolean]'  : 'boolean', 
	    '[object Number]'   : 'number', 
	    '[object String]'   : 'string', 
	    '[object Function]' : 'function', 
	    '[object Array]'    : 'array', 
	    '[object Date]'     : 'date', 
	    '[object RegExp]'   : 'regExp', 
	    '[object Undefined]': 'undefined',
	    '[object Null]'     : 'null', 
        '[object Object]'   : 'object',
        '[object Symbol]'   : 'symbol'
	};
	if(obj instanceof Element) {//因为对不同标签,toString会返回对应不同标签的构造函数
        return 'element';
	}
	return map[toString.call(obj)];
}
// ES6 flags 代替此方法
// const getRegExp = re => {
//   var flags = '';
//   if (re.global) flags += 'g';
//   if (re.ignoreCase) flags += 'i';
//   if (re.multiline) flags += 'm';
//   return flags;
// };

/**
* deep clone
* @param  {[type]} parent object 需要进行克隆的对象
* @return {[type]}        深克隆后的对象
*/
const deepClone = oldObj => {
  // 维护两个储存循环引用的数组
  const oldObjArr = [];
  const newObjArr = [];

  const clone = oldObj => { 
      
    let newObj, proto;

    const type = getType(oldObj);

    switch(type){
        case 'boolean':
        case 'number':
        case 'string':
        case 'null':
        case 'undefined':
        case 'function':{
            return oldObj;
            break;
        }
        case 'symbol':{
            return Symbol(Symbol.keyFor(oldObj).toString());
            break;
        }
        case 'array':{
            newObj = [];
            break;
        }
        case 'regExp':{
            newObj = new RegExp(oldObj.source, oldObj.flags);
            if (oldObj.lastIndex) newObj.lastIndex = oldObj.lastIndex;
            break;
        }
        case 'date':{
            newObj = new Date(oldObj.getTime());            
            break;
        }
        //case 'obj':
        default:{
            // 处理对象原型
            proto = Object.getPrototypeOf(oldObj);
            // 利用Object.create切断原型链
            newObj = Object.create(proto);
            break;
        }
   }

   // 处理循环引用
   const index = oldObjArr.indexOf(oldObj);
   if (index != -1) {// 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象    
     return newObjArr[index];
   }

   oldObjArr.push(oldObj);
   newObjArr.push(newObj);
  /*数组和对象都可以用forin语句,虽然数组使用forin会有一个问题(具体看最下面)。
  但是这里不会影响,所以这么用 
  */
   for (let i in oldObj) {// 递归     
     newObj[i] = clone(oldObj[i]);
   }

   return newObj;
 };


 return clone(oldObj);
}

方法二

const isObjFun = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
const deepClone = function(obj, hash=weakMap()) {
  if (hash.has(obj)) {
    return hash.get(obj)
  }
  let type = [Date, RegExp, Set, Map, WeakMap, WeakSet]
  if (type.includes(obj.constructor)) {
    return new Obj.constructor(obj)
  }
  let allDesc = Object.getOwnPropertyDescriptors(obj) 
  // allDesc 解决 function symbol 
  let cloneObj = Object.create(Object.getPrototypeOf(obj, allDesc))
  hash.set(obj,cloneObj)

  for (let key of Reflect.ownKeys(obj)) {
    cloneObj[key] = isObjFun(obj[key]) ? deepClone(obj[key], hash) : obj[key]
  }
  return cloneObj
}

核弹扩展

1 Map和WeakMap的主要区别:

Map对象的键可以是任何类型,但WeakMap对象中的键只能是对象引用

WeakMap不能包含无引用的对象,否则会被自动清除出集合(垃圾回收机制)。

WeakSet对象是不可枚举的,无法获取大小。

2 Object.getOwnPropertyDescriptors() 方法用来获取一个对象的所有自身属性的描述

a: {
    value: 1
    writable: true
    enumerable: true
    configurable: true
    __proto__: Object
    __proto__: Object
}

复制

3 for in 对比 Reflect.ownKeys(obj)

1 静态方法 Reflect.ownKeys() 返回一个由目标对象自身的属性键组成的数组
2 for in 取不到 不可枚举的key 和 symbol