JS深拷贝

83 阅读1分钟

工具方法

const iterableObject = Symbol('iteratorObject');

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

function handleObject(data) {
  const type = getType(data);
  // 可迭代的数组对象
  if (type === '[object Object]' || type === '[object Array]') {
    return iterableObject;
  } else {
    if ((typeof data === 'object' || typeof data === 'function') && data !== null) {
      // function set map regexp date等...
      return new data.constructor(data);
    } else {
      // 基础数据类型
      return data;
    }
  }
}

递归

function deepCopy(data, hash = new WeakMap()) {
  const handledData = handleObject(data);
  if (handledData !== iterableObject) {
    return handledData;
  }
  // 如果hash中包含就直接返回,避免重复引用无限递归
  if (hash.has(data)) {
    return hash.get(data)
  }

  // 剩下数组和对象
  const target = Array.isArray(data) ? [] : {};

  // 保存对象引用
  hash.set(data, target);

  // 遍历数据,执行递归
  Reflect.ownKeys(data).forEach(key => {
    target[key] = deepCopy(data[key], hash)
  })

  return target
}

迭代

function deepCopy(data) {
  const handledData = handleObject(data);
  if (handledData !== iterableObject) return handledData;
  // 走到这里最外层只剩数组和对象
  const target = Array.isArray(data) ? [] : {};
  const stack = [{
    data,
    parent: target
  }];
  // 使用hash存储数据要搞清楚存取key是什么,存取value是什么。存取的key必须一致,比如这里存取的key都使用源对象,存取的value都是目标对象
  const hash = new WeakMap();
  hash.set(data, target);

  while (stack.length) {
    // 取出栈顶元素
    const node = stack.pop();
    const {data, parent} = node;

    Reflect.ownKeys(data).forEach(key => {
      const value = data[key];
      const handledData = handleObject(value);
      if (handledData === iterableObject) {
        // 如果已经保存这个对象,就直接返回,避免循环引用进入死循环
        if (hash.has(value)) {
          parent[key] = hash.get(value);
        } else {
          // 为要拷贝的数据申请一块内存,并把这个内存地址绑定为下一次要迭代的parent
          parent[key] = Array.isArray(value) ? [] : {};
          // 保存这个目标对象,避免对这个数据有多个引用,内存地址却不一致
          hash.set(value, parent[key]);
          // 把对象入栈,继续下一轮循环
          stack.push({
            data: value,
            parent: parent[key]
          })
        }

      } else {
        parent[key] = handledData;
      }
    })
  }
  return target;
}