js深拷贝最终解决方案

本文将提供javascript深拷贝的解决方案,能处理多重嵌套,对数组、函数、Date、RegExp、null、undefined、原型链、循环引用等都有进行处理。

相信大家已经了解到JavaScript的深拷贝和浅拷贝概念。对于深拷贝,大家常用JSON“序列化+反序列化”的方式来解决,但是

由于JSON.parse(JSON.stringify())进行深拷贝会有如下问题:

对于 JSON.parse(JSON.stringify(data))

  • 1.data内若属性的键值为时间对象,其会转为日期字符串形式。
  • 2.data内若属性的键值为RegExp、Error对象,其会转为空对象{}
  • 3.data内若属性的键值为undefined、函数或symbol,其会把该属性丢失。
  • 4.data内若属性的键值有NaN、Infinity和-Infinity,其会变成null
  • 5.data内若属性的键值由自定义构造函数生成的,转换后会丢弃对象的constructor。
  • 6.data中若存在循环引用的情况也无法正确实现深拷贝。

情况1-5示例: 

function Person(name) {
  this.name = name;     
}      
Person.prototype.speak = function(){
  console.log('i can speak')
}
var arr1 =  {
  a:1,
  b:undefined,
  c:new Date('2019-12-24'),
  d:new Person('mily'),
  e:new RegExp('\\w+'),
  f:NaN,
  g:Symbol("foo"),
  h:{
    total:12,
    list:[{name:'小明',age:34},{name:'小红',age:23}]
  }
}
// JSON.parse(JSON.stringify())方式
console.log(JSON.parse(JSON.stringify(arr1))
//结果为
//{
//   a: 1
//   c: "2019-12-24T00:00:00.000Z"
//   d: {name: "mily"}
//   e: {}
//   f: null
//   h: {total: 12, list: Array(2)}
// }

循环引用示例

var obj1 = {
    x: 1, 
    y: 2
};
obj1.z = obj1;//循环引用
console.log(JSON.parse(JSON.stringify(arr1)))

直接就报错了。 

所以JSON.parse(JSON.stringify(data))并不是万能的解决方案。不过JSON.parse(JSON.stringify(data))简单粗暴,已经满足90%的使用场景了。因此 只能通过"递归"来解决上述的问题,函数如下:

/**
 * isType 判断数据类型
 * @param obj 要检验类型的数据,必填
 * @param type 要检验的数据类型,选填
 * @returns {String|Boolean} 返回对应数据格式的字符串,或者与type参数比较的布尔值
 */
const isType = function(obj, type) {
  // tostring会返回对应不同的标签的构造函数
  const toString = Object.prototype.toString
  const map = {
    '[object Boolean]': 'boolean',
    '[object Number]': 'number',
    '[object String]': 'string',
    '[object Function]': 'function',
    '[object Array]': 'array',
    '[object Date]': 'date',
    '[object RegExp]': 'regExp',
    '[object Undefined]': 'undefined',
    '[object Null]': 'null',
    '[object Object]': 'object'
  }
  if (obj instanceof HTMLElement) {
    return type ? type === 'element' : 'element'
  }
  return type ? type === map[toString.call(obj)] : map[toString.call(obj)]
}
/**
 * clone 对象或数组的拷贝函数
 * @param obj 要拷贝的数据(对象/数组)
 * @returns {Object/Array} 返回对象或数组
 */
const clone = function(obj, parent = null) {
  //parent用于递归循环引用爆栈处理
  // 创建一个新对象
  let result = new obj.constructor() //保持继承链
  let keys = Object.keys(obj),
    key = null,
    temp = null,
    _parent = parent
  // 该字段若有父级则需要追溯该字段的父级
  while (_parent) {
    // 如果该字段引用了它的父级则为循环引用
    if (_parent.originalParent === obj) {
      return _parent.currentParent // 循环引用直接返回同级的新对象
    }
    _parent = _parent.parent
  }
  for (let i = 0; i < keys.length; i++) {
    key = keys[i]
    temp = obj[key]
    if (temp && isType(temp) === 'date') {
      result[key] = new Date(temp)
      continue
    }
    if (temp && isType(temp) === 'regExp') {
      result[key] = new RegExp(temp)
      continue
    }
    // 若字段的值是一个对象/数组
    if (temp && (isType(temp) === 'object' || isType(temp) === 'array')) {
      // 递归执行深拷贝 将同级的待拷贝对象与新对象传递给 parent 方便追溯循环引用
      result[key] = clone(temp, {
        originalParent: obj,
        currentParent: result,
        parent: parent
      })
    } else {
      result[key] = temp
    }
  }
  return result
}

还是使用上述例子运行,用户可以输出复制前的arr1,和复制后clone(arr1)进行比对,发现数据及格式完全没有丢失。

console.log(arr1,clone(arr1))

 用户还可以手动验证:修改复制后的var copydata = clone(arr1)的某些属性,发现不会修改到arr1。真正实现了深拷贝。

上面的isType()函数还可以用来判断数据的类型,大家可以放在公共函数内积极使用!

初次练手,如果有错误及优化的地方请积极指正。

发布了1 篇原创文章 · 获赞 5 · 访问量 79

猜你喜欢

转载自blog.csdn.net/liao_yii/article/details/103924574
今日推荐