• 如果只是对一个简单对象进行深拷贝,那么使用该方法是很方便的;
  • 但根据上面的打印结果可以发现,原 obj 的方法属性并没有被拷贝到 newObj 中;
  • JSON序列化只能对普通对象进行深拷贝,如果对象中包含 函数、undefined、Symbol 等类型的值是无能为力的,会直接将其忽略掉;
  • 2.自定义深拷贝函数

    既然上面的方法不能满足我们的需求,那么就自己来一步步实现一个深拷贝函数吧。

    2.1.基本功能实现

  • 实现深拷贝基本功能,暂时先不对特殊类型进行处理;
  • 定义一个辅助函数 isObject ,用于判断传入数据是否是对象类型;
  • function isObject(value) {
      const valueType = typeof value
      // 值不能为null,并且为对象或者函数类型
      return (value !== null) && (valueType === 'object' || valueType === 'function')
    
    function deepClone(originValue) {
      // 判断传入的是否是对象类型,如果不是,说明是普通类型的值,直接返回即可
      if (!isObject(originValue)) {
        return originValue
      const newObj = {} // 定义一个空对象
      // 循环遍历对象,取出key和值存放到空对象中
      // 注意:for...in遍历对象会将其继承的属性也遍历出来,所以需要加hasOwnProperty进行判断是否是自身的属性
      for (const key in originValue) {
        if (originValue.hasOwnProperty(key)) {
          // 递归调用deepClone,如果对象属性值中还包含对象,就会再次进行拷贝处理
          newObj[key] = deepClone(originValue[key])
      // 深拷贝完成,将得到新对象返回
      return newObj
    

    简单测试一下:

    const obj = {
      name: 'curry',
      age: 30,
      friends: {
        name: 'klay',
        age: 11
    const newObj = deepClone(obj)
    console.log(newObj)
    console.log(newObj.friends === obj.friends)
    

    打印结果:

    2.2.其他类型处理

  • 对其它数据类型进行处理,如数组、函数、Symbol、Set、Map等;
  • 对函数类型的判断,直接返回该函数即可,因为函数本身就是可以复用的;
  • Symbol不仅可以作为value,还可以作为key,需要对key为Symbol类型的情况进行处理;
  • function deepClone(originValue) {
      // 1.判断传入的是否是一个函数类型
      if (typeof originValue === 'function') {
        // 将函数直接返回即可
        return originValue
      // 2.判断传入的是否是一个Map类型
      if (originValue instanceof Map) {
        return new Map([...originValue])
      // 3.判断传入的是否是一个Set类型
      if (originValue instanceof Set) {
        return new Set([...originValue])
      // 4.判断传入的值是否是一个Symbol类型
      if (typeof originValue === 'symbol') {
        // 返回一个新的Symbol,并且将其描述传递过去
        return Symbol(originValue.description)
      // 5.判断传入的值是否是一个undefined
      if (typeof originValue === 'undefined') {
        return undefined
      // 6.判断传入的是否是对象类型,如果不是,说明是普通类型的值,直接返回即可
      if (!isObject(originValue)) {
        return originValue
      // 7.定义一个变量,如果传入的是数组就定义为一个数组
      const newValue = Array.isArray(originValue) ? [] : {}
      // 8.循环遍历,如果是对象,就取出key和值存放到空对象中,如果是数组,就去除下标和元素放到空数组中
      // 注意:for...in遍历对象会将其继承的属性也遍历出来,所以需要加hasOwnProperty进行判断是否是自身的属性
      for (const key in originValue) {
        if (originValue.hasOwnProperty(key)) {
          // 递归调用deepClone,如果对象属性值中还包含对象,就会再次进行拷贝处理
          newValue[key] = deepClone(originValue[key])
      // 9.对key为Symbol类型的情况进行处理
      // 拿到所有为Symbol类型的key
      const symbolKeys = Object.getOwnPropertySymbols(originValue)
      // for...of遍历取出所有的key,存放到新对象中
      for (const sKey of symbolKeys) {
        newValue[sKey] = deepClone(originValue[sKey])
      // 10.深拷贝完成,将得到新对象返回
      return newValue
    

    简单测试一下:

    const s1 = Symbol('aaa')
    const s2 = Symbol('bbb')
    const obj = {
      name: 'curry',
      age: undefined,
      friends: {
        name: 'klay',
        age: 11
      hobbies: ['篮球', '足球', '高尔夫'],
      map: new Map([[1, 'aaa'], [2, 'bbb'], [3, 'ccc']]),
      set: new Set([1, 2, 3]),
      s: s1,
      [s2]: 'abc'
    const newObj = deepClone(obj)
    console.log(newObj)
    

    打印结果:

    2.3.循环引用处理

    我们自定义深拷贝的函数是通过递归来实现的,如果对象中有一个属性值指向了自己,那么在进行深拷贝时会陷入无限循环,这种情况也就是循环引用。

    如果没有处理循环引用,那么就会不断递归,最终报错栈溢出:

  • 循环引用的处理,只需要拿到新创建的对象返回即可,所以必须将这个新对象保存下来,在遇到循环引用属性时,直接就可以拿到;
  • Map和WeakMap都可以实现对对象进行存储,这里使用WeakMap进行存储,原因是WeakMap对对象的引用是弱引用;
  • 只需要将原对象作为WeakMap中的key,其值对应存放我们新创建出来的对象即可,下一次递归时进行判断WeakMap中是否存有该对象,如果有就取出返回;
  • function deepClone(originValue, wMap = new WeakMap()) {
      // 1.判断传入的是否是一个函数类型
      if (typeof originValue === 'function') {
        // 将函数直接返回即可
        return originValue
      // 2.判断传入的是否是一个Map类型
      if (originValue instanceof Map) {
        return new Map([...originValue])
      // 3.判断传入的是否是一个Set类型
      if (originValue instanceof Set) {
        return new Set([...originValue])
      // 4.判断传入的值是否是一个Symbol类型
      if (typeof originValue === 'symbol') {
        // 返回一个新的Symbol,并且将其描述传递过去
        return Symbol(originValue.description)
      // 5.判断传入的值是否是一个undefined
      if (typeof originValue === 'undefined') {
        return undefined
      // 6.判断传入的是否是对象类型,如果不是,说明是普通类型的值,直接返回即可
      if (!isObject(originValue)) {
        return originValue
      // 循环引用处理:判断wMap中是否存在原对象,如果存在就取出原对象对应的新对象返回
      if (wMap.has(originValue)) {
        return wMap.get(originValue)
      // 7.定义一个变量,如果传入的是数组就定义为一个数组
      const newValue = Array.isArray(originValue) ? [] : {}
      // 循环引用处理:将原对象作为key,新对象作为value,存入wMap中
      wMap.set(originValue, newValue)
      // 8.循环遍历,如果是对象,就取出key和值存放到空对象中,如果是数组,就去除下标和元素放到空数组中
      // 注意:for...in遍历对象会将其继承的属性也遍历出来,所以需要加hasOwnProperty进行判断是否是自身的属性
      for (const key in originValue) {
        if (originValue.hasOwnProperty(key)) {
          // 递归调用deepClone,如果对象属性值中还包含对象,就会再次进行拷贝处理
          newValue[key] = deepClone(originValue[key], wMap)
      // 9.对key为Symbol类型的情况进行处理
      // 拿到所有为Symbol类型的key
      const symbolKeys = Object.getOwnPropertySymbols(originValue)
      // for...of遍历取出所有的key,存放到新对象中
      for (const sKey of symbolKeys) {
        newValue[sKey] = deepClone(originValue[sKey], wMap)
      // 10.深拷贝完成,将得到新对象返回
      return newValue
    

    简单测试一下:

    const s1 = Symbol('aaa')
    const s2 = Symbol('bbb')
    const obj = {
      name: 'curry',
      age: undefined,
      friends: {
        name: 'klay',
        age: 11
      hobbies: ['篮球', '足球', '高尔夫'],
      map: new Map([[1, 'aaa'], [2, 'bbb'], [3, 'ccc']]),
      set: new Set([1, 2, 3]),
      s: s1,
      [s2]: 'abc'
    // 循环引用
    obj.self = obj
    const newObj = deepClone(obj)
    console.log(newObj)
    console.log(newObj.self.self.self.self)
    

    打印结果: