Javascript深浅拷贝

拷贝

复制与拷贝

let user = {
  name: "John"
};
let user2=user; //变量名复制,只是持有了源对象的引用
let userClone=clone(user);//对象克隆,新对象是是源对象的拷贝

复制:将一个对象a赋值给另一个变量b,这个只是存储了对象a的引用地址,是属于同一个对象

克隆:创建一份独立的对象拷贝,新对象具有源对象项的所有可枚举属性(值),两个对象之间相互独立

浅拷贝

思路:声明一个新对象,将源对象的可枚举属性(值)拷贝到新对象上

实现方式

  1. for...in 复制所有属性值
  • 会拷贝对象自身以及其原型链上的可枚举属性
   let dest = {}; // 新的空对象
   // 复制所有的属性值
   for (let key in src) {
     dest[key] = src[key];
   }

  1. 采用jQuery使用extend,jQuery.extent(dest,src)以默认配置为优先,用户设置为覆盖
    赋值对象的可枚举属性
  • 会拷贝对象自身以及其原型链上的可枚举属性
  • 无法处理值为undefined的属性/值
  • 只拷贝对象中基本数据类型的属性,对于引用数据类型的数据会保持对象引用,
  1. Object.assign(dest,[ src1, src2, src3...]),将 src1, ..., srcN 这些所有的对象复制到 dest
  • 只拷贝对象中基本数据类型的属性,对于引用数据类型的数据会保持对象引用
  • 如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后面的源对象的属性将类似地覆盖前面的源对象的属性。
  • 只会拷贝源对象自身可枚举的属性到目标对象。该方法使用源对象的[[Get]]和目标对象的[[Set]],所以它会调用相关 getter 和 setter。因此,它分配属性,而不仅仅是复制或定义新的属性。如果合并源包含getter,这可能使其不适合将新属性合并到原型中。为了将属性定义(包括其可枚举性)复制到原型,应使用Object.getOwnPropertyDescriptor()和Object.defineProperty() 。
  • String类型和 Symbol 类型的属性都会被拷贝。
  • 在出现错误的情况下,例如,如果属性不可写,会引发TypeError,如果在引发错误之前添加了任何属性,则可以更改target对象。
  • 不会在那些src对象值为 null或 undefined 的时候抛出错误。
  • 原始类型会被包装为对象

总结

无法正常处理属性(值)为引用类型的数据,

深拷贝

思路:复制的时候应该检查 obj[key] 的每一个值,如果它是一个对象,那么把它也复制一遍

实现方式

  1. jQuery.extend(true,dest,src),会递归处理对象的中引用数据类型属性(值)

  2. JSON.parse(JSON.stringify(obj))

  • 无法拷贝对象中Function类型的属性
  • 无法拷贝对象中值为undefined的属性
  • 无法拷贝具有循环引用的对象(可用来检测对象是否循环引用)
  1. 基于递归实现
var deepClone=function(obj) {
  // 处理数组
  if(isArray(obj)){
    return obj.map(function(ele) {
      return isArray(ele)||isObject(ele)?deepClone(ele):ele
    })
  } else if(isObject(obj)){
    return reduce(obj,function(memo,value,key) {
      memo[key]=isArray(value)||isObject(value)?deepClone(value):value
      return memo
    },{})
  }else {
    return obj
  }
}

以上版本并未处理循环引用问题,以及特殊的引用数据类型(Set/Map/RegExp等)

循环引用

我们先来看个例子

var man = {
    name: 'amsterdam',
    sex: 'male'
};
man['father'] = man;

对象man的属性father又指向了man本身,形成了“环”,如果不能正常处理此类情况,将出现调用栈溢出。

有一个标准的深拷贝算法,用于解决上面这种和一些更复杂的情况,叫做 结构化克隆算法(Structured cloning algorithm)。

算法的优点是:

  • 可以复制 RegExp 对象。
  • 可以复制 Blob、File 以及 FileList 对象。
  • 可以复制 ImageData 对象。CanvasPixelArray 的克隆粒度将会跟原始对象相同,并且复制出来相同的像素数据。
  • 可以正确的复制有循环引用的对象

依然存在的缺陷是:

  • Error 以及 Function 对象是不能被结构化克隆算法复制的;如果你尝试这样子去做,这会导致抛出 DATA_CLONE_ERR 的异常。

  • 企图去克隆 DOM 节点同样会抛出 DATA_CLONE_ERROR 异常。

  • 对象的某些特定参数也不会被保留

    • RegExp 对象的 lastIndex 字段不会被保留
    • 属性描述符,setters 以及 getters(以及其他类似元数据的功能)同样不会被复制。例如,如果一个对象用属性描述符标记为 read-only,它将会被复制为 read-write,因为这是默认的情况下。
    • 原形链上的属性也不会被追踪以及复制。

可参考lodash等库函数的实现

手动实现深拷贝

const deepCloneClourse = (target) => {
  let cached = new WeakMap()

  function baseClone (obj) {
    let objectType = getType(obj)
    let cloneObj
    // 检测对象是否已克隆 返回克隆后的对象
    let temp = cache(cached, obj)
    if (temp) {
      return temp
    }
    switch (objectType) {
      // Object
      case 'Object':
        //缓存已克隆对象
        cached.set(obj, cloneObj = {})
        //key-value 类型中Key可能是symbol
        Object.getOwnPropertySymbols(obj).forEach(item => {
          let symbol = Object(Symbol.prototype.valueOf.call(item))
          cloneObj[symbol] = baseClone(obj[item])
        })
        break
      // 容器类
      case 'Set':
        //缓存已克隆对象
        cached.set(obj, cloneObj = new Set())
        obj.forEach((val) => {
          cloneObj.add(baseClone(val, cached))
        })
        break
      case 'Map':
        //缓存已克隆对象
        cached.set(obj, cloneObj = new Map())
        obj.forEach((val, key) => {
          cloneObj.set(key, baseClone(val))
        })
        //key-value 类型中Key可能是symbol
        Object.getOwnPropertySymbols(obj).forEach(item => {
          let symbol = Object(Symbol.prototype.valueOf.call(item))
          cloneObj[symbol] = baseClone(obj[item])
        })
        break
      case 'Array':
        //缓存已克隆对象
        cached.set(obj, cloneObj = [])
        obj.forEach((val) => {
          cloneObj.push(baseClone(val))
        })
        break
      // 普通对象
      case 'RegExp':
        cloneObj = new RegExp(obj.source, obj.flags)
        break
      case 'Date':
        cloneObj = new Date(obj)
        break
      case 'Symbol':
        cloneObj = Object(Symbol.prototype.valueOf.call(obj))
        break
      case 'Boolean':
        cloneObj = Boolean(obj)
        break
      case 'Function':
        cloneObj = function () {
          return obj.apply(this, arguments)
        }
        break
      default://null undefined NaN string number boolean
        cloneObj = obj
    }
    if (typeof obj === 'object') {
      for (let item in obj) {
        if (obj.hasOwnProperty(item)) {
          cloneObj[item] = baseClone(obj[item])
        }
      }
    }
    return cloneObj
  }

  return baseClone(target)
}

总结

  • 在实际开发过程中,我们可以预估对象的基本结构,正确的使用深浅拷贝,避免在函数中因修改对象值照成数据异常的情形。
  • 大而全的东西,往往是最昂贵的。

参考

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

推荐阅读更多精彩内容

  • underscore 的源码中,有很多地方用到了 Array.prototype.slice() 方法,但是并没有...
    theCoder阅读 596评论 0 1
  • 简单讲呢,深浅拷贝,都是进行复制,那么区别主要在于复制出来的新对象和原来的对象是否会互相影响,改一个,另一个也会变...
    _千寻瀑_阅读 247评论 0 2
  • Javascript有六种基本数据类型(也就是简单数据类型),它们分别是:Undefined,Null,Boole...
    XMUBeike阅读 295评论 0 0
  • 浅拷贝 1.基本数据类型 是存在栈中的,所以=赋值,都会创建一个新的空间,例如 变量b有自己独立的空间 2.对象数...
    Addy_Zhou阅读 258评论 0 0
  • 明星的书画有多值钱?上百万! 现在的娱乐圈明星,好多都开始热衷绘画书法。 明星们的书画作品,价值多少呢? 1、赵本...
    瓷之醉阅读 145评论 0 0