JavaScript 深拷贝实现思路

312 阅读2分钟

一、克隆基础数据类型

const deepClone = (target) => target;

二、克隆复杂数据类型

2.1、基础版本

const deepClone = (target) => {
  if (target instanceof Object) {
    let dist = {};
    for (let key in target) {
      // 递归调用自己获取到每个值
      dist[key] = deepClone(target[key]);
    }
    return dist;
  } else {
    return target;
  }
};

2.2、数组

const deepClone = (target) => {
  if (target instanceof Object) {
    let dist;
    if (target instanceof Array) {
      dist = [];
    } else {
      dist = {};
    }
    for (let key in target) {
      dist[key] = deepClone(target[key]);
    }
    return dist;
  } else {
    return target;
  }
};

2.3、函数

const deepClone = (target) => {
  if (target instanceof Object) {
    let dist;
    if (target instanceof Array) {
      dist = [];
    } else if (target instanceof Function) {
      dist = () => {
        return target.call(this, ...arguments);
      };
    } else {
      dist = {};
    }
    // 函数的话不会进到这个 for... in... 循环里面
    for (let key in target) {
      dist[key] = deepClone(target[key]);
    }
    return dist;
  } else {
    return target;
  }
}

2.4、正则

const deepClone = (target) => {
  if (target instanceof Object) {
    let dist;
    if (target instanceof Array) {
      dist = [];
    } else if (target instanceof Function) {
      dist = () => {
        return target.call(this, ...arguments);
      };
    } else if (target instanceof RegExp) {
      dist = new RegExp(target.source, target.flags);
    } else {
      dist = {};
    }
    // 正则、函数的话不会进到这个 for... in... 循环里面
    for (let key in target) {
      dist[key] = deepClone(target[key]);
    }
    return dist;
  } else {
    return target;
  }
}

2.5、日期

const deepClone = (target) => {
  if (target instanceof Object) {
    let dist;
    if (target instanceof Array) {
      dist = [];
    } else if (target instanceof Function) {
      dist = () => {
        return target.call(this, ...arguments);
      };
    } else if (target instanceof RegExp) {
      dist = new RegExp(target.source, target.flags);
    } else if (target instanceof Date) {
      dist = new Date(target);
    } else {
      dist = {};
    }
    // 日期、正则、函数的话不会进到这个 for... in... 循环里面
    for (let key in target) {
      dist[key] = deepClone(target[key]);
    }
    return dist;
  } else {
    return target;
  }
}

三、优化

3.1、忽略原型上的属性

我们在遍历对象的属性的时候,使用的是for infor in 会遍历包括原型上的所有可迭代的属性。 比如:

let a = Object.create({name:'hello'});
a.age = 14;

但是,事实上我们不应该去遍历原型上的属性,因为这样会导致对象属性非常深。

const deepClone = (target) => {
  if (target instanceof Object) {
    let dist;
    if (target instanceof Array) {
      dist = [];
    } else if (target instanceof Function) {
      dist = () => {
        return target.call(this, ...arguments);
      };
    } else if (target instanceof RegExp) {
      dist = new RegExp(target.source, target.flags);
    } else if (target instanceof Date) {
      dist = new Date(target);
    } else {
      dist = {};
    }
    // 日期、正则、函数的话不会进到这个 for... in... 循环里面
    for (let key in target) {
      // 过滤掉原型身上的属性
      if (target.hasOwnProperty(key)) {
        dist[key] = deepClone(target[key]);
      }
    }
    return dist;
  } else {
    return target;
  }
}

3.2、环状对象的爆栈问题

用 new Map() 处理一下就好了

const deepClone = (target, cache = new Map()) => {
  if (cache.get(target)) {
    return cache.get(target);
  }
  if (target instanceof Object) {
    let dist;
    if (target instanceof Array) {
      // 拷贝数组
      dist = [];
    } else if (target instanceof Function) {
      // 拷贝函数
      dist = function() {
        return target.call(this, ...arguments);
      };
    } else if (target instanceof RegExp) {
      // 拷贝正则表达式
      dist = new RegExp(target.source, target.flags);
    } else if (target instanceof Date) {
      dist = new Date(target);
    } else {
      // 拷贝普通对象
      dist = {};
    }
    // 将属性和拷贝后的值作为一个map
    cache.set(target, dist);
    for (let key in target) {
      // 过滤掉原型身上的属性
      if (target.hasOwnProperty(key)) {
        dist[key] = deepClone(target[key], cache);
      }
    }
    return dist;
  } else {
    return target;
  }
};

3.3、对象过长导致的爆栈问题

正常情况下不会出现那种奇怪的数据结构,一般可以用数组来解决这样的问题,不过那就是另外一种思路了