沒有對象?那就複製一個吧!(科普向,實際應用向)

趁着週五沒那麼忙,抽個空整理一下最近使用比較頻繁的一個小技術 對象的深複製
感受啊,這個標題和今天的節日(僞裝不知道原來是情人節)那麼遙相呼應。啊,沒有女友?沒有女友不要緊,複製一個啊。先走你一個:console.log('GirlFriend')
同時筆者須要預個警,本篇注重實際中的應用狀況,中規中矩,不搞花裏胡哨。因此主要是對數組字面量對象進行討論,畢竟前端開發中遇到最多的對象就是這兩個了。在正式分析對象的深複製這個問題以前,筆者以爲有必要對在座的朋友科普下必要的知識。html

科普區

JavaScript中數據類型分爲哪幾種?

這個問題在面試中的命中率真的挺高,並且也是本篇的重要所在。瞭解的朋友可跳過繼續向下看,不瞭解的朋友請跟着我慢慢理解。
JavaScript中數據類型分爲基本數據類型引用數據類型 (固然還有別的叫法,只是筆者認爲這種叫法更科學)。前端

基本數據類型: null, undefined, int, string, boolean

引用數據類型: {'a':'b'}(字面量對象), Array, Map, Set, Symbol 等等...其實就是基本數據類型之外的類型面試

並且,不一樣的數據類型在內存中的存儲方式也是不一樣的:編程

基本數據類型在內存中的存儲方式

咱們都知道,JavaScript中的內存分爲棧內存堆內存(不知道?請點擊)。基本數據類型將變量和值一塊兒放在棧內存;引用數據類型則將變量放在棧內存而將值放在堆內存。先來說基本數據類型
基本數據類型在JavaScript編程中用得極爲普遍,好比:json

let a = 1;
let b = 'name';
let c = true;
let d = null;
let e = undefined;

這些都屬於基本數據類型,都存儲在棧內存中;如如下形式:後端

clipboard.png

棧內存中的變量數據有個好處是若是須要獲取某個變量值的copy值很方便,直接經過 = 操做便可,不須要考慮額外的問題。好比:let f = a; 這個操做意在拷貝一份 a 這個變量,此時棧內存中就變成這樣了:數組

clipboard.png

多出一個變量 f,即便說是從 a 複製而來的,結果卻和 a 沒有任何關係。用代碼驗證一下:測試

let a = 1;
let f = a;

//此時改變a的值
a = 2;

console.log(`a 是${a}`);
console.log(`f 是${f}`);

運行結果:
clipboard.pngspa

引用數據類型在內存中的存儲方式

上面說到:引用數據類型則將變量放在棧內存而將值放在堆內存。該怎麼理解?沒圖我說個jb?
假設有個變量person:指針

let person={
    'name':'Mario'
}

在內存中是這樣的

clipboard.png

棧內存中的變量 person 指向堆內存中一塊內存(至關於持有該內存的指針),而那塊內存中存儲 person 變量的相關內容。
所以能夠看出引用數據類型的複製並無基本數據類型來得方便。即使如此,咱們仍是來證明下這個想法:

let person={
    'name':'Mario'
}

let person_copy = person;

//修改person中的內容
person['name']='JavaScript';

console.log(`person: ${person['name']}`);
console.log(`person_copy: ${person_copy['name']}`);

運行結果:

clipboard.png

能夠看出當咱們複製好一份 person_copy ,並對 person 進行了一次修改。結果兩個變量同時變化。爲何?
由於當程序進行到let person_copy = person;這裏的時候,並非直接把 person 的內容賦值給變量 person_copy,而是把 person 的指針賦值給了變量 person_copy。
clipboard.png

因此說變量 person 和 person_copy 都持有了該內存塊的指針,所以無論哪一個變量修改了內存中的內容,另外一個變量對應的值也會變化。最終咱們能夠肯定:若是想複製一個引用類型的數據,就是要將該內存塊中的內容複製進另外一個內存塊中並把新的指針賦值給新變量

大概內容已經科普完了,接下來就開始本文的重點內容

對象的(深)複製

在開發過程當中,若是某條數據(尤爲是從後臺請求回來的數據)使用頻率很高並且用途複雜,那麼就不得不爲它進行一次複製以備不時之需。針對使用頻率較高的兩個對象數組字面量對象,咱們開始逐一討論。

數組的複製

數組在實際開發中,元素主要分爲基本數據類型字面量對象(不排除還有別的類型數據,只是筆者沒遇到過,因此只針對廣泛的狀況)。
針對元素是基本數據類型的數組的複製操做,筆者提供4種方法:

  • ES6的對象擴展運算符 [...]
let origin = [1, 2, 3, 4, 5];
let another = [...origin];

//向原數組中添加一個元素
origin.push(6);
console.log(`another元素: ${another}`);

運算結果:

clipboard.png

  • slice
let origin = [1, 2, 3, 4, 5];
let another = origin.slice();

//向原數組中添加一個元素
origin.push(6);
console.log(`another元素: ${another}`);

運行結果:

clipboard.png

  • concat
let origin = [1, 2, 3, 4, 5];

//至關於向origin中拼接一個空數組
let another = origin.concat([]);

//向原數組中添加一個元素
origin.push(6);
console.log(`another元素: ${another}`);

運行結果:

clipboard.png

  • JSON.stringify

這個方法可行可是幾乎沒人這麼用。原理是將數組轉爲字符串再轉回數組類型。

let origin = [1, 2, 3, 4, 5];
let another = JSON.parse(JSON.stringify(origin));

//向原數組中添加一個元素
origin.push(6);
console.log(`another元素: ${another}`);

運行結果:

clipboard.png

其中筆者以爲第一和第二個方法比較好用。不過若是數組中的元素是字面量對象的話,請繼續向下看。

字面量對象的深複製

實際開發中,所謂字面量對象能夠人爲是一段json數據。json的重要性不用說,那麼至關重要,先後端交互的核心。
先給出一段json:

{
    'name': 'Mario',
    'age': 26,
    'isCoder': true,
    'homeWebPage': null,
    'fullStackSkills': undefined,
    'hobbies': ['LoL', 'Travel', 'Coding'],
    'phone': {
        'home': 123321,
        'office': 456654
    }
}

首先分析這段json中的數據,不論是基本數據類型仍是引用數據類型都有了,因此想要複製這個json對象,咱們須要針對不一樣的數據作不一樣的處理。根據科普的知識咱們已經知道,複製基本數據類型能夠直接賦值,對於引用數據類型則不行,並且主要使用到的數組和字面量對象(這裏也能夠認爲是json)這兩個類型數據的複製方法都不一樣。所以咱們首先要判斷某一個數值是不是對象:

//判斷item是否爲'object';該方法主要是爲了區分參數是基本類型仍是引用類型
function isObject(item) {
    return (item === null || item === undefined) ? false : (typeof item === 'object');
}

其次,咱們能夠看到origin是一段json,orgin中的phone字段對應的值也是一段json,因此要想整個複製這個對象不免要用到遞歸,一層一層嵌套進行。奉上核心代碼:

/**
 * 
 * @param {字面量對象} origin 
 * @param {origin的鏡像對象} mirror
 */
function deepClone(origin, mirror) {
    //獲取該字面量對象的全部的key
    let keys = Object.keys(origin);
    //遍歷全部的key已保證複製的完整
    keys.forEach(key => {
        let value = origin[key];
        if (isObject(value)) {   //判斷是否爲對象,若是是則須要額外處理;若是不是則直接複製
            if (Array.isArray(value)) { //判斷是否爲數組,若是是則須要複製該數組並存入mirror;若是不是則進行遞歸調用
                let copy = value.slice();
                mirror[key] = copy;
            } else {
                //初始化本次字面量對象的鏡像對象
                let obj = {};
                mirror[key] = obj;
                //引用傳值
                //遞歸調用
                deepClone(value, obj);
            }
        } else {
            mirror[key] = value;
        }
    });
}

經過測試,

//Test
let mirror = {};
deepClone(origin, mirror);

//向原對象中的hobbies中增長一項
origin['hobbies'].push('Eat');
console.log(mirror);

運行結果:

clipboard.png

證實方法有效。
固然針對json數據的複製,也能夠只用JSON.parse(JSON.stringify(origin))實現,具體效率怎麼樣,筆者也沒有進行測試。因此這塊有待驗證。由於該文章注重實際開發中的應用,因此例子沒有用到複雜的對象(例如:Set, Symbol 等等...)。因此若是有這個疑問的朋友也不用糾結了。

寫得差很少了,能想到了就這些了...收拾收拾準備跑路了

相關文章
相關標籤/搜索