# 浅拷贝与深拷贝

# 浅拷贝

什么是浅拷贝?如果是对象类型,只拷贝第一层,如果对象的属性又是一个对象,那么此时拷贝的就是这个属性的应用。

function shadowCopy(obj) {
  const newObj = {}
  for (let prop in obj) {
    if (obj.hasOwnProperty(prop)) {
      newObj[prop] = obj[prop]
    }
  }
  return newObj
}

const obj1 = {
  name: 'litterStar',
  a: {
    b: '1',
  },
}
const obj2 = shadowCopy(obj1)
obj2.name = 'lucyStar'
obj2.a.b = '2'
console.log(obj1)
// { name: 'litterStar', a: { b: '2' } }
console.log(obj2)
// { name: 'lucyStar', a: { b: '2' } }

可以看到修改 obj2 的 name 属性不会影响 obj1,但是修改 obj2 的 a 属性(是个对象)的 b,就会影响 obj1.a.b

使用下面这些函数得到的都是浅拷贝:

  • Object.assign
  • Array.prototype.slice()Array.prototype.concat()
  • 使用拓展运算符实现的复制

# 深拷贝

什么是深拷贝? 浅拷贝是只拷贝一层,深拷贝会拷贝所有的属性。深拷贝前后两个对象互不影响。

深拷贝的实现:

  • JSON.parse(JSON.stringify())
  • 手写递归函数
  • 函数库 lodash (或者其他库)

JSON.parse(JSON.stringify())有存在以下问题:

  • 无法解决循环引用问题
  • 无法拷贝特殊的对象,比如:RegExp, Date, Set, Map 等在序列化的时候会丢失。
  • 无法拷贝函数
let obj = {
    fun: function(){},
    syb: Symbol('foo'),
    a: undefined,
    b: NaN,
    c: Infinity,
    reg : /^abc$/,
    date: new Date(),
    set: new Set([1, 2, 3, 4, 4]),
    map: new Map([
        ['name', '张三'],
        ['title', 'Author']
      ]),
    text:'aaa',
}
let cloneObj = JSON.parse(JSON.stringify(obj));
console.log(cloneObj);
// 结果如下
{
  reg: {},
  b: null,
  c: null,
  date: '2020-05-05T09:32:52.533Z',
  set: {},
  map: {},
  text: 'aaa'
}
console.log(typeof obj.date); // object
console.log(typeof cloneObj.date); // string
  1. 如果 obj 里有函数,undefined,则序列化的结果会把函数或 undefined 丢失;
  2. 如果 obj 里面有时间对象,则 JSON.stringify 后再 JSON.parse 的结果,时间将只是字符串的形式。而不是时间对象;
  3. 如果 obj 里有 RegExp、 Set, Map 等,则序列化的结果将只得到空对象;
  4. 如果 obj 里有 NaN、Infinity 和-Infinity,则序列化的结果会变成 null;
  5. 循环引用,则直接报错 TypeError: Converting circular structure to JSON
const obj = {
  a: '111',
}
obj.b = obj

let cloneObj = JSON.parse(JSON.stringify(obj))
console.log(cloneObj) // TypeError: Converting circular structure to JSON

写一个深拷贝,需要考虑下面几种情况:

  1. 属性是基本类型
  2. 属性是对象
  3. 属性是数组
  4. 循环引用的情况,比如 obj.prop1 = obj
function deepCopy(originObj, map = new WeakMap()) {
  if (typeof originObj === 'object') {
    // 判断是都否为数组
    const cloneObj = Array.isArray(originObj) ? [] : {}
    // 判断是否为循环引用
    if (map.get(originObj)) {
      return map.get(originObj)
    }
    map.set(originObj, cloneObj)
    for (const prop in originObj) {
      cloneObj[prop] = deepCopy(originObj[prop], map)
    }
    return cloneObj
  } else {
    return originObj
  }
}

const obj1 = {
  a: '111',
}
obj1.obj2 = obj1

const aa = deepCopy(obj1)
console.log(aa)
// { a: '111', obj2: [Circular] }

上面的实现存在一些问题:

  1. 一些特殊类型的对象,比如 Date, 正则,Set,Map 等没有处理
  2. 使用 typeof 来判断是否是对象是有问题的,typeof null 的结果也是 'object'

完善版本

// 是否为引用类型
function isObject(obj) {
  return typeof obj === 'object' || (typeof obj === 'function' && obj !== null)
}

function deepCopy(originObj, map = new WeakMap()) {
  // 判断是否为基本数据类型
  if (isObject(originObj)) {
    // 判断是否为循环引用
    if (map.get(originObj)) {
      return map.get(originObj)
    }

    // 判断是否为几种特殊需要处理的类型
    let type = [Date, RegExp, Set, Map, WeakMap, WeakSet]
    if (type.includes(originObj.constructor)) {
      return new originObj.constructor(originObj)
    }
    // 其他类型
    let allDesc = Object.getOwnPropertyDescriptors(originObj)
    let cloneObj = Object.create(Object.getPrototypeOf(originObj), allDesc)

    // Reflect.ownKeys 可以获取到
    for (const prop of Reflect.ownKeys(originObj)) {
      cloneObj[prop] =
        isObject(originObj[prop]) && typeof originObj[prop] !== 'function'
          ? deepCopy(originObj[prop], map)
          : originObj[prop]
    }
    return cloneObj
  } else {
    return originObj
  }
}

let obj = {
  fun: function () {},
  syb: Symbol('foo'),
  a: undefined,
  b: NaN,
  c: Infinity,
  reg: /^abc$/,
  date: new Date(),
  set: new Set([1, 2, 3, 4, 4]),
  map: new Map([
    ['name', '张三'],
    ['title', 'Author'],
  ]),
  text: 'aaa',
}
let cloneObj = deepCopy(obj)
console.log(cloneObj)

打印的结果如下:

{
  fun: [Function: fun],
  syb: Symbol(foo),
  a: undefined,
  b: NaN,
  c: Infinity,
  reg: /^abc$/,
  date: 2020-05-05T13:57:14.904Z,
  set: Set { 1, 2, 3, 4 },
  map: Map { 'name' => '张三', 'title' => 'Author' },
  text: 'aaa'
}
Last Updated: 3/17/2022, 5:01:11 PM