Skip to content

JS 之浅拷贝与深拷贝 #48

Open
@myLightLin

Description

@myLightLin

日常业务开发中,经常会遇到拷贝数据的情况。在 JS 基本数据类型与引用类型 一文里介绍了 JS 的几种数据类型,对于基本数据类型,直接拷贝栈中存放的值即可;而对于引用类型,栈中存放的只是一个指针,这个指针指向堆内存中真正存放的数据。

对于引用类型的拷贝,分为浅拷贝和深拷贝。所谓浅拷贝,就是指复制栈中的指针,复制后,两个指针指向的是同一块内存地址,因此修改其中一方数据会影响到另外一方;而深拷贝是在内存中开辟一块新区域,它复制的是完整数据,因此两份数据是完全独立,互不影响的。

浅拷贝对象的几种方式

第一种,通过 Object.assign

const obj = {
    name: '张三',
    friends: ['A', 'B', 'C']
}
const copy = Object.assign(obj)
obj.name = '李四'
obj.friends.push('D')

console.log(copy.name)  // 李四
console.log(copy.friends)  // ['A', 'B', 'C', 'D']

第二种,通过 ES6 的扩展运算符 ...

const obj = {
    name: '张三',
    friends: ['A', 'B', 'C']
}
const copy = {...obj}
obj.name = '李四'
obj.friends.push('D')

console.log(copy.name)  // 注意,这里依旧打印 '张三',对基本类型复制的是独立的值
console.log(copy.friends)  // ['A', 'B', 'C', 'D']

深拷贝的方式

第一种(推荐),使用 lodashlicia 等库提供的方法。

// lodash
_.cloneDeep(obj)

// licia
cloneDeep(obj)

第二种,使用 JSON.parse(JSON.stringify())

const obj = {
    name: '张三',
    friends: ['A', 'B', 'C']
}
const copy = JSON.parse(JSON.stringify(obj))
console.log(copy)

第三种,自己实现一个,处理对象、数组、set、map 以及循环引用问题:

const mapTag = '[object Map]'
const setTag = '[object Set]'
const arrayTag = '[object Array]'
const objectTag = '[object Object]'

const deepTag = [mapTag, setTag, arrayTag, objectTag]

function isObject(target) {
  const type = typeof target
  return target !== null && (type === 'object' || type === 'function')
}

function getType(target) {
  return Object.prototype.toString.call(target)
}

function getInstance(target) {
  const Ctor = target.constructor
  return new Ctor()
}

function cloneDeep(target, map = new WeakMap()) {
  // 原始类型直接返回
  if (!isObject(target)) {
    return target
  }

  const type = getType(target)
  let cloneTarget
  if (deepTag.includes(type)) {
    cloneTarget = getInstance(target)
  }

  // 处理循环引用
  if (map.get(target)) {
    return map.get(target)
  }
  map.set(target, cloneTarget)

  // 处理 Set
  if (type === setTag) {
    target.forEach(val => {
      cloneTarget.add(cloneDeep(val, map))
    })
    return cloneTarget
  }

  // 处理 Map
  if (type === mapTag) {
    target.forEach((val, key) => {
      cloneTarget.set(key, cloneDeep(val, map))
    })
    return cloneTarget
  }

  // 处理 Array 和 Object
  for (const key in target) {
    cloneTarget[key] = cloneDeep(target[key], map)
  }

  return cloneTarget
}

export default cloneDeep

总结

  • 浅拷贝对基本数据类型拷贝的是值,对引用类型拷贝的是指针
  • 深拷贝是在堆内存中新开辟一块区域,拷贝出来的数据完全独立,互不影响

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions