JavaScript中的淺拷貝與深拷貝

值類型與引用類型

談淺拷貝與深拷貝之前,我們需要先理清一個概念,即值類型引用類型

什么是值類型與引用類型?這要先從JS中的基本類型說起。

首先我們知道,JS中有六種基本類型,number, string, boolean, null, undefined,object。這幾個類型就統(tǒng)共被分為兩類值類型引用類型

number,string,boolean,undefined就是值類型;object就是引用類型。object里面涵蓋的就多了,我們常用的數(shù)組呀,函數(shù)呀,還有什么Date對象,Math對象,這些都算在object里面的。

這里面null比較特殊,ECMA標準中將它定義為值類型,當你使用在你的編譯器里執(zhí)行typeof(null)時,它的返回值是object。我個人偏好于將它理解為一個指向空對象的指針,便于理解。

(在stackoverflow上搜索的時候看到這么一個回答

If null is a primitive, why does typeof(null) return "object"?

Because the spec says so.

深以為然哈哈哈哈

等等,可能有人要問了,你說null是一個空對象指針,那什么是指針呢?

不著急,讓我們從計算機如何存儲一個數(shù)據(jù)說起。

值類型與引用類型的存儲

計算機存儲值類型和引用類型的方法是不同的。這里我們需要提到兩種分配內(nèi)存的數(shù)據(jù)結構,

什么是堆和棧呢?這講起來就復雜了,我們只需要知道,棧和堆都是一種內(nèi)存的分配方式,棧是后進先出的,堆是先進先出的(這個聽起來有點像隊列,但實際上它的存儲更像是鏈表)。

棧里面的數(shù)據(jù)占據(jù)空間的大小是固定的(例如JS里的數(shù)字就固定為64bit的浮點數(shù)),空間也是相對較小的,JS里面會把值類型放到棧里面去存儲,而存儲的就是這個值本身。

而堆里面的數(shù)據(jù)占據(jù)空間的大小是不固定的,空間相對較大,JS會把引用類型的值放到堆里面去存儲,而把這個引用類型的地址存放到棧里面去(這個保存地址的變量就是指針)。

為什么要這樣做呢?

你想呀,我們學的很多知識,什么算法呀,什么數(shù)據(jù)結構呀,都有一個中心思想,節(jié)約是美德。而計算機里最寶貴的是什么?內(nèi)存和CPU呀。

想想我們平常會用到的引用類型,數(shù)組元素可以幾百上千,對象里面定義幾十個成員,函數(shù)里面變量表達式幾十行。跟值類型比起來,引用類型的大小不定,而且通常還蠻大的。這么些個大家伙,計算可要好好想想怎么存儲它們。

于是計算機拿了一個指針指向引用類型,當你想要用到那些引用值時,計算機就會去找指向它們地址的指針,然后再去找到它們的值。

于是,回歸正題,當我們想要拷貝一個變量的值得時候,它的存儲類型就決定了我們拷貝一個值的方式。

這里偷一張《JavaScript高級程序設計》里面的圖,很清晰了表示了兩者的區(qū)別。

值類型的拷貝
引用類型的拷貝

值類型的拷貝

JS里面,經(jīng)常有這么一個需求,讓你去實現(xiàn)一個函數(shù),可以復制當前傳入?yún)?shù)的值,而傳入的參數(shù)有數(shù)字、布爾值、字符串,當然,還有對象。

透過上面的圖,我們可以很輕松地就完成一個值類型的拷貝。

上面我們說了,值類型是存儲在棧里面的,直接存儲的就是這個變量的值。那么要拷貝值類型,很直接的將這個變量賦值給另一個新的變量就行了。

引用類型的淺拷貝與深拷貝

淺拷貝

引用類型與值類型就不同了。

引用類型的淺拷貝,我個人認為就是上圖所示,直接拷貝的對象的引用,放到代碼里面就長這樣。

var obj = {
    "a":"1",
    "b":"2",
    "c":{
        "c1":3,
        "c2":4
    }
}
var newObj = obj;
newObj[a] = 3;
console.log(obj[a]);//3

很容易理解,拷貝了原對象的引用,那么這個新變量的值實際上保存的就是原對象的地址,當新對象對對象中的值進行賦值的時候,同時也改變了原對象的值

也有人把只拷貝對象中的一層屬性的拷貝稱為淺拷貝。什么意思呢?像上面的那個對象的a和b屬性就只有一層屬性,而c屬性復雜一些,它代表了一個對象。

但是我決定把這個放在深拷貝里討論。

深拷貝

深拷貝是一個復雜的命題。何為深拷貝?即復制一個與原對象一模一樣的對象,包括里面的每個屬性,不論是嵌套了幾層的,是日期還是數(shù)組還是對象。并且兩者的地址不同,是兩個獨立的對象。與淺拷貝不同,不論你如何修改新對象的值,都不會對舊的對象造成任何的影響。

遍歷屬性拷貝

最簡單也是最容易想到的一個辦法,即創(chuàng)建一個新的空對象,把原對象的值遍歷一遍,然后賦給新對象。

var obj = {
    "a": 1,
    "object": {
        "b": [2, 3, 4],
        "c": 3
    }
}

function cloneObject(obj) {
    var copy = {};
    for (var prop in obj) {
        if (obj.hasOwnProperty(prop))
            copy[prop] = obj[prop];
    }
    return copy;
}

var newObj = cloneObject(obj);

console.log(newObj);//與obj看起來似乎是相同的

然而事實真是這樣嗎?讓我們改變一下newObj中object屬性中的值,然后打印出來原對象object屬性的值。

newObj["object"].c = 4;

console.log(obj["object"].c);//變成了4

這是為什么呢?

這是因為當我們遍歷到例如(原對象中的)對象或者數(shù)組這樣引用類型時,進行的卻是淺拷貝

于是問題來了,這種拷貝方式如果要進行真真正正的深拷貝必然是不行的,對于對象中的引用類型,我們還要做一次深拷貝。如何做呢?遞歸。

遞歸拷貝

這是我在做百度前端學院的2015春季題的時候?qū)懙纳羁截惔a,只考慮了對象中出現(xiàn)數(shù)組、對象、日期的情況。(這里我也記錄了一下做春季題的思路和代碼,有興趣可以看看我的另一篇博文:點我

function getVarType(data) {
  //確定當前變量的對象
    if (data === undefined) {
        return 'Undefined';
    }
    if (data === null) {
        return 'Null';
    }
    return Object.prototype.toString.call(data).slice(8, -1).toLowerCase();
};

function cloneObject(data) {
    var objectType = getVarType(data);
    //the object for cloning is native object
    if (objectType == "null" || objectType == "undefined") {
        return data;
    }

    if (objectType == "string" || objectType == "number" || objectType == "boolean") {
        var copy = data;
        return copy;
    } else if (objectType == "date") {
        var copy = new Date();
        copy.setTime(data.getTime());
        return copy;
    } else if (objectType == "array") {
        var copy = [];
        for (var i = 0; i < data.length; i++) {
            copy[i] = cloneObject(data[i]);
        }
        return copy;
    } else if (objectType == "object") {
        var copy = {};
        for (var attr in data) {
            if (data.hasOwnProperty(attr)) {
                copy[attr] = cloneObject(data[attr]);
            }
        }
        return copy;
    }
}

前面一大堆完成了對值類型和數(shù)組字符串日期的拷貝。最后一個if語句中,完成了對對象的深拷貝。

這里用到遞歸,相當于再對對象的屬性值做一次深拷貝,如果是值類型,直接賦值就好,如果是引用類型,再按分類進行分別的拷貝。

讓我們用在這個函數(shù)再進行一次上面的檢測。

var obj = {
    "a": 1,
    "object": {
        "b": [2, 3, 4],
        "c": 3
    }
}
var newObj = cloneObject(obj);

console.log(newObj);

newObj["object"].c = 4;

console.log(obj["object"].c);//與新對象不同,這里輸出的值為3

于是,我們完成了對對象的深拷貝。

但是等等。

是不是還有點東西沒考慮?

想想如果對象屬性的值有函數(shù)呢?讓我們來試試這個例子。

var obj = {
    "a": 1,
    "b": {
        "c": 2,
    },
    "c": function hello() {
        console.log("hello,world");
    }
}

console.log(cloneObject(obj));

/*
打印結果如下:
{
    a: 1,
    b: {
        c: 2,
    },
    c: undefined
}

*/

我們這個函數(shù)有點小小的遺憾,它不能拷貝函數(shù)。

但是仔細想想,我們需要拷貝函數(shù)嗎?

函數(shù)是做什么用的?我們需要它去實現(xiàn)一個功能的,拷貝一個一模一樣的函數(shù),它實現(xiàn)的功能不也一模一樣嗎?拷貝一個函數(shù)真的有必要嗎?(并不是偷懶哈哈哈哈哈

自己寫完了,讓我們也來看看用點其他方法去實現(xiàn)的深拷貝。

jQuery實現(xiàn)深拷貝

jQuery要實現(xiàn)深拷貝,要用到extend這個方法,這是干嘛的呢?讓我們看看文檔:

Merge the contents of two or more objects together into the first object.

[jQuery.extend( deep ], target, object1 [, objectN ] )

  • deep

    Type: Boolean

    If true, the merge becomes recursive (aka. deep copy). Passing false for this argument is not supported.

  • target

    Type: Object

    The object to extend. It will receive the new properties.

  • object1

    Type: Object

    An object containing additional properties to merge in.

  • objectN

    Type: Object

    Additional objects containing properties to merge in.

jQuery怎么做深拷貝?簡單粗暴一行代碼var newObj = $.extend(true,{},obj);

至于具體的,等博主有力氣了再來分析分析源碼(躺)。

JSON實現(xiàn)深拷貝

JSON怎么做深拷貝?最開始我挺莫名其妙的,然后看了代碼才豁然開朗。

也是簡單粗暴的一句代碼newObj = JSON.parse( JSON.stringify(obj) );

巧用了JSON的parse和stringify,但是它也沒辦法實現(xiàn)函數(shù)的拷貝。

其他

還有一些工具庫,例如lodash,underscore等等,這些對深拷貝的實現(xiàn),就……等以后再分析分析啦。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

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

  • 簡述 -在面試中經(jīng)常被問到深拷貝(深復制)和淺拷貝(淺復制),下面就對其進行簡單的說明一下。-淺拷貝:在使用Jav...
    諾奕閱讀 1,453評論 6 6
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,195評論 30 471
  • __block和__weak修飾符的區(qū)別其實是挺明顯的:1.__block不管是ARC還是MRC模式下都可以使用,...
    LZM輪回閱讀 3,350評論 0 6
  • 晚安 愿你的夢里有我 在街角的櫥窗旁 捧著玫瑰花 等你
    言良閱讀 407評論 0 0
  • 當冬夜?jié)u暖,當夏炎漸涼 我以為,是你來了 空空的期待,小心翼翼 惆悵的莫名,隱秘的渴望 仔細的思量,創(chuàng)構那些屬于你...
    亦步亦趨從遙遙到無期閱讀 284評論 0 1