Skip to content

Latest commit

 

History

History
183 lines (158 loc) · 4.81 KB

File metadata and controls

183 lines (158 loc) · 4.81 KB

JavaScript 实现深拷贝函数

1.基础递归版

当遇到一个无限层级的对象拷贝时,我们的第一反应应该就是递归

下面,我们仅考虑对象基本类型来做一个深拷贝:

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

function deepClone(obj) {
  if (getType(obj) === '[object Object]') {
    const newObj = {};
    for (const key in obj) {
      // 过滤原型上的属性
      if (obj.hasOwnProperty(key)) {
        newObj[key] = deepClone(obj[key]);
      }
    }
    return newObj;
  } else {
    return obj;
  }
}

2.兼容数组

在确定了深拷贝的基本逻辑后,接下来,我们对数组进行兼容

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

function deepClone(obj) {
  if (getType(obj) === '[object Object]') {
    const newObj = Array.isArray(obj) ? [] : {}; // 这里对类型进行判断,返回空数组或空对象
    for (const key in obj) {
      // 过滤原型上的属性
      if (obj.hasOwnProperty(key)) {
        newObj[key] = deepClone(obj[key]);
      }
    }
    return newObj;
  } else {
    return obj;
  }
}

3.兼容正则

正则表达式可以通过属性sourceflags获取其正则的内容和参数,如下

const reg = /(?<=\[)(.*)(?=\])/g;
console.log(reg.source); // 输出(?<=\[)(.*)(?=\])
console.log(reg.flags); // 输出g

获取到正则的主体和参数后,我们就可以来构造一个正则表达式了

代码如下:

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

function deepClone(item) {
  if (item instanceof Object) {
    const type = getType(item);
    switch (type) {
      case '[object RegExp]':
        return new RegExp(item.source, item.flags); // 取出正则表达式中的内容和参数,重新赋值
      case '[object Object]':
      case '[object Array]':
        const newItem = Array.isArray(item) ? [] : {};
        for (const key in item) {
          // 过滤原型上的属性
          if (item.hasOwnProperty(key)) {
            newItem[key] = deepClone(item[key]);
          }
        }
        return newItem;
      // 如函数/日期等对象,直接返回
      default:
        return item;
    }
  } else {
    return item;
  }
}

4. 兼容函数

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

function deepClone(item) {
  if (item instanceof Object) {
    const type = getType(item);
    switch (type) {
      case '[object RegExp]':
        return new RegExp(item.source, item.flags); // 取出正则表达式中的内容和参数,重新赋值
      case '[object Function]':
        return new Function('return ' + item.toString())(); // 使用new Function()实例化一个新的函数
      case '[object Object]':
      case '[object Array]':
        const newItem = Array.isArray(item) ? [] : {};
        for (const key in item) {
          // 过滤原型上的属性
          if (item.hasOwnProperty(key)) {
            newItem[key] = deepClone(item[key]);
          }
        }
        return newItem;
      // 如函数/日期等对象,直接返回
      default:
        return item;
    }
  } else {
    return item;
  }
}

5.处理循环引用问题

由于我们是使用递归实现的深拷贝,当存在一个属性指向自身的时候,会导致无限递归,栈溢出的情况。如下示例:

const obj = {
  name: 'kerwin',
};

obj.temp = obj; // obj的某个属性指向了本身
const newObj = deepClone(obj); // 报错:栈溢出

为解决上述的情况,我们需要额外添加一种递归的终止条件,即:当某对象已经被拷贝过,则不再进行递归。

通过一个哈希表来存储已拷贝过的对象,当递归过程中发现哈希表中已存在该指,则直接取出,不再递归

function deepClone(item, cache = new Map()) {
  // 判断哈希表中是否存在当前对象的索引
  if (cache.has(item)) {
    return cache.get(item);
  }
  if (item instanceof Object) {
    const type = getType(item);
    switch (type) {
      case '[object RegExp]':
        return new RegExp(item.source, item.flags); // 取出正则表达式中的内容和参数,重新赋值
      case '[object Function]':
        return new Function('return ' + item.toString())();
      case '[object Object]':
      case '[object Array]':
        const newItem = Array.isArray(item) ? [] : {};
        cache.set(item, newItem); // 将当前对象存储到哈希表中
        for (const key in item) {
          // 过滤原型上的属性
          if (item.hasOwnProperty(key)) {
            newItem[key] = deepClone(item[key], cache);
          }
        }
        return newItem;
      // 如函数/日期等对象,直接返回
      default:
        return item;
    }
  } else {
    return item;
  }
}