一篇文章完全說清JS的深拷貝/淺拷貝

一篇文章完全說清JS的深拷貝and淺拷貝

這篇文章的受衆

  • 第一類,業務須要,急需知道如何深拷貝JS對象的開發者。
  • 第二類,但願紮實JS基礎,未來好去面試官前秀操做的好學者。

寫給第一類讀者

你只須要一行黑科技代碼就能夠實現深拷貝
var copyObj = {
    name: 'ziwei',
    arr : [1,2,3]
}

var targetObj = JSON.parse(JSON.stringify(copyObj))

此時 copyObj.arr !== targetObj.arr  已經實現了深拷貝
彆着急走,利用window.JSON的方法作深拷貝存在2個缺點:
  • 若是你的對象裏有函數,函數沒法被拷貝下來
  • 沒法拷貝copyObj對象原型鏈上的屬性和方法

固然,你明確知道他們的缺點後,若是他的缺點對你的業務需求沒有影響,就能夠放心使用了,一行原生代碼就能搞定。面試

目前我在開發業務場景中,大多還真能夠忽略上面2個缺點。每每須要深拷貝的對象裏沒有函數,也不須要拷貝它原型鏈的屬性。api

寫給第二類讀者

下面我會盡量全面的講解清楚JS裏對象的拷貝,要講清楚拷貝,你須要一點點前置知識數組

你須要的前置知識:數據結構

  • 理解JS裏的引用類型和值類型的區別,知道Obj存儲的只是引用
  • 對原型鏈有基本瞭解

關於對象拷貝的所有:框架

  • 1.深拷貝、淺拷貝是什麼
  • 2.深拷貝、淺拷貝在業務裏的最多見的應用場景
  • 3.深拷貝和淺拷貝的實現方式
  • 4.總結與建議

1.深拷貝、淺拷貝是什麼

咱們討論JS對象深拷貝、淺拷貝的前提

只有對象裏嵌套對象的狀況下,纔會根據需求討論,咱們要深拷貝仍是淺拷貝。函數

好比下面這種對象spa

var obj1 = {
    name: 'ziwei',
    arr : [1,2,3]
}

由於,若是是相似這樣{name: 'ziwei'},沒有嵌套對象的對象的話,就不必區分深淺拷貝了。只有在有嵌套的對象時,深拷貝和淺拷貝纔有區別code

淺拷貝是什麼樣子的 (咱們暫時無論具體如何實現,由於下面會單講)

調用shallowCopy()後,obj2拷貝obj1全部的屬性。可是obj2.arr和obj1.arr是不一樣的引用,指向同一個內存空間對象

var obj2 = shallowCopy( obj1 , {})
 
 console.log( obj1 !== obj2 )                   // true    不管哪一種拷貝,obj1和obj2必定都是2個不一樣的對象(內存空間不一樣)
 
 console.log( obj2.arr === obj1.arr )            // true   他們2個對象裏arr的引用,指向【相同的】內存空間

因此, 2個obj通過拷貝後,雖然他們屬性相同,也的確是不一樣的對象,但他們內部的obj都是指向同一個內存空間,這種咱們叫淺拷貝blog

深拷貝是什麼樣子的 (咱們暫時無論具體如何實現,由於下面會單講)

調用deepCopy()後,obj2拷貝obj1全部的屬性,並且obj2.arr和obj1.arr是指向不一樣的內存空間,

2個obj2除了拷貝了同樣的屬性,沒有任何其餘關聯。

var obj2 = deepCopy( obj1 , {})
 
 console.log( obj1 !== obj2 )                   // true    不管哪一種拷貝,obj1和obj2必定都是2個不一樣的對象(內存空間不一樣)
 
 console.log( obj2.arr === obj1.arr )            // false   他們2個對象裏arr的引用,指向【不一樣的】內存空間

因此, 2個obj通過拷貝後,除了拷貝下來相同的屬性以外,沒有任何其餘關聯的2個對象,這種咱們叫深拷貝

2.深拷貝在業務裏的最多見的應用場景

舉個栗子,業務需求是 : 一個表格展現商品各類信息,點擊【贊成】時,是能夠彈出對話框調整商品數量的。

這種業務需求下,咱們就會用到對象的深拷貝。由於【商品表格】的屬性和【調整商品表格】的屬性幾乎同樣,咱們須要拷貝。

下面的僞代碼和圖片就是展現使用淺拷貝存在的問題

圖片描述

這樣獲得的adjustTableArr和tableArr裏,內部對象都是相同的,因此就出現了圖中紅線標註的狀況,

當咱們修改【調整商品表格】裏的商品數量時,【商品表格】也跟着改變了,這並非咱們想要的

// 表格對象的數據結構
var tableArr = [
        {goods_name : '長袖腰背夾' , code : 'M216C239E0864' , num : '2'},
        {goods_name : '長袖腰背夾' , code : 'M216C240B0170' , num : '3'},
        {goods_name : '短塑褲' , code : 'M216D241C04106' , num : '3'},
    ]
    
var adjustTableArr = []                  // 調整表格用的數組

for (var key in tableArr) {               // 淺拷貝
    adjustTableArr[key] = tableArr[key]
}

而實際上,咱們但願這2個表格裏的數據徹底獨立,互不干擾,只有在確認調整以後才刷新商品數量。

這種狀況下咱們就可使用前面說的深拷貝的一行黑科技

var adjustTableArr = JSON.parse(JSON.stringify(tableArr))

還記得它的缺陷嗎? 對象裏的函數沒法被拷貝,原型鏈裏的屬性沒法被拷貝。這裏就對業務沒有影響,能夠很方便的深拷貝。

3.深拷貝和淺拷貝的實現方式

其實JQ裏已經有$.extend()函數,實現就是深拷貝和淺拷貝的功能。有興趣的小夥伴也能夠看看源碼。

淺拷貝

淺拷貝比較簡單,就是用for in 循環賦值

function shallowCopy(source, target = {}) {
        var key;
        for (key in source) {
            if (source.hasOwnProperty(key)) {        // 意思就是__proto__上面的屬性,我不拷貝
                target[key] = source[key];
            }
        }
        return target;
    }
深拷貝的實現
  • 深拷貝,就是遍歷那個被拷貝的對象
  • 判斷對象裏每一項的數據類型
  • 若是不是對象類型,就直接賦值,若是是對象類型,就再次調用deepCopy,遞歸的去賦值。
function deepCopy(source, target = {}) {
        var key;
        for (key in source) {
            if (source.hasOwnProperty(key)) {                         // 意思就是__proto__上面的屬性,我不拷貝
                if (typeof(source[key]) === "object") {               // 若是這一項是object類型,就遞歸調用deepCopy
                    target[key] = Array.isArray(source[key]) ? [] : {};
                    deepCopy(source[key], target[key]);
                } else {                                            // 若是不是object類型,就直接賦值拷貝
                    target[key] = source[key];
                }
            }
        }
        return target;
    }

以上的不管深、淺拷貝,都用了source.hasOwnProperty(key),意思是判斷這一項是不是其自有屬性,是的話才拷貝,不是就不拷貝。

也就是說__proto__上面的屬性,我不拷貝。這個其實你能夠根據業務需求,來決定加上和這個條件

(JQ的$.extend()是會連__proto__上的屬性也拷貝下來的,可是是直接拷貝到對象上,而不是放到以前的__proto__上)

4.總結與建議

雖然你們可能常常用框架提供的api來實現深拷貝。

這篇文章分享的目的,更多仍是但願用一篇文章整理清楚深淺拷貝的含義、遞歸實現思路,以及小夥伴們若是使用了JSON.parse()這種黑科技,必定要清楚這樣寫的優缺點。

5.修正

上面的deepCopy方法有漏洞,沒有考慮source一開始就是數組的狀況

下面是一個修改後版本

function deepCopy( source ) {
    let target = Array.isArray( source ) ? [] : {}
    for ( var k in source ) {
        if ( typeof source[ k ] === 'object' ) {
            target[ k ] = deepCopy( source[ k ] )
        } else {
            target[ k ] = source[ k ]
        }
    }
    return target
}
相關文章
相關標籤/搜索