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閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容

  • underscore 的源碼中,有很多地方用到了 Array.prototype.slice() 方法,但是并沒有...
    theCoder閱讀 603評論 0 1
  • 簡單講呢,深淺拷貝,都是進行復制,那么區別主要在于復制出來的新對象和原來的對象是否會互相影響,改一個,另一個也會變...
    _千尋瀑_閱讀 253評論 0 2
  • Javascript有六種基本數據類型(也就是簡單數據類型),它們分別是:Undefined,Null,Boole...
    XMUBeike閱讀 301評論 0 0
  • 淺拷貝 1.基本數據類型 是存在棧中的,所以=賦值,都會創建一個新的空間,例如 變量b有自己獨立的空間 2.對象數...
    Addy_Zhou閱讀 263評論 0 0
  • 明星的書畫有多值錢?上百萬! 現在的娛樂圈明星,好多都開始熱衷繪畫書法。 明星們的書畫作品,價值多少呢? 1、趙本...
    瓷之醉閱讀 146評論 0 0