JS对象深拷贝和浅拷贝

2019-01-03  本文已影响0人  她是我的bug

浅复制和深复制都可以实现在已有对象基础上再生一份对象的引用,但是对象的实例存储都是在堆内存中,然后通过一个引用值去操作对象,所以复制对象的时候就会存在两种情况:复制引用和复制实例,这也是浅复制和深复制的区别所在。

浅拷贝 js 对象

正如上面说的,浅拷贝只拷贝一层对象的属性,因此实现起来是很方便的,下面是一个简单的自己写浅拷贝的代码:

var sourceObj = {
  name: 'tt',
  age: 18,
  job: 'web',
  friends: ['t1', 't2']
}

function cloneObj(sourceObj) {
  var targetObj = {}

  for (var prop in sourceObj) {
    if (sourceObj.hasOwnProperty(prop)) {
      targetObj[prop] = sourceObj[prop]
    }
  }

  return targetObj
}

var targetObj = cloneObj(sourceObj)

但是浅拷贝存在一个问题,sourceObj 的 friends 属性是一个引用类型的数组对象,浅拷贝只是实现了 targetObj 和 sourceObj 指向同一个引用,如果这时候修改 targetOb.friends 的值,sourceObj.fiends 的值也会受影响:

targetObj.friends.push('t3')
console.log(sourceObj.friends, targetObj.friends) // ['t1', 't2', 't3']

到现在有些人应该看出来了,要实现 targetObj 与 sourceObj 之间没有任何关联,要求如果源对象存在对象属性,那么需要进一步进行一层层递归拷贝,从而保证拷贝的对象与源对象完全隔离。这就是我们所说的深拷贝

深拷贝 js 对象

JSON 对象的 parse 和 stringify

JSON 对象 parse 方法可以将 JSON 字符串反序列化成 JS 对象,stringify 方法可以将 JS 对象序列化成 JSON 字符串,借助这两个方法,也可以实现对象的深复制。

var sourceObj = {
  name: 'tt',
  age: 18,
  job: 'web',
  friends: ['t1', 't2']
}

var targetObj = JSON.parse(JSON.stringify(sourceObj))
targetObj.friends.push('t3')
console.log(sourceObj) // ['t1', 't2']

从代码的输出可以看出,复制后的 targetObj 与 sourceObj 是完全隔离的,二者不会相互影响。

这个方法使用较为简单,可以满足基本的深复制需求,而且能够处理 JSON 格式能表示的所有数据类型,但是对于正则表达式类型、函数类型等无法进行深复制(而且会直接丢失相应的值),同时如果对象中存在循环引用的情况也无法正确处理,并且这会抛弃对象的 constructor,也就是深复制之后,无论这个对象原本的构造函数是什么,在深复制之后都会变成 Object。

首先我们要考虑两个问题,带着这两个问题去创建深度复制函数:

  1. 对于任何对象,它可能的类型有 Boolean, Number, Date, String, RegExp, Array 以及 Object(所有自定义的对象全都继承于 Object)
  2. 我们必须保留对象的构造函数信息(从而使新对象可以使用定义在 prototype 上的函数)

下面是根据这两个思路编写的简单的深拷贝代码,当然实现深度复制代码的方法有很多种,这只是其中的一种:

var sourceObj = {
  name: 'tt',
  age: 18,
  job: 'web',
  friends: ['t1', 't2']
}

// util作为判断变量具体类型的辅助模块
var util = (function() {
  var class2Type = {}
  var objTypes = ["Null","Undefined","Number","Boolean","String","Object","Function","Array","RegExp","Date"]

  objTypes.forEach(function(item) {
    class2Type['[object ' + item + ']'] = item.toLowerCase()
  })

  function isType(obj, type) {
    return getType(obj) === type
  }

  function getType(obj) {
    return class2type[Object.prototype.toString.call(obj)] || 'object'
  }

  return {
    isType: isType,
    getType: getType
  }
})()

// deep参数用来判断是否是深度复制
function copy(obj, deep){
  // 如果obj不是对象,那么直接返回值就可以了
  if(obj == null || typeof obj !== 'object'){
    return obj
  }

  // 定义需要的局部变量,根据obj的类型来调整target的类型
   var i,
   target = util.isType(obj,"array") ? [] : {},
   value,
   valueType

   for(i in obj){
        value = obj[i]
        valueType = util.getType(value)
     // 只有在明确执行深复制,并且当前的value是数组或对象的情况下才执行递归复制
        if(deep && (valueType === "array" || valueType === "object")){
            target[i] = copy(value)
        }else{
            target[i] = value
        }
    }
    return target
}

var targetObj = copy(sourceObj, true);
targetObj.friends.push ('t3');
console.log(sourceObj) // ['t1', 't2']

通过看博客发现还有一个比较优美的方法,这里一并贴出来

Object.prototype.clone = function() {
  var Constructor = this.constructor
  var obj = new Constructor()
  for (var attr in this) {
    if (this.hasOwnProperty(attr)) {
      if (typeof this[attr] !== 'function') {
        if (this[attr] === null) {
          obj[attr] = null
        } else {
          obj[attr] = this[attr].clone()
        }
      }
    }
  }
  return obj
}
/* Method of Array*/
Array.prototype.clone = function() {
  var thisArr = this.valueOf()
  var newArr = []
  for (var i = 0; i < thisArr.length; i++) {
    newArr.push(thisArr[i].clone())
  }
  return newArr
}
/* Method of Boolean, Number, String*/
Boolean.prototype.clone = function() {
  return this.valueOf()
}
Number.prototype.clone = function() {
  return this.valueOf()
}
String.prototype.clone = function() {
  return this.valueOf()
}
/* Method of Date*/
Date.prototype.clone = function() {
  return new Date(this.valueOf())
}
/* Method of RegExp*/
RegExp.prototype.clone = function() {
  var pattern = this.valueOf()
  var flags = ''
  flags += pattern.global ? 'g' : ''
  flags += pattern.ignoreCase ? 'i' : ''
  flags += pattern.multiline ? 'm' : ''
  return new RegExp(pattern.source, flags)
}

定义在 Object.prototype 上的 clone()函数是整个方法的核心,对于任意一个非 js 预定义的对象,都会调用这个函数。而对于所有 js 预定义的对象,如 Number,Array 等,就通过一个辅助 clone()函数来实现完整的克隆过程。

原文地址

js 对象深拷贝和浅拷贝

上一篇下一篇

猜你喜欢

热点阅读