JS对象深拷贝和浅拷贝

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

  • 浅复制:浅复制是复制对象引用,复制后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响
  • 深复制:深复制不是简单的复制引用,而是在堆中重新分配内存,并且把源对象实例的所有属性都进行新建复制,以保证深复制的对象引用图不包含任何原有对象或对象图上的任何对象,复制后的对象与原来的对象是完全隔离的
    简单来说,浅复制只复制一层对象的属性,而深复制则递归复制了所有层级

浅拷贝 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 对象深拷贝和浅拷贝

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,348评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,122评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,936评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,427评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,467评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,785评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,931评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,696评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,141评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,483评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,625评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,291评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,892评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,492评论 2 348