JS深浅拷贝

简介: 本文介绍了JavaScript中实现数据拷贝的四种方法:`Object.assign()`, 扩展运算符(`...`), `JSON.parse(JSON.stringify())` 和递归深拷贝。`Object.assign()`及扩展运算符对基本数据类型进行深拷贝,而对引用类型则进行浅拷贝。`JSON.parse(JSON.stringify())`对所有类型的数据都执行深拷贝,但存在一些限制如日期类型被转为字符串等。递归深拷贝则避免了这些问题,并支持循环引用,是一种更安全的选择。

一、Object.assign(target, source)

基本数据类型的拷贝,是深拷贝;引用数据类型的拷贝,是浅拷贝

①对象复制

var source = {
      name: '库里',
      player: {
        num: 30,
        age: 34
      }
    }
var target = {}
target = Object.assign(target, source)
target.name = 'curry' // 基本类型
target.player.num = 3 // 引用类型
console.log('source:', source) // { name: '库里', player: { num: 3, age: 34 } }
console.log('target:', target) // { name: 'curry', player: { num: 3, age: 34 } }

②数组复制

var source = [
      '库里',
      {
        num: 30,
        age: 34
      }
    ]
var target = []
target = Object.assign(target, source)
target[0] = 'curry' // 基本类型
target[1].num = 3 // 引用类型
console.log('source:', source) // [ '库里', { num: 3, age: 34 } ]
console.log('target:', target) // [ 'curry', { num: 3, age: 34 } ]

二、扩展运算符(…)

基本数据类型的拷贝,是深拷贝;引用数据类型的拷贝,是浅拷贝

①对象复制

var source = {
      name: '库里',
      player: {
        num: 30,
        age: 34
      }
    }
var target = { ...source }
target.name = 'curry' // 基本类型
target.player.num = 3 // 引用类型
console.log('source:', source) // { name: '库里', player: { num: 3, age: 34 } }
console.log('target:', target) // { name: 'curry', player: { num: 3, age: 34 } }

②数组复制

var source = [
      '库里',
      {
        num: 30,
        age: 34
      }
    ]
var target = [ ...source ]
target[0] = 'curry' // 基本类型
target[1].num = 3 // 引用类型
console.log('source:', source) // [ '库里', { num: 3, age: 34 } ]
console.log('target:', target) // [ 'curry', { num: 3, age: 34 } ]

三、**var target = JSON.parse(JSON.stringify(source))**

无论是基本数据类型还是引用数据类型,都是深拷贝,但有些问题存在!

JSON.stringify():将 JavaScript值(通常为对象或数组)转换为 JSON 字符串

JSON.parse():将一个 JSON 字符串转换为对象或数组

①对象深复制

var source = {
      name: '库里',
      player: {
        num: 30,
        age: 34
      }
    }
var target = JSON.parse(JSON.stringify(source))
target.name = 'curry' // 基本类型
target.player.num = 3 // 引用类型
console.log('source:', source) // { name: '库里', player: { num: 30, age: 34 } }
console.log('target:', target) // { name: 'curry', player: { num: 3, age: 34 } }

②数组深复制

var source = [
      '库里',
      {
        num: 30,
        age: 34
      }
    ]
var target = JSON.parse(JSON.stringify(source))
target[0] = 'curry' // 基本类型
target[1].num = 3 // 引用类型
console.log('source:', source) // [ '库里', { num: 30, age: 34 } ]
console.log('target:', target) // [ 'curry', { num: 3, age: 34 } ]

四、**var target = JSON.parse(JSON.stringify(source))深拷贝存在的问题:**

  • 对象中有Date类型时,序列化之后会变成字符串类型。
  • 对象中有Error或RegExp类型时,序列化之后会变成空对象{}
  • 对象中有undefined和Function类型数据的时候,序列化之后会直接丢失。
  • 对象中有NaN、Infinity和-Infinity的时候,序列化之后value会变成null。
  • 对象循环引用后,执行序列化会报错。
  • 最后,深拷贝建议使用递归,安全方便。
// JSON.parse(JSON.stringify())深拷贝存在的问题
let source = {
  name: '库里',
  player: {
    num: 30,
    age: 34
  },
  date: new Date(), // 深拷贝后类型会变为string
  reg: /^[1-9]$/, // 或new RegExp('[1-9]'),深拷贝后会变为空对象{}
  err: new Error('err'), // 深拷贝后会变为空对象{}
  undef: undefined, // 深拷贝后会丢失
  func: () => { console.log('func') }, // 深拷贝后会丢失
  nan: NaN, // 深拷贝后会变成null
  infinityMax: Infinity, // 深拷贝后会变成null
  infinityMin: -Infinity // 深拷贝后会变成null
}
// source.child = source // 循环引用后,深拷贝报错:TypeError: Converting circular structure to JSON
const target = JSON.parse(JSON.stringify(source))
console.log('type:', typeof source.date) // object
console.log('type:', typeof target.date) // string
console.log('source:', source)
console.log('target:', target)

五、 递归深拷贝(推荐)

function deepClone (target, wm = new WeakMap()) {
  if (target === null) return target // null 直接返回
  if (target instanceof Date) return new Date(target)
  if (target instanceof RegExp) return new RegExp(target)
  // 可能是对象或者普通的值,如果是函数的不需要深拷贝
  if (typeof target !== 'object') { // 包括: boolean | number | string | undefined | function
    return target
  }
  // 防止循环引用
  if (wm.get(target)) {
    return wm.get(target)
  }
  // 实例的构造函数,就是其类原型的构造函数constructor()方法,类原型的构造函数constructor,直接指向类本身
  // obj.constructor === Object.prototype.constructor === Object
  let cloneTarget = new target.constructor()
  wm.set(target, cloneTarget)
  for (let key in target) {
    cloneTarget[key] = deepClone(target[key], wm)
  }
  return cloneTarget
}
let source = {
  name: '库里',
  player: {
    num: 30,
    age: 34
  },
  date: new Date(),
  reg: /^[1-9]$/,
  err: new Error('err'),
  undef: undefined,
  func: () => { console.log('func') },
  nan: NaN,
  infinityMax: Infinity,
  infinityMin: -Infinity
}
source.self = source // 循环引用,即对象的属性间接或直接的引用了自身的情况
let target = deepClone(source)
console.log('source:', source)
console.log('target:', target)
相关文章
|
3月前
|
JSON JavaScript 数据格式
手写JS实现深拷贝函数
本文介绍了如何实现一个深拷贝函数`deepClone`,该函数可以处理对象和数组的深拷贝,确保拷贝后的对象与原始对象在内存中互不干扰。通过递归处理对象的键值对和数组的元素,实现了深度复制,同时保留了函数类型的值和基础类型的值。
23 3
|
3月前
|
JSON JavaScript 前端开发
js原型继承|26
js原型继承|26
|
4月前
|
JavaScript
js基础数据类型
js基础数据类型
|
JavaScript
JS 构造函数在 new 时做了啥?
JS 构造函数在 new 时做了啥?
74 0
|
7月前
|
存储 JSON JavaScript
JS深浅拷贝
JavaScript中的深浅拷贝
75 3
|
7月前
|
存储 JavaScript 前端开发
js深浅拷贝的区别
js深浅拷贝的区别
40 0
|
7月前
|
存储 设计模式 JavaScript
js使用构造函数的注意点?
js使用构造函数的注意点?
69 0
|
JavaScript
js中数组的深拷贝的方法
js中数组的深拷贝的方法
137 1
|
存储 JavaScript
js -深浅拷贝
针对引用类型而言,浅拷贝指的是复制对象的引用,即直接给引用类型赋值,如果拷贝后的对象发生变化,原对象也会发生变化。而深拷贝是真正地对对象进行拷贝,修改拷贝后的新对象并不会对原对象产生任何影响。
|
JavaScript 前端开发
js深浅贝和浅拷贝区别
js深浅贝和浅拷贝区别