Javascript深淺拷貝

拷貝

復(fù)制與拷貝

let user = {
  name: "John"
};
let user2=user; //變量名復(fù)制,只是持有了源對象的引用
let userClone=clone(user);//對象克隆熬尺,新對象是是源對象的拷貝

復(fù)制:將一個對象a賦值給另一個變量b摸屠,這個只是存儲了對象a的引用地址,是屬于同一個對象

克铝缓摺:創(chuàng)建一份獨(dú)立的對象拷貝季二,新對象具有源對象項(xiàng)的所有可枚舉屬性(值),兩個對象之間相互獨(dú)立

淺拷貝

思路:聲明一個新對象揭措,將源對象的可枚舉屬性(值)拷貝到新對象上

實(shí)現(xiàn)方式

  1. for...in 復(fù)制所有屬性值
  • 會拷貝對象自身以及其原型鏈上的可枚舉屬性
   let dest = {}; // 新的空對象
   // 復(fù)制所有的屬性值
   for (let key in src) {
     dest[key] = src[key];
   }

  1. 采用jQuery使用extend,jQuery.extent(dest,src)以默認(rèn)配置為優(yōu)先胯舷,用戶設(shè)置為覆蓋
    賦值對象的可枚舉屬性
  • 會拷貝對象自身以及其原型鏈上的可枚舉屬性
  • 無法處理值為undefined的屬性/值
  • 只拷貝對象中基本數(shù)據(jù)類型的屬性,對于引用數(shù)據(jù)類型的數(shù)據(jù)會保持對象引用绊含,
  1. Object.assign(dest,[ src1, src2, src3...]),將 src1, ..., srcN 這些所有的對象復(fù)制到 dest
  • 只拷貝對象中基本數(shù)據(jù)類型的屬性需纳,對于引用數(shù)據(jù)類型的數(shù)據(jù)會保持對象引用
  • 如果目標(biāo)對象中的屬性具有相同的鍵,則屬性將被源對象中的屬性覆蓋艺挪。后面的源對象的屬性將類似地覆蓋前面的源對象的屬性。
  • 只會拷貝源對象自身可枚舉的屬性到目標(biāo)對象。該方法使用源對象的[[Get]]和目標(biāo)對象的[[Set]]麻裳,所以它會調(diào)用相關(guān) getter 和 setter口蝠。因此,它分配屬性津坑,而不僅僅是復(fù)制或定義新的屬性妙蔗。如果合并源包含getter,這可能使其不適合將新屬性合并到原型中疆瑰。為了將屬性定義(包括其可枚舉性)復(fù)制到原型眉反,應(yīng)使用Object.getOwnPropertyDescriptor()和Object.defineProperty() 。
  • String類型和 Symbol 類型的屬性都會被拷貝穆役。
  • 在出現(xiàn)錯誤的情況下寸五,例如,如果屬性不可寫耿币,會引發(fā)TypeError梳杏,如果在引發(fā)錯誤之前添加了任何屬性,則可以更改target對象淹接。
  • 不會在那些src對象值為 null或 undefined 的時候拋出錯誤十性。
  • 原始類型會被包裝為對象

總結(jié)

無法正常處理屬性(值)為引用類型的數(shù)據(jù),

深拷貝

思路:復(fù)制的時候應(yīng)該檢查 obj[key] 的每一個值塑悼,如果它是一個對象劲适,那么把它也復(fù)制一遍

實(shí)現(xiàn)方式

  1. jQuery.extend(true,dest,src),會遞歸處理對象的中引用數(shù)據(jù)類型屬性(值)

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

  • 無法拷貝對象中Function類型的屬性
  • 無法拷貝對象中值為undefined的屬性
  • 無法拷貝具有循環(huán)引用的對象(可用來檢測對象是否循環(huán)引用)
  1. 基于遞歸實(shí)現(xiàn)
var deepClone=function(obj) {
  // 處理數(shù)組
  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
  }
}

以上版本并未處理循環(huán)引用問題,以及特殊的引用數(shù)據(jù)類型(Set/Map/RegExp等)

循環(huán)引用

我們先來看個例子

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

對象man的屬性father又指向了man本身,形成了“環(huán)”厢蒜,如果不能正常處理此類情況霞势,將出現(xiàn)調(diào)用棧溢出。

有一個標(biāo)準(zhǔn)的深拷貝算法郭怪,用于解決上面這種和一些更復(fù)雜的情況支示,叫做 結(jié)構(gòu)化克隆算法(Structured cloning algorithm)。

算法的優(yōu)點(diǎn)是:

  • 可以復(fù)制 RegExp 對象鄙才。
  • 可以復(fù)制 Blob颂鸿、File 以及 FileList 對象。
  • 可以復(fù)制 ImageData 對象攒庵。CanvasPixelArray 的克隆粒度將會跟原始對象相同嘴纺,并且復(fù)制出來相同的像素數(shù)據(jù)。
  • 可以正確的復(fù)制有循環(huán)引用的對象

依然存在的缺陷是:

  • Error 以及 Function 對象是不能被結(jié)構(gòu)化克隆算法復(fù)制的浓冒;如果你嘗試這樣子去做栽渴,這會導(dǎo)致拋出 DATA_CLONE_ERR 的異常。

  • 企圖去克隆 DOM 節(jié)點(diǎn)同樣會拋出 DATA_CLONE_ERROR 異常稳懒。

  • 對象的某些特定參數(shù)也不會被保留

    • RegExp 對象的 lastIndex 字段不會被保留
    • 屬性描述符闲擦,setters 以及 getters(以及其他類似元數(shù)據(jù)的功能)同樣不會被復(fù)制。例如,如果一個對象用屬性描述符標(biāo)記為 read-only墅冷,它將會被復(fù)制為 read-write纯路,因?yàn)檫@是默認(rèn)的情況下。
    • 原形鏈上的屬性也不會被追蹤以及復(fù)制寞忿。

可參考lodash等庫函數(shù)的實(shí)現(xiàn)

手動實(shí)現(xiàn)深拷貝

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)
}

總結(jié)

  • 在實(shí)際開發(fā)過程中驰唬,我們可以預(yù)估對象的基本結(jié)構(gòu),正確的使用深淺拷貝腔彰,避免在函數(shù)中因修改對象值照成數(shù)據(jù)異常的情形叫编。
  • 大而全的東西,往往是最昂貴的霹抛。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末搓逾,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子上炎,更是在濱河造成了極大的恐慌恃逻,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件藕施,死亡現(xiàn)場離奇詭異寇损,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)裳食,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門矛市,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人诲祸,你說我怎么就攤上這事浊吏。” “怎么了救氯?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵找田,是天一觀的道長。 經(jīng)常有香客問我着憨,道長墩衙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任甲抖,我火速辦了婚禮漆改,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘准谚。我一直安慰自己挫剑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布柱衔。 她就那樣靜靜地躺著樊破,像睡著了一般愉棱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捶码,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天羽氮,我揣著相機(jī)與錄音,去河邊找鬼惫恼。 笑死,一個胖子當(dāng)著我的面吹牛澳盐,可吹牛的內(nèi)容都是我干的祈纯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼叼耙,長吁一口氣:“原來是場噩夢啊……” “哼腕窥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起筛婉,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤簇爆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后爽撒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體入蛆,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年硕勿,在試婚紗的時候發(fā)現(xiàn)自己被綠了哨毁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡源武,死狀恐怖扼褪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情粱栖,我是刑警寧澤话浇,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站闹究,受9級特大地震影響幔崖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜跋核,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一岖瑰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧砂代,春花似錦蹋订、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽椒功。三九已至,卻和暖如春智什,著一層夾襖步出監(jiān)牢的瞬間动漾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工荠锭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留旱眯,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓证九,卻偏偏與公主長得像删豺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子愧怜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容

  • underscore 的源碼中呀页,有很多地方用到了 Array.prototype.slice() 方法,但是并沒有...
    theCoder閱讀 599評論 0 1
  • 簡單講呢拥坛,深淺拷貝蓬蝶,都是進(jìn)行復(fù)制,那么區(qū)別主要在于復(fù)制出來的新對象和原來的對象是否會互相影響猜惋,改一個丸氛,另一個也會變...
    _千尋瀑_閱讀 251評論 0 2
  • Javascript有六種基本數(shù)據(jù)類型(也就是簡單數(shù)據(jù)類型),它們分別是:Undefined惨奕,Null雪位,Boole...
    XMUBeike閱讀 299評論 0 0
  • 淺拷貝 1.基本數(shù)據(jù)類型 是存在棧中的,所以=賦值梨撞,都會創(chuàng)建一個新的空間雹洗,例如 變量b有自己獨(dú)立的空間 2.對象數(shù)...
    Addy_Zhou閱讀 262評論 0 0
  • 明星的書畫有多值錢?上百萬卧波! 現(xiàn)在的娛樂圈明星时肿,好多都開始熱衷繪畫書法。 明星們的書畫作品港粱,價值多少呢螃成? 1、趙本...
    瓷之醉閱讀 145評論 0 0