JS 深浅拷贝

目录

深浅拷贝

浅拷贝

深拷贝

示例1

总结:

浅拷贝

深拷贝

深浅拷贝

浅拷贝

拷贝的是对象的指针,修改内容互相影响

深拷贝

整个对象拷贝到另一块内存空间中,修改内容不互相影响

示例1

如下例子:对对象直接复制后,导致原对象值发生改变
let a = {
  age: 1
}
let b = a
a.age = 2
console.log(b.age) // 2

解决办法一:
let a = {
  age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1

解决办法二:
let a = {
  age: 1
}
let b = { ...a }
a.age = 2
console.log(b.age) // 1

解决办法三:
let a = ['ant', 'bison', 'camel', 'duck', 'elephant']
let b = a.slice(1, 5)
console.log(b) // ["bison", "camel", "duck", "elephant"]

浅拷贝只解决了第一层的问题,但是如果遇到嵌套对象,就不行了,就得用深拷贝。

let a = {
  age: 1,
  jobs: {
    first: 'FE'
  }
}
let b = { ...a }
a.jobs.first = 'native'
console.log(b.jobs.first) // native

解决办法一:

let a = {
  age: 1,
  jobs: {
    first: 'FE'
  }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE

但是JSON.parse(JSON.stringify(object))方法也是有局限性的:

  1. 会忽略undefined和symbol
  2. 不能序列化函数
  3. 不能解决循环引用的对象

解决办法二:自己实现深拷贝函数(考虑了对象、数组、Symbol类型以及多层嵌套)

function deepClone(obj) {
  function isObject(o) {
    return (typeof o === 'object' || typeof o === 'function') && o !== null
  }

  if (!isObject(obj)) {
    throw new Error('非对象')
  }

  let isArray = Array.isArray(obj)
  let newObj = isArray ? [] : {}

  Reflect.ownKeys(obj).forEach(key => {
    newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
  })

  return newObj
}


const e = Symbol("e")
const f = Symbol.for("f")
let obj = {
  a: [1, 2, 3],
  b: {
    c: 2,
    d: 3
  }
}
obj[e] = 'localSymbol'
obj[f] = 'globalSymbol'


let newObj = deepClone(obj)
newObj.b.c = 1

console.log(newObj)  // { a: [ 1, 2, 3 ], b: { c: 2, d: 3 }, [Symbol(e)]: 'localSymbol', [Symbol(f)]: 'globalSymbol' }
console.log(newObj[e] === obj[e]) // true
console.log(obj.b.c) // 2

上述函数的问题是没有考虑循环引用以及来自原型链上的属性的拷贝。

let obj = {
  a: [1, 2, 3],
  b: {
    c: 2,
    d: 3
  }
}
obj.e = obj

let newObj = deepClone(obj)
console.log(newObj.e) // 2

>输出
RangeError: Maximum call stack size exceeded

let childObj = Object.create(obj)
let newObj = deepClone(childObj)

console.log('原对象:')
for(let key in childObj){
  console.log(childObj[key])
}
console.log('新对象:')
for(let key in newObj){
  console.log(newObj[key])
}

>输出
原对象:
[ 1, 2, 3 ]
{ c: 2, d: 3 }
新对象:

解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系。
当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。

这里使用Reflect.ownKeys() 获取所有的键值,同时包括 Symbol

  • for…in 获取当前对象及其原型链上的所有可枚举属性
  • Object.keys 获取当前对象上的所有可枚举属性
  • Object.getOwnPropertyNames 获取当前对象上的所有可枚举和不可枚举属性
  • Object.getOwnPropertySymbols 获取当前对象上所有Symbol属性
  • Reflect.ownKeys 获取当前对象上所有可枚举、不可枚举属性以及Symbol属性
  • Object.prototypeOf 获取对象原型链上一级的对象
  • Reflect.prototypeOf 获取对象原型链上一级的对象

所有通过Object和Reflect方法获取对象的属性,都无法访问到对象原型链上的属性

  • Object.keys 是获取到对象属性的所有方法中范围最小的一种方法
  • Reflect.ownKeys 是获取到对象属性的所有方法中范围最大的一种方法
  • 此外 Reflect.ownKeys = Object.getOwnPropertyNames + Object.getOwnPropertySymbols
function deepClone(obj, wm = new WeakMap()) {
  function isObject(o) {
    return (typeof o === 'object' || typeof o === 'function') && o !== null
  }

  if (!isObject(obj)) {
    throw new Error('非对象')
  }

  if (wm.has(obj)) return wm.get(obj); // 新增代码,查哈希表

  let isArray = Array.isArray(obj)
  let newObj = isArray ? [] : {}
  wm.set(obj, newObj); // 新增代码,哈希表设值

  Object.getOwnPropertySymbols(obj).forEach(symKey => {
    newObj[symKey] = isObject(obj[symKey]) ? deepClone(obj[symKey], wm) : obj[symKey]
  })

  //使用for in替换Reflect.ownKeys
  for( let key in obj){
    newObj[key] = isObject(obj[key]) ? deepClone(obj[key],wm) : obj[key]
  }

  return newObj
}

测试一下:

const e = Symbol('e')
const f = Symbol.for('f')
const g = Symbol.for('g')
let obj = {
  a: [1, 2, 3],
  b: {
    c: 2,
    d: 3,
  },
}
obj[e] = 'localSymbol'
obj[f] = 'globalSymbol'

let childObj = Object.create(obj)
childObj[g] = 'globalSymbol_'

let newObj = deepClone(childObj)


console.log('原对象:')
for (let key in childObj) {
  console.log(childObj[key])
}
while (childObj) { // 循环
  Object.getOwnPropertySymbols(childObj).forEach(symKey => {
    console.log(childObj[symKey])
  })
  childObj = Object.getPrototypeOf(Object(childObj))
}

console.log('新对象:')
for (let key in newObj) {
  console.log(newObj[key])
}
while (newObj) { // 循环
  Object.getOwnPropertySymbols(newObj).forEach(symKey => {
    console.log(newObj[symKey])
  })
  newObj = Object.getPrototypeOf(Object(newObj))
}


>输出
原对象:
[ 1, 2, 3 ]
{ c: 2, d: 3 }
globalSymbol_
localSymbol
globalSymbol
新对象:
[ 1, 2, 3 ]
{ c: 2, d: 3 }
globalSymbol_

此时,上述函数可以深拷贝当前对象或数组的所有可枚举属性、Symbol类型键,以及该对象原型链上的所有可枚举属性。但仍然有一个问题,就是不能拷贝原型链上的Symbol类型键。
需要使用Object.getPrototypeOf来循环获取上一级对象的Symbol类型键属性。

function deepClone(obj, wm = new WeakMap()) {
  function isObject(o) {
    return (typeof o === 'object' || typeof o === 'function') && o !== null
  }

  if (!isObject(obj)) {
    throw new Error('非对象')
  }

  if (wm.has(obj)) return wm.get(obj) // 新增代码,查哈希表

  let isArray = Array.isArray(obj)
  let newObj = isArray ? [] : {}
  wm.set(obj, newObj) // 新增代码,哈希表设值

  while (obj) {
    Reflect.ownKeys(obj).forEach(key => {
      if(obj.propertyIsEnumerable(key)){
        newObj[key] = isObject(obj[key]) ? deepClone(obj[key],wm) : obj[key]
      }
    })
    obj = Object.getPrototypeOf(Object(obj))
  }

  return newObj
}

至此,该函数可以深拷贝当前对象和它原型链上的所有可枚举属性及Symbol属性,结果大家可以去验证。

总结

浅拷贝

  • Object.assign()
  • 扩展运算符 …
  • Array.prototype.slice()/Array.prototype.concat()

深拷贝

  • JSON.parse(JSON.stringify())
  • lodash的深拷贝函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值