javascript中的对象相关方法四(深拷贝)


前言

在开发中涉及到对象或数组的深拷贝,推荐使用lodash库中的cloneDeep方法,我在github上看这个方法已经好几年没更新了,说明已经很成熟了。哪怕自己封装,封装到最完善也和这个方法一样重复造轮子,这里主要理一下深拷贝函数的封装思路。

lodash.cloneDeep()

使用很简单,就是引库调函数。

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

JSON.parse(JSON.stringify())

很多人喜欢此方法进行深拷贝,但是此方法很不靠谱,强烈不推荐
有很多弊端,首先是对象中有函数方法时,函数方法不会被拷贝;其次属性值为undefined的属性也不会拷贝;还有对象中存在循环引用时会造成内存溢出。

const obj = { 
  age: null,
  name: '',
  address: undefined,
  friends: { a: '1'},
  running: function() {console.log('--')}
}
const newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj); // { age: null, name: '', friends: { a: '1' } }

自己封装深拷贝

首先是最简单的深拷贝封装,需要处理下列数据。

  • 处理基本数据类型
  • 处理对象类型
function isObj(obj) {
  return Object(obj) === obj  // 判断传入的参数是否为引入类型
}
function cloneDeep(obj) {
  if (!isObj(obj)) { // 基本数据类型直接返回
    return obj
  }
  const newObj = {}
  for (const k in obj) {
    newObj[k] = cloneDeep(obj[k])
  }
  return newObj
}

const obj = { 
  age: null,
  name: '',
  address: undefined,
  friends: { a: '1'},
}
console.log(cloneDeep(obj)); // { age: null, name: '', address: undefined, friends: { a: '1' } }

但是对象中的属性值可能也是数组和函数,所以要加上对数组类型和函数的处理。

  • 处理数组类型
  • 处理函数
function isObj(obj) {
  return Object(obj) === obj
}
function cloneDeep(obj) {
  if (typeof obj === 'function') {
    return obj
  }
  if (!isObj(obj)) {
    return obj
  }
  const newObj = Array.isArray(obj) ? [] : {}
  for (const k in obj) {
    newObj[k] = cloneDeep(obj[k])
  }
  return newObj
}

const obj = { 
  age: null,
  address: [1,2,3],
  friends: { a: '1'},
}
console.log(cloneDeep(obj)); // { age: null, address: [ 1, 2, 3 ], friends: { a: '1' } }

javascript中后来新增了symbol基本数据类型,也可以作为对象的属性,要加上对这个的处理。

  • 处理属性是symbol类型
  • 处理属性值是symbol类型
function isObj(obj) {
  return Object(obj) === obj
}
function cloneDeep(obj) {
  if (typeof obj === 'symbol') {
    return Symbol(obj.description)
  }
  if (typeof obj === 'function') {
    return obj
  }
  if (!isObj(obj)) {
    return obj
  }
  const newObj = Array.isArray(obj) ? [] : {}
  for (const k in obj) {
    newObj[k] = cloneDeep(obj[k])
  }
  const objSymbolKeys = Object.getOwnPropertySymbols(obj)
  for (const k of objSymbolKeys) {
    newObj[k] = cloneDeep(obj[k])
  }
  return newObj
}

const s1 = Symbol('a')
const s2 = Symbol('b')
const obj = { 
  age: null,
  address: [1,2,3],
  friends: { a: '1'},
  [s1]: s2
}
console.log(cloneDeep(obj));
// {
  age: null,
  address: [ 1, 2, 3 ],
  friends: { a: '1' },
  [Symbol(a)]: Symbol(b)
}

下面也可以接着处理属性值是Set和Map数据结构的数据。

  • 处理Set数据类型
  • 处理Map数据类型
function isObj(obj) {
  return Object(obj) === obj
}
function cloneDeep(obj) {
  if (typeof obj === 'symbol') {
    return Symbol(obj.description)
  }
  if (obj instanceof Set) {
    return new Set([...obj])
  }
  if (obj instanceof Map) {
    return new Map([...obj])
  }
  if (typeof obj === 'function') {
    return obj
  }
  if (!isObj(obj)) {
    return obj
  }
  const newObj = Array.isArray(obj) ? [] : {}
  for (const k in obj) {
    newObj[k] = cloneDeep(obj[k])
  }
  const objSymbolKeys = Object.getOwnPropertySymbols(obj)
  for (const k of objSymbolKeys) {
    newObj[k] = cloneDeep(obj[k])
  }
  return newObj
}

const s1 = Symbol('a')
const s2 = Symbol('b')
const obj = { 
  age: null,
  address: [1,2,3],
  friends: { a: '1'},
  [s1]: s2,
  set: new Set(["aaa", "bbb",]),
  map: new Map([["aaa", "bbb"], ["bbb", "ccc"]]),
}
console.log(cloneDeep(obj));
// {
  age: null,
  address: [ 1, 2, 3 ],
  friends: { a: '1' },
  set: Set(2) { 'aaa', 'bbb' },
  map: Map(2) { 'aaa' => 'bbb', 'bbb' => 'ccc' },
  [Symbol(a)]: Symbol(b)
}

下面就是处理循环引用的问题了。什么是循环引用呢,看下图就懂了。但是处理循环引用有点麻烦,需要使用弱引用,弱引用的使用方式可自行去了解。
在这里插入图片描述

  • 处理循环引用
    这里一定要加弱引用,否则会无限循环,导致内存溢出。那么问题来了,为啥不用强引用呢,强引用无法使用垃圾回收机制进行垃圾回收。
    在这里插入图片描述
    最终版封装函数如下所示:
function isObj(obj) {
  return Object(obj) === obj
}
function cloneDeep(obj, map = new WeakMap()) {
  if (typeof obj === 'symbol') {
    return Symbol(obj.description)
  }
  if (obj instanceof Set) {
    return new Set([...obj])
  }
  if (obj instanceof Map) {
    return new Map([...obj])
  }
  if (typeof obj === 'function') {
    return obj
  }
  if (!isObj(obj)) {
    return obj
  }
  if (map.has(obj)) {
    return map.get(obj)
  }
  const newObj = Array.isArray(obj) ? [] : {}
  map.set(obj, newObj)
  for (const k in obj) {
    newObj[k] = cloneDeep(obj[k], map)
  }
  const objSymbolKeys = Object.getOwnPropertySymbols(obj)
  for (const k of objSymbolKeys) {
    newObj[k] = cloneDeep(obj[k], map)
  }
  return newObj
}

const s1 = Symbol('a')
const s2 = Symbol('b')
const obj = { 
  age: null,
  address: [1,2,3],
  friends: { a: '1'},
  eat: () => {console.log('aaa')},
  [s1]: s2,
  set: new Set(["aaa", "bbb",]),
  map: new Map([["aaa", "bbb"], ["bbb", "ccc"]]),
}
obj.info = obj // 循环引用
console.log(cloneDeep(obj));
// <ref *1> {
  age: null,
  address: [ 1, 2, 3 ],
  friends: { a: '1' },
  eat: [Function: eat],
  set: Set(2) { 'aaa', 'bbb' },
  map: Map(2) { 'aaa' => 'bbb', 'bbb' => 'ccc' },
  info: [Circular *1],
  [Symbol(a)]: Symbol(b)
}
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值