Skip to content

深浅拷贝(克隆)

浅拷贝

数组浅拷贝

  • Array.prototype.slice
js
let arr = [1, 2, 3, 4]
console.log(arr.slice()) // [1, 2, 3, 4]
console.log(arr.slice() === arr) // false
  • Array.prototype.concat
js
let arr = [1, 2, 3, 4]
console.log(arr.concat()) // [1, 2, 3, 4]
console.log(arr.concat() === arr) // false

对象浅拷贝

浅拷贝是指,一个新的对象对原始对象的属性值进行精确地拷贝,如果拷贝的是基本数据类型,拷贝的就是基本数据类型的值,如果是引用数据类型,拷贝的就是内存地址。如果其中一个对象的引用内存地址发生改变,另一个对象也会发生变化。

  • 常规实现
js
function isObject(val) {
  return typeof val === 'object' && val !== null
}

function shallowCopy(obj) {
  if (!isObject(obj)) return
  let newObj = obj instanceof Array ? [] : {}
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = obj[key]
    }
  }
  return newObj
}
  • Object.assign
js
let target = { a: 1 }
let object2 = { b: 2 }
let object3 = { c: 3 }
Object.assign(target, object2, object3)
console.log(target)  // { a: 1, b: 2, c: 3 }
  • 扩展运算符
js
let obj1 = { a: 1, b: { c: 1 } }
let obj2 = { ...obj1 }
obj1.a = 2
console.log(obj1) // { a: 2, b: { c: 1 } }
console.log(obj2) // { a: 1, b: { c: 1 } }
obj1.b.c = 2
console.log(obj1) // { a: 2, b: { c: 2 } }
console.log(obj2) // { a: 1, b: { c: 2 } }

深拷贝

深拷贝相对浅拷贝而言,如果遇到属性值为引用类型的时候,它新建一个引用类型并将对应的值复制给它,因此对象获得的一个新的引用类型而不是一个原有类型的引用。深拷贝对于一些对象可以使用 JSON 的两个函数来实现,但是由于 JSON 的对象格式比 js 的对象格式更加严格,所以如果属性值里边出现函数或者 Symbol 类型的值时,会转换失败。

递归

js
// 只考虑普通对象属性,不考虑内置对象和函数
function deepCopy(obj) {
  if (!obj || typeof obj !== "object") return
  let newObj = obj instanceof Array ? [] : {}
  for (let k in obj) {
    if (obj.hasOwnProperty(k) && typeof obj[k] === "object") {
      newObj[k] = deepCopy(obj[k])
    } else {
      newObj[k] = obj[k]
    }
  }
  return newObj
}

JSON.stringify

  • JSON.parse(JSON.stringify(obj)) 是目前比较常用的深拷贝方法之一,它的原理就是利用 JSON.stringify 将 js 对象序列化(JSON 字符串),再使用 JSON.parse 来反序列化(还原) js 对象。
  • 这个方法可以简单粗暴的实现深拷贝,但是还存在问题,拷贝的对象中如果有:函数,undefined,symbol,当使用过 JSON.stringify() 进行处理之后,都会消失。
js
let obj1 = {
  a: 0,
  b: {
    c: 0
  }
}
let obj2 = JSON.parse(JSON.stringify(obj1))
obj1.a = 1
obj1.b.c = 1
console.log(obj1) // { a: 1, b: { c: 1 } }
console.log(obj2) // { a: 0, b: { c: 0 } }

lodash.cloneDeep()

js
import _ from 'lodash'
const obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
const obj2 = _.cloneDeep(obj1)
console.log(obj1.b.f === obj2.b.f) // false

原生语法 structuredClone

  • 全局的 structuredClone() 方法使用结构化克隆算法将给定的值进行深拷贝
  • 该方法还支持把原始值种的可转移对象转移到新对象,而不是把属性引用拷贝过去,可转移对象于原始对象分离并附加到新对象。它们不可以在原始对象中被访问到
js
// Create an object with a value and a circular reference to itself.
const original = { name: "MDN" }
original.itself = original

// Clone it
const clone = structuredClone(original)

console.assert(clone !== original) // the objects are not the same (not same identity)
console.assert(clone.name === "MDN") // they do have the same values
console.assert(clone.itself === clone) // and the circular reference is preserved

MessageChannel

js
function deepCopy(obj) {
  return new Promise(resolve => {
    const { port1, port2 } = new MessageChannel()
    port1.postMessage(obj)
    port2.onmessage = msg => {
      resolve(msg.data)
    }
  })
}