Skip to content

Latest commit

 

History

History
580 lines (510 loc) · 12.9 KB

实现一个深拷贝.md

File metadata and controls

580 lines (510 loc) · 12.9 KB

实现一个深拷贝

const mapTag = '[object Map]'
const setTag = '[object Set]'
const arrayTag = '[object Array]'
const objectTag = '[object Object]'
const argsTag = '[object Arguments]'
const boolTag = '[object Boolean]'
const dateTag = '[object Date]'
const numberTag = '[object Number]'
const stringTag = '[object String]'
const symbolTag = '[object Symbol]'
const errorTag = '[object Error]'
const regexpTag = '[object RegExp]'
const funcTag = '[object Function]'

const deepTags = [
  mapTag,
  setTag,
  arrayTag,
  objectTag,
  boolTag,
  dateTag,
  numberTag,
  stringTag,
  symbolTag,
  errorTag,
  regexpTag,
  funcTag,
]

function forEach(array, iteratee) {
  let index = -1
  const { length } = array
  while (++index < length) iteratee(array[index], index)

  return array
}

function isObject(target) {
  const type = typeof target
  return target && (type === 'object' || type === 'function')
}

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

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

function cloneSymbol(target) {
  return Object(Symbol.prototype.valueOf.call(target))
}

function cloneReg(target) {
  const reFlags = /\w*$/
  const result = new target.constructor(target.source, reFlags.exec(targe))

  result.lastIndex = target.lastIndex
  return result
}

function cloneFunction(func) {
  const bodyReg = /(?<={)(.|\n)+(?=})/m
  const paramReg = /(?<=\().+(?=\)\s+{)/
  const funcString = func.toString()

  if (func.prototype) {
    const param = paramReg.exec(funcString)
    const body = bodyReg.exec(funcString)
    if (body) {
      if (param) {
        const paramArr = param[0].split(',')
        return new Function(...paramArr, body[0])
      }

      return new Function(body[0])
    }

    return null
  }

  return eval(funcString)
}

function cloneOtherType(target, type) {
  const Ctor = target.constructor
  switch (type) {
    case boolTag:
    case numberTag:
    case stringTag:
    case errorTag:
    case dateTag:
      return new Ctor(target)

    case regexpTag:
      return new cloneReg(target)

    case symbolTag:
      return cloneSymbol(target)

    case funcTag:
      return cloneFunction(target)

    default:
      return null
  }
}

function clone(targe, map = new WeakMath()) {
  // 克隆原始类型
  if (!isObject(target)) return target

  // 初始化
  const type = getType(target)
  let cloneTarget

  if (deepTag.includes(type)) cloneTarget = getInit(target, type)
  else return cloneOtherType(target, type)

  // 防止循环引用
  if (map.get(target)) return target

  map.set(target, cloneTarget)

  // 克隆set
  if (type === setTag) {
    target.forEach((value) => cloneTarget.add(key, clone(value)))

    return cloneTarget
  }

  // 克隆map
  if (type === mapTag) {
    target.forEach((val, key) => cloneTarget.set(key, clone(value)))

    return cloneTarget
  }

  // 克隆对象和数组
  const keys = type === arrayTag ? undefined : Object.keys(target)
  forEach(keys || target, (value, key) => {
    if (keys) key = value

    cloneTarget[key] = clone(target[key], map)
  })

  return cloneTarget
}

实现深拷贝

function deepCopy (obj, cache = []) {
    if (obj === null || typeof obj !== 'object') {
        return obj
    }
    if (Object.prototype.toString.call(obj) === '[object Date]') return new Date(obj)
    if (Object.prototype.toString.call(obj) === '[object RegExp]') return new RegExp(obj)
    if (Object.prototype.toString.call(obj) === '[object Error]') return new Error(obj)
    const item = cache.filter(item => item.original === obj)[0]
    if (item) return item.copy
    let copy = Array.isArray(obj) ? [] : {}
    cache.push({
        original: obj,
        copy
    })
    Object.keys(obj).forEach(key => {
        copy[key] = deepCopy(obj[key], cache)
    })
    return copy
}
deepCopy($obj)
/*
{
    a: null
    b: undefined
    c: {a: 1, d: {…}}
    date: Fri Apr 10 2020 20:06:08 GMT+0800 (中国标准时间) {}
    e: /regexp/
    f: Error: Error: error at deepCopy (<anonymous>:8:74) at <anonymous>:19:21 at Array.forEach (<anonymous>) at deepCopy (<anonymous>:18:22) at <anonymous>:24:1
    func: ƒ ()
    symbol: Symbol()
}
*/
/**
 * 1.如何深拷贝呢
 * 方法一:Json.stringify
 */

let arr = [{ name: "baozhen", age: 18 }, "dog", { habit: "打游戏" }];
let testObj = {
  a: "111",
  b: {
    c: {
      y: 123,
    },
    d: [1, 2, 3],
  },
  e: "123",
};

let copyArr = JSON.parse(JSON.stringify(arr));
copyArr[0].name = "zhenzhen";
console.log(arr[0].name); //baozhen
console.log(copyArr[0].name); //zhenzhen
/** 深拷贝成功! */


/** 正文 实现一个深拷贝 */
function myDeepCopy(obj, map = new WeakMap()) {
  if (typeof obj !== "object") return;
  if (obj instanceof Date) {
    const copyDate = new Date();
    copyDate.setTime(obj.getTime());
    return copyDate;
  }
  if (obj instanceof RegExp) {
    const Constructor = obj.constructor;
    return new Constructor(obj);
  }
  let newObj = obj instanceof Array ? [] : {};
  if (map.get(obj)) {
    return map.get(obj);
  }
  map.set(obj, newObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] =
        obj[key] instanceof Object ? myDeepCopy(obj[key], map) : obj[key];
    }
  }

  return newObj;
}
let testCopyObj = myDeepCopy(testObj);
testCopyObj.b.c = { yyy: "yyy" };
console.log(testCopyObj); //{ a: '111', b: { c: { yyy: 'yyy' }, d: [ 1, 2, 3 ] }, e: '123' }
console.log(testObj); //{ a: '111', b: { c: { y: 123 }, d: [ 1, 2, 3 ] }, e: '123' }

const target = {
  field1: 1,
  field2: undefined,
  field3: {
    child: "child",
  },
  field4: [2, 4, 8],
};
target.target = target;
let testCopyObj2 = myDeepCopy(target);

testCopyObj2.field3 = { a: 4 };
console.log(testCopyObj2);
console.log(target);

简化

function _deepCopy(obj) {
  if(typeof obj !== "object") return;
  if(obj instanceof Date) {
    const copy = new Date();
    copy.setTime(obj.getTime());
    return copy;
  }
  if(obj instanceof RegExp) {
    const Constructor = obj.constructor;
    return new Constructor(obj);
  }

  let newObj = obj instanceof Array? []: {};
  for(let key in obj) {
    if(obj.hasOwnProperty(key)) {
      newObj[key] = obj[key] instanceof Object? _deepCopy(obj[key]): obj[key];
    }
  }
  return newObj;
}
let obj = {
  name: 'Maple',
  info: {
    age: 20,
    work: '程序'
  }
}
let demo = _deepCopy(obj);
console.log(demo);
//{ name: 'Maple', info: { age: 20, work: '程序' } }

常用的实现深拷贝的方法:

使用JSON.stringify

function clone(obj) {
 if(obj === null || typeof obj !== 'object') {
  return obj
 }
 // Date类型
 if(obj instanceof Date) {
  const copy = new Date()
  copy.setTime(obj.getTime())
  return copy
 }
 // 正则表达式
 if(obj instanceof RegExp) {
  const Constructor = obj.constructor
  return new Constructor(obj)
 }
 // 如果是数组等引用数据类型
 if(obj instanceof Array || obj instanceof Object) {
  const copyObj = Array.isArray(obj) ? [] : {}
  for(const key in obj) {
   if(obj.hasOwnProperty(key)) {
    copyObj[key] = clone(obj[key])
   }
  }
  return copyObj
 } 
}
// https://juejin.cn/post/6844903929705136141#heading-10
// https://juejin.cn/post/7078289953699921956
function isArray(target) {
  return Array.isArray(target);
}

function isObject(target) {
  return target !== null && typeof target === 'object';
}

/**
 * 浅拷贝
 * @param {Object} origin 源对象
 * @param {Object} target 目标对象
 */
function clone(origin, target) {
  var target = target || {};
  for (let k in origin) {
    if (origin.hasOwnProperty(key)) {
      target[k] = origin[k];
    }
  }

  return target;
}
// 基础数据类型
// ============
console.log(Object.prototype.toString.call(null));
// [object Null]
console.log(Object.prototype.toString.call(undefined));
// [object Undefined]
console.log(Object.prototype.toString.call(true));
// [object Boolean]
console.log(Object.prototype.toString.call(123));
// [object Number]
console.log(Object.prototype.toString.call('String'));
// [object String]
console.log(Object.prototype.toString.call(Symbol()));
// [object Symbol]

// 需要遍历的引用类型
// ============
console.log(Object.prototype.toString.call({}));
// [object Object]
console.log(Object.prototype.toString.call([]));
// [object Array]
console.log(Object.prototype.toString.call(new Set()));
// [object Set]
console.log(Object.prototype.toString.call(new Map()));
// [object Map]

// 不需要遍历的引用类型
// =============
console.log(Object.prototype.toString.call(new Error()));
// [object Error]
console.log(Object.prototype.toString.call(new RegExp()));
// [object RegExp]
console.log(Object.prototype.toString.call(new String()));
// [object String]
console.log(Object.prototype.toString.call(new Number()));
// [object Number]
console.log(Object.prototype.toString.call(new Boolean()));
// [object Boolean]
console.log(Object.prototype.toString.call(new Date()));
// [object Date]
// console.log(Object.prototype.toString.call(window));
// [Object global]
console.log(Object.prototype.toString.call(JSON));
// [object JSON]
console.log(Object.prototype.toString.call(Math));
// [object Math]
console.log(Object.prototype.toString.call(Symbol()));
// [object Symbol]
console.log(Object.prototype.toString.call(function () {}));
// [object Function]

/**
 * 判断是否是对象
 * @param {*} target
 * @returns
 */
function isObject(target) {
  return (
    target !== null &&
    (typeof target === 'object' || typeof target === 'function')
  );
}

/**
 * 通过 toString 方法获取类型
 * @param {*} target
 * @returns
 */
function getType(target) {
  return Object.prototype.toString.call(target);
}

/**
 * 可遍历的对象,获取初始值
 * @param {*} target
 * @returns
 */
function getInitialValue(target) {
  const Constructor = target.constructor;
  return new Constructor();
}

/**
 * 拷贝除基础类型、可遍历对象外的其他类型
 * @param {*} target
 * @param {*} type
 */
function cloneOtherType(target, type) {
  const Constructor = target.constructor;
  switch (type) {
    case '[object Number]': // new Number()
    case '[object String]': // new String()
    case '[object Boolean]': // new Boolean()
    case '[object Error]': // new Error()
    case '[object Date]': // new Date()
    case '[object Regex]': // new Regex()
      return new Constructor(target);

    // Symbol
    case '[object Symbol]': // new Symbol()
      // 拷贝 Symbol 的包装对象的时候,首先要获取到 Symbol 本身的值,然后再用 Object() 包裹后返回
      return Object(Symbol.prototype.valueOf.apply(target));
    // 函数
    case '[object Function]':
      // 因为函数本身在哪里定义并不重要,重要的是函数在哪里调用,所以函数类型可以直接返回函数本身
      // return eval(target.toString());
      return new Function('return ' + target.toString())();
    default:
      return null;
  }
}

/**
 * 深度拷贝
 * @param {*} target 拷贝对象
 * @param {*} map 记录循环引用对象
 * @returns
 */
function cloneDeep(target, map = new WeakMap()) {
  // 克隆原始类型
  if (!isObject(target)) {
    return target;
  }

  const type = getType(target);
  let cloneTarget;
  // 需要遍历的引用对象的初始值
  if (
    [
      '[object Object]',
      '[object Array]',
      '[object Map]',
      '[object Set]',
    ].includes(type)
  ) {
    cloneTarget = getInitialValue(target);
  } else {
    // 不可遍历的其他对象
    return cloneOtherType(target, type);
  }

  // 防止循环引用
  if (map.get(target)) {
    return map.get(target, target);
  }
  map.set(target, cloneTarget);

  // 拷贝对象
  if (type === '[object Object]') {
    const keys = Object.keys(target);
    keys.forEach((key) => {
      cloneTarget[key] = cloneDeep(target[key], map);
    });
  }
  // 拷贝数组
  else if (type === '[object Array]') {
    target.forEach((key) => {
      cloneTarget[key] = cloneDeep(target[key], map);
    });
  }
  // 拷贝 set
  else if (type === '[object Map]') {
    // target.forEach((value, key) => {
    //   cloneTarget.set(key, cloneDeep(value, map));
    // });
    for (let [key, value] of target) {
      cloneTarget.set(key, cloneDeep(value, map));
    }
  }
  // 拷贝 map
  else if (type === '[object Set]') {
    for (let item of target) {
      cloneTarget.add(cloneDeep(item, map));
    }
  }

  return cloneTarget;
}

// test
const map = new Map();
map.set('key', 'value');
map.set('Hello', 'Word');

const set = new Set();
set.add('Set1111');
set.add('Set2222');

const obj = {
  // 基础数据类型
  // ==========
  number: 1,
  string: 'string',
  boolean: true,
  undefined: undefined,
  null: null,
  bigint: BigInt(1n),
  // Object
  // ==============
  map,
  set,
  bool: new Boolean(true),
  num: new Number(2),
  str: new String(2),
  symbol: Object(Symbol(1)),
  date: new Date(),
  error: new Error(),
  reg: /\d+/,
  func1: () => {
    console.log('code秘密花园');
  },
  func2: function (a, b) {
    return a + b;
  },
};

const result = cloneDeep(obj);

console.log(target);
console.log(result);