ES6 对象的拷贝

313 阅读4分钟

这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战

JavaScript 中,怎么拷贝对象呢?ES5 中的做法是把对象遍历一下,把数据逐项拷贝到目标对象中去;ES6 则提供了一个新的 API,实现对象数据的拷贝:

const target = {} // 目标对象(就是我要把数据拷贝到这个对象上来)
const source = { b: 4, c: 5 } // 源对象(就是要把数据拷贝出来的地方)
Object.assign(target, source) // 拷贝 source 对象的数据到 target 对象上来
console.log(target) // {b: 4, c: 5}
console.log(source) // {b: 4, c: 5}

但是,这个 API 是有“缺陷”的,比如目标对象和源对象的数据结构如下时,拷贝的结果就会丢失掉“h: 10”:

const target = {
  a: {
    b: {
      c: {
        d: 9
      }
    },
    e: 5,
    f: 6,
    h: 10
  },
  i: 3
};
const source = {
  a: {
    b: {
      c: {
        d: 1
      }
    },
    e: 2,
    f: 3
  }
};
Object.assign(target, source);
console.log(target);
/* 运行结果:
{
  a: {
    b: {
      c: {
        d: 1
      }
    },
    e: 2,
    f: 3
  },
  i: 3
}
 */

这样的结果是不合理的,拷贝可以修改原来的数据,但不应该删除原有的东西(原来的“h: 10”没有了) 实际上,Object.assign() 实现的是浅复制(对于不是引用类型的值,会进行数据的替换, 对于引用类型的值,则不再遍历,只是将引用的对象的地址进行了替换),而不是深复制, 所以才会出现上面的问题(a 是个对象,属于引用类型的值,所以在拷贝时拷贝的是地址值, 也就是用源对象中 a 的地址值替换掉了目标对象中 a 的地址值,a 里面的数据也就完全被替换掉了) 所以呢,使用 Object.assign(target, source) 时,sourcetarget 的拷贝过程中, 可能会出现数据丢失的情况,就是它不能实现深拷贝,只能实现浅拷贝。 如果在使用 Object.assign() 时想要实现深拷贝,则还需要进行递归。

如果目标对象传入的是 undefinednull 将会怎么样呢?

const tar1 = undefined
const tar2 = null
const sou = {
  a: 2,
  b: 3,
  c: 4
}
Object.assign(tar1, sou) // Uncaught TypeError: Cannot convert undefined or null to object
Object.assign(tar2, sou) // Uncaught TypeError: Cannot convert undefined or null to object

可见,目标对象传入的是 undefinednull 时,会报错,“不能将 undefinednull 转换为对象”。

如果源对象传入的是 undefinednull 将会怎么样呢?

let tar = {
  a: {
    b: {
      c: {
        d: 9
      }
    },
    e: 5,
    f: 6,
    h: 10
  },
  i: 3
}
const sou1 = undefined
const sou2 = null
Object.assign(tar, sou1)
console.log(tar)
/* 运行结果:
{
  a: {
    b: {
      c: {
        d: 9
      }
    },
    e: 5,
    f: 6,
    h: 10
  },
  i: 3
}
*/
tar = {
  a: {
    b: {
      c: {
        d: 9
      }
    },
    e: 5,
    f: 6,
    h: 10
  },
  i: 3
}
Object.assign(tar, sou2)
console.log(tar)
/* 运行结果:
{
  a: {
    b: {
      c: {
        d: 9
      }
    },
    e: 5,
    f: 6,
    h: 10
  },
  i: 3
}
*/

可见,源对象传入的是 undefinednull 时,拷贝后目标对象还是其原来的值。

如果目标对象个嵌套的对象,子对象的属性会被覆盖吗?

let tg = {
  a: {
    b: 2,
    d: 4
  }
}
const sr1 = {
  e: 7
}
Object.assign(tg, sr1)
console.log(tg)
/* 运行结果:
{
  a: {
    b: 2,
    d: 4
  },
  e: 7
}
*/
tg = {
  a: {
    b: 2,
    d: 4
  }
}
const sr2 = {
  a: {
    b: 2
  }
}
Object.assign(tg, sr2)
console.log(tg)
/* 运行结果:
{
  a: {
    b: 2
  }
}
*/

可见,目标对象是个嵌套的对象时,如果源对象中没有目标对象的子对象名,源对象的数据会拷贝到子对象的后面,子对象的属性不会被覆盖;如果源对象中存在目标对象的子对象名时,子对象的属性会被覆盖。

如果目标对象中存在嵌套的对象,子对象的属性会被覆盖吗?

let tg = {
  a: {
    b: 2,
    d: 4
  },
  c: 3
} // 目标对象中嵌套了子对象 a
const sr1 = {
  e: 7
}
Object.assign(tg, sr1)
console.log(tg)
/* 运行结果:
{
  a: {
    b: 2,
    d: 4
  },
  c: 3,
  e: 7
}
*/
tg = {
  a: {
    b: 2,
    d: 4
  },
  c: 3
} // 目标对象中嵌套了子对象 a
const sr2 = {
  a: {
    b: 5
  },
  c: 6,
  e: 7
}
Object.assign(tg, sr2)
console.log(tg)
/* 运行结果:
{
  a: {
    b: 5
  },
  c: 6,
  e: 7
}
*/

可见,目标对象中存在嵌套的对象时,如果源对象中没有目标对象的子对象名,源对象的数据会添加到目标对象中,子对象的属性不会被覆盖;如果源对象中存在目标对象的子对象名时,子对象的属性会被覆盖。