也來談一談js的淺複製和深複製

来源:http://www.cnblogs.com/tracylin/archive/2016/04/01/5346314.html
-Advertisement-
Play Games

1.淺複製VS深複製 本文中的複製也可以稱為拷貝,在本文中認為複製和拷貝是相同的意思。另外,本文只討論js中複雜數據類型的複製問題(Object,Array等),不討論基本數據類型(null,undefined,string,number和boolean),這些類型的值本身就存儲在棧記憶體中(stri ...


1.淺複製VS深複製

本文中的複製也可以稱為拷貝,在本文中認為複製和拷貝是相同的意思。另外,本文只討論js中複雜數據類型的複製問題(Object,Array等),不討論基本數據類型(null,undefined,string,number和boolean),這些類型的值本身就存儲在棧記憶體中(string類型的實際值還是存儲在堆記憶體中的,但是js把string當做基本類型來處理 ),不存在引用值的情況。

淺複製和深複製都可以實現在已有對象的基礎上再生一份的作用,但是對象的實例是存儲在堆記憶體中然後通過一個引用值去操作對象,由此複製的時候就存在兩種情況了:複製引用和複製實例,這也是淺複製和深複製的區別所在。

淺複製:淺複製是複製引用,複製後的引用都是指向同一個對象的實例,彼此之間的操作會互相影響

深複製:深複製不是簡單的複製引用,而是在堆中重新分配記憶體,並且把源對象實例的所有屬性都進行新建複製,以保證深複製的對象的引用圖不包含任何原有對象或對象圖上的任何對象,複製後的對象與原來的對象是完全隔離的

由深複製的定義來看,深複製要求如果源對象存在對象屬性,那麼需要進行遞歸複製,從而保證複製的對象與源對象完全隔離。然而還有一種可以說處在淺複製和深複製的粒度之間,也是jQuery的extend方法在deep參數為false時所謂的“淺複製”,這種複製只進行一個層級的複製:即如果源對象中存在對象屬性,那麼複製的對象上也會引用相同的對象。這不符合深複製的要求,但又比簡單的複製引用的複製粒度有了加深。

2. 淺複製

本文認為淺複製就是簡單的引用複製,這種情況較很簡單,通過如下代碼簡單理解一下:

 var src = {
        name:"src"
    }
    //複製一份src對象的應用
    var target = src;
    target.name = "target";
    console.log(src.name);   //輸出target

target對象只是src對象的引用值的複製,因此target的改變也會影響src。

3. 深複製

深複製的情況比較複雜一些,我們先從一些比較簡單的情況說起:

3.1 Array的slice和concat方法

Array的slice和concat方法都會返回一個新的數組實例,但是這兩個方法對於數組中的對象元素卻沒有執行深複製,而只是複製了引用了,因此這兩個方法並不是真正的深複製,通過以下代碼進行理解:

    var array = [1,2,3];
    var array_shallow = array;
    var array_concat = array.concat();
    var array_slice = array.slice(0);
    console.log(array === array_shallow);   //true
    console.log(array === array_slice);     //false
    console.log(array === array_concat);    //false

可以看出,concat和slice返回的不同的數組實例,這與直接的引用複製是不同的。

    var array = [1, [1,2,3], {name:"array"}];
    var array_concat = array.concat();
    var array_slice = array.slice(0);
    //改變array_concat中數組元素的值
    array_concat[1][0] = 5;
    console.log(array[1]);    //[5,2,3]
    console.log(array_slice[1]);  //[5,2,3]
    //改變array_slice中對象元素的值
    array_slice[2].name = "array_slice";
    console.log(array[2].name);   //array_slice
    console.log(array_concat[2].name); //array_slice

通過代碼的輸出可以看出concat和slice並不是真正的深複製,數組中的對象元素(Object,Array等)只是複製了引用

3.2 JSON對象的parse和stringify

JSON對象是ES5中引入的新的類型(支持的瀏覽器為IE8+),JSON對象parse方法可以將JSON字元串反序列化成JS對象,stringify方法可以將JS對象序列化成JSON字元串,藉助這兩個方法,也可以實現對象的深複製。

    var source = {
        name:"source",
        child:{
            name:"child"
        }
    }
    var target = JSON.parse(JSON.stringify(source));
    //改變target的name屬性
    target.name = "target";
    console.log(source.name);   //source
    console.log(target.name);   //target
    //改變target的child
    target.child.name = "target child";
    console.log(source.child.name);  //child
    console.log(target.child.name);  //target child

從代碼的輸出可以看出,複製後的target與source是完全隔離的,二者不會相互影響。

這個方法使用較為簡單,可以滿足基本的深複製需求,而且能夠處理JSON格式能表示的所有數據類型,但是對於正則表達式類型、函數類型等無法進行深複製(而且會直接丟失相應的值),同時如果對象中存在迴圈引用的情況也無法正確處理

3.3 jQuery中的extend複製方法

jQuery中的extend方法可以用來擴展對象,這個方法可以傳入一個參數:deep(true or false),表示是否執行深複製(如果是深複製則會執行遞歸複製),我們首先看一下jquery中的源碼(1.9.1)

jQuery.extend = jQuery.fn.extend = function() {
	var options, name, src, copy, copyIsArray, clone,
		target = arguments[0] || {},
		i = 1,
		length = arguments.length,
		deep = false;

	// Handle a deep copy situation
	if ( typeof target === "boolean" ) {
		deep = target;
		target = arguments[1] || {};
		// skip the boolean and the target
		i = 2;
	}

	// Handle case when target is a string or something (possible in deep copy)
	if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
		target = {};
	}

	// extend jQuery itself if only one argument is passed
	if ( length === i ) {
		target = this;
		--i;
	}

	for ( ; i < length; i++ ) {
		// Only deal with non-null/undefined values
		if ( (options = arguments[ i ]) != null ) {
			// Extend the base object
			for ( name in options ) {
				src = target[ name ];
				copy = options[ name ];

				// Prevent never-ending loop
				if ( target === copy ) {
					continue;
				}

				// Recurse if we're merging plain objects or arrays
				if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
					if ( copyIsArray ) {
						copyIsArray = false;
						clone = src && jQuery.isArray(src) ? src : [];

					} else {
						clone = src && jQuery.isPlainObject(src) ? src : {};
					}

					// Never move original objects, clone them
					target[ name ] = jQuery.extend( deep, clone, copy );

				// Don't bring in undefined values
				} else if ( copy !== undefined ) {
					target[ name ] = copy;
				}
			}
		}
	}

	// Return the modified object
	return target;
};

這個方法是jQuery中重要的基礎方法之一,可以用來擴展jQuery對象及其原型,也是我們編寫jQuery插件的關鍵方法,事實上這個方法基本的思路就是如果碰到array或者object的屬性,那麼就執行遞歸複製,這也導致對於Date,Function等引用類型,jQuery的extend也無法支持。下麵我們大致分析一下這個方法:

(1)第1-6行定義了一些局部變數,這些局部變數將在以後用到,這種將函數中可能用到的局部變數先統一定義好的方式也就是“單var”模式

(2)第9-13行用來修正deep參數,jQuery的這個方法是將deep作為第一個參數傳遞的,因此這裡就判斷了第一個參數是不是boolean類型,如果是,那麼就調整target和i值,i值表示第一個source對象的索引

(3)第17-19行修正了target對象,如果target的typeof操作符返回的不是對象,也不是函數,那麼說明target傳入的是一個基本類型,因此需要修正為一個空的對象字面量{}

(4)第22-25行來處理只傳入了一個參數的情況,這個方法在傳入一個參數的情況下為擴展jQuery對象或者其原型對象

(5)從27行開始使用for in去遍歷source對象列表,因為extend方法是可以傳入多個source對象,取出每一個source對象,然後再嵌套一個for in迴圈,去遍歷某個source對象的屬性

(6)第32行分別取出了target的當前屬性和source的當前屬性,35-38行的主要作用在於防止深度遍歷時的死迴圈。然而如果source對象本身存在迴圈引用的話,extend方法依然會報堆棧溢出的錯誤

(7)第41行的if用來處理深複製的情況,如果傳入的deep參數為true,並且當前的source屬性值是plainObject(使用對象字面量創建的對象或new Object()創建的對象)或數組,則需要進行遞歸深複製

(8)第42-48根據copy的類型是plainObject還是Array,對src進行處理:如果copy是數組,那麼src如果不是數組,就改寫為一個空數組;如果copy是chainObject,那麼src如果不是chainObject,就改寫為{}

(9)如果41行的if條件不成立,那麼直接把target的src屬性用copy覆蓋

jQuery的extend方法使用基本的遞歸思路實現了深度複製,但是這個方法也無法處理source對象內部迴圈引用的問題,同時對於Date、Function等類型的值也沒有實現真正的深度複製,但是這些類型的值在重新定義時一般都是直接覆蓋,所以也不會對源對象造成影響,因此一定程度上也符合深複製的條件

3.4 自己實現一個copy方法

根據以上的思路,自己實現一個copy,可以傳入deep參數表示是否執行深複製:

 //util作為判斷變數具體類型的輔助模塊
    var util = (function(){
        var class2type = {};
        ["Null","Undefined","Number","Boolean","String","Object","Function","Array","RegExp","Date"].forEach(function(item){
            class2type["[object "+ item + "]"] = item.toLowerCase();
        })

        function isType(obj, type){
            return getType(obj) === type;
        }
        function getType(obj){
            return class2type[Object.prototype.toString.call(obj)] || "object";
        }
        return {
            isType:isType,
            getType:getType
        }
    })();

    function copy(obj,deep){
         //如果obj不是對象,那麼直接返回值就可以了
        if(obj === null || typeof obj !== "object"){
            return obj;
        }
     //定義需要的局部變臉,根據obj的類型來調整target的類型
        var i, target = util.isType(obj,"array") ? [] : {},value,valueType;
        for(i in obj){
            value = obj[i];
            valueType = util.getType(value);
        //只有在明確執行深複製,並且當前的value是數組或對象的情況下才執行遞歸複製
            if(deep && (valueType === "array" || valueType === "object")){
                target[i] = copy(value);
            }else{
                target[i] = value;
            }
        }
        return target;
    }

  

  

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 現如今做電商網站必不可少的一個效果就是限時搶購,這也是各大電商網站的一種促銷手段。下麵的圖片就是聚划算上面的限時搶的效果 一、實現限時搶的效果需要用到的知識 :Javascript Date()對象 Date()返回當前的日期和事件 getYear()返回年份 獲得年最好用 getFullYear( ...
  • 第一章、瞭解web及網路基礎 1.2 http的誕生 HTTP於1990年問世,那時候HTTP並沒有作為正式的標準被建立,被稱為HTTP/0.9 HTTP正式作為標準被公佈是在1996年5月,版本被命名為HTTP/1.0,該協議至今仍被廣泛用在伺服器端。 HTTP/1.1於1997年1月公佈,是目前 ...
  • 開始這個實例之前,我們簡單談一下Node.js吧,Node.js是一個由JavaScript書寫而成的強大Web開發框架,它讓開發強壯的、伸縮性良好的伺服器端Web應用變得更加簡單、容易。這種技術誕生於09年末,在一個JavaScript大會上宣佈,當時這項在伺服器端運行JavaScript技術讓所 ...
  • 前幾天寫的那個拖拽,自己留下的疑問。。。這次在熱心博友的提示下又修正了一些小小的bug,也加了拖拽的邊緣檢測部分。。。就再聊聊拖拽吧 一、不要直接操作dom元素 react中使用了虛擬dom的概念,目地就是要儘量避免直接操作dom元素,所以我們在對dom元素進行操作的時候需要註意,我之前為了獲取fo ...
  • 本文同步至微信公眾號http://mp.weixin.qq.com/s?__biz=MzAxMzgwNDU3Mg==&mid=402267570&idx=1&sn=4b0dc27842c32d902bad2dc4eea75f9a#rd 感興趣的可以掃碼關註哈 生命周期(Life Cycle)這個詞, ...
  • test 隨著自己對於web前端知識瞭解的越多,越來越發現自己真的好菜 一臉茫然階段 兩年前大學接觸網頁設計,那時對於網頁設計一竅不通,只是看了一本自己大學編的一本入門教材,我甚至不知道那些網頁設計的代碼是乾什麼用的,大學的老師自己講的很投入,然而我並不懂。最後考試他就划了重點。我們只要記一些簡單的 ...
  • 一直以來,大家都在說Javascript是單線程,瀏覽器無論在什麼時候,都且只有一個線程在運行JavaScript程式。 但是,不知道大家有疑問沒——就是我們在編程過程中的setTimeout(類似的還有setInterval、Ajax),不是非同步執行的嗎?!! 例如: 運行代碼,打開chrome調 ...
  • Atitit.獲得向上向下左的右的鄰居的方法 軟鍵盤的設計.. Left right可以直接使用next prev.. Up down可以使用pix 判斷...獲得next element的position...比較top 不過,要是跨block的化...僅僅所有的可以使用positon方案了... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...