js中的深拷贝deepClone函数实现

1,054 阅读2分钟

学在前面

  1. MapWeakMapSet
  2. for in 和 for of 的区别:
  • for in 语句以任意顺序遍历一个对象的除Symbol以外的可枚举属性(只能遍历对象)。
  • for...of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句
  • for of 循环的是value, for in 循环的是key值
  • for in 遍历的属性包括原型, for of 不包括原型上的属性
  • for in 遍历数组可能顺序不同
  1. 判断变量的类型(typeof instanceof constructor Object.prototype.toString.call)
  • typeof 只能判断基本类型 不能判断引用类型(Array/Object)
  • instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链
  • constructor 返回创建实例对象的 Object 构造函数的引用
  • Object.prototype.toString.call 精准判断变量的类型

简单的深拷贝

  • 检测数据类型
const checkType = (type) => (val) => Object.prototype.toString.call(val) === `[object ${type}]`;
const utils = {};
const needArray = ['Object', 'Array', 'String', 'Number', 'Boolean', 'Null', 'Undefined', 'Null', 'Symbol', 'Error', 'RegExp', 'Set', 'Map', 'WeakMap','Date'];
needArray.forEach((methods) => {
	utils[`is${methods}`] = checkType(methods);
})
  • 先实现一个简单的deepClone
const deepClone = (arr) => {
  if (typeof arr !== 'object' || arr == null) return arr;
  let result = new arr.constructor();
  for (let key in arr) {
    if (arr.hasOwnProperty(key)) {
      result[key] = deepClone(arr[key]);
    }
  }
  return result;
}
let obj = {
    name: 'zhangsan',
    d: {
        x: 100,
        y: 200,
        z: [1, {name: '456'}]
    },
   count: 1,
   flag: true,
   c: null,
   m: undefined,
   h: BigInt(1111)
}
let obj1 = deepClone(obj);
// 上面utils涵盖的类型都测试了一遍 可以通过
  • 上面的实现会遇到一个问题,如果一个对面类型的属性值是自己或者赋的值是一个对象里面的属性是自己,就会出现死循环这种情况
let obj = {
    name: 'zhangsan',
}
obj.child = obj;
deepClone(obj) 
  • 进一步优化解决死循环的问题
const deepClone = (arr, weakMap = new WeakMap()) => {
  if (typeof arr !== 'object' || arr == null) return arr;
  if (!utils.isWeakMap(arr)) {
  	weakMap = new WeakMap();
  }
  let result = new arr.constructor();
  if (weakMap.has(arr)) {
    return weakMap.get(arr);
  }
  weakMap.set(arr, result);
  for (let key in arr) {
    if (arr.hasOwnProperty(key)) {
      result[key] = deepClone(arr[key], weakMap)
    }
  }
  return result
}

完整版的deepClone(这里需要对里面的数据进行特殊处理:Symbol, Error, RegExp, Set, Map,Date)

const checkType = (type) => (val) => Object.prototype.toString.call(val) === `[object ${type}]`;
const utils = {};
const needArray = ['Object', 'Array', 'String', 'Number', 'Boolean', 'Null', 'Undefined', 'Null', 'Symbol', 'Error', 'RegExp', 'Set', 'WeakMap', 'Map','Date'];
needArray.forEach((methods) => {
	utils[`is${methods}`] = checkType(methods);
})
const deepClone = (arr, weakMap = new WeakMap()) => {
    if (typeof arr !== 'object' || arr == null) return arr;
    if (!utils.isWeakMap(arr)) {
        weakMap = new WeakMap();
    }
    // Symbol
    if (utils.isSymbol(arr)) return Object(Symbol.prototype.valueOf.call(arr));
    
    // 日期
    if (utils.isDate(arr)) return new arr.constructor(arr);

    // Error
    if (utils.isError(arr)) return new arr.constructor(arr.message);

    // 正则表达式
    if (utils.isRegExp(arr)) return new RegExp(arr);

    let result = new arr.constructor();
    // set
    if (utils.isSet(arr)) {
        arr.forEach(item => result.add(deepClone(item, weakMap)))
        return result;
    }
    // Map
    if (utils.isMap(arr)) {
        arr.forEach((key, value) => {
            result.set(deepClone(key, weakMap), deepClone(value, weakMap)) 
        })
        return result;
    }
    // Array Object
    if (utils.isArray(arr) || utils.isObject(arr)) {

        if (weakMap.has(arr)) {
          return weakMap.get(arr);
        }
        weakMap.set(arr, result);
        for (let key in arr) {
          if (arr.hasOwnProperty(key)) {
            result[key] = deepClone(arr[key], weakMap)
          }
        }
        return result
    }
  }
let obj = {
    name: 'zhangsan',
    d: {
        x: 100,
        y: 200,
        z: [1, {name: '456'}]
    },
   count: 1,
   flag: true,
   
   c: null,
   m: undefined,
   h: BigInt(1111),
   s: Symbol('s'),
   t: new Date(),
   e: new Error('sb'),
   r: /\w*/gi, 
     p: new Map([[0, '红'], [1, '太'], [2, '狼'], ['美', 4], ['羊', 5], ['羊', 6], [{name: '1111'}, '1111']]),
}
console.log(deepClone(obj))