也來談一談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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...