最近的學習中,仔細研究了下深拷貝和淺拷貝,下面就來簡單的總結下。
資料型別
首先我們瞭解下兩種資料型別:
1、基本型別:像Number、String、Boolean等這種為基本型別
2、複雜型別:Object和Array
淺拷貝與深拷貝的概念
接著我們分別來了解下淺拷貝和深拷貝,深拷貝和淺拷貝是隻針對Object和Array這樣的複雜型別的。
淺拷貝:
1 2 3 4 5 6 7 |
var a = { myname: 'yana' }; var b = a; b.myname = '小雅'; console.log(b.myname); // 小雅 console.log(a.myname); // 小雅 |
1 2 3 4 5 |
var a = ['myname', 'yana']; var b = a; b[1] = '小雅'; console.log(a); // ["myname", "小雅"] console.log(b); // ["myname", "小雅"] |
可以看出,對於物件或陣列型別,當我們將a賦值給b,然後更改b中的屬性,a也會隨著變化。
也就是說a和b指向了同一塊記憶體,所以修改其中任意的值,另一個值都會隨之變化,這就是淺拷貝。
深拷貝:
剛剛我們瞭解了什麼是淺拷貝,那麼相應的,如果給b放到新的記憶體中,將a的各個屬性都複製到新記憶體裡,就是深拷貝。
也就是說,當b中的屬性有變化的時候,a內的屬性不會發生變化。
淺拷貝
那麼除了上面簡單的賦值引用,還有哪些方法使用了淺拷貝呢?
Object.assign()
在MDN上介紹Object.assign():”Object.assign() 方法用於將所有可列舉的屬性的值從一個或多個源物件複製到目標物件。它將返回目標物件。”
複製一個物件
1 2 3 4 5 6 |
var target = {a: 1, b: 1}; var copy1 = {a: 2, b: 2, c: {ca: 21, cb: 22, cc: 23}}; var copy2 = {c: {ca: 31, cb: 32, cd: 34}}; var result = Object.assign(target, copy1, copy2); console.log(target); // {a: 2, b: 2, c: {ca: 31, cb: 32, cc: 33}} console.log(target === result); // true |
可以看到,Object.assign()拷貝的只是屬性值,假如源物件的屬性值是一個指向物件的引用,它也只拷貝那個引用值。所以Object.assign()只能用於淺拷貝或是合併物件。這是Object.assign()值得注意的地方。
深拷貝
那麼下面我們就來說說複雜的深拷貝。
jQuery.extend()
說到深拷貝,第一想到的就是jQuery.extend()方法,下面我們簡單看下jQuery.extend()的使用。
jQuery.extend( [deep ], target, object1 [, objectN ] ),其中deep為Boolean型別,如果是true,則進行深拷貝。
我們還是用上面的資料來看下extend()方法。
1 2 3 4 5 |
var target = {a: 1, b: 1}; var copy1 = {a: 2, b: 2, c: {ca: 21, cb: 22, cc: 23}}; var copy2 = {c: {ca: 31, cb: 32, cd: 34}}; var result = $.extend(true, target, copy1, copy2); // 進行深拷貝 console.log(target); // {a: 2, b: 2, c: {ca: 31, cb: 32, cc: 23, cd: 34}} |
1 2 3 4 5 |
var target = {a: 1, b: 1}; var copy1 = {a: 2, b: 2, c: {ca: 21, cb: 22, cc: 23}}; var copy2 = {c: {ca: 31, cb: 32, cd: 34}}; var result = $.extend(target, copy1, copy2); // 不進行深拷貝 console.log(target); // {a: 1, b: 1, c: {ca: 31, cb: 32, cd:34}} |
通過上面的對比可以看出,當使用extend()進行深拷貝的時候,物件的所有屬性都新增到target中了。
我們知道了extend()可以進行深拷貝,那麼extend()是如何實現深拷貝的呢?
先來看下jQuery.extend()原始碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
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; // Skip the boolean and the target target = arguments[ i ] || {}; i++; } // 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 ( i === length ) { 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 = Array.isArray( copy ) ) ) ) { if ( copyIsArray ) { copyIsArray = false; clone = src && Array.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; }; |
主要看下關於深拷貝的部分,取第一個引數,如果是boolean型別的,就賦值給deep,下面如果deep為true(也就是進行深拷貝),就遞迴呼叫extend(),這樣就將物件的所有屬性都新增到了target中實現了深拷貝。
JSON.parse()和JSON.stringify()
上面的jQuery原始碼是否讓你眼花繚亂?有沒有什麼辦法無腦實現深拷貝呢?JSON.parse()和JSON.stringify()給了我們一個基本的解決辦法。
1 2 3 4 5 6 7 |
var target = {a: 1, b: 1, c: {ca: 11, cb: 12, cc: 13}}; var targetCopy = JSON.parse(JSON.stringify(target)); targetCopy.a = 2; targetCopy.c.ca = 21; console.log(target); // {a: 1, b: 1, c: {ca: 11, cb: 12, cc: 13}} console.log(targetCopy); // {a: 2, b: 1, c: {ca: 21, cb: 12, cc: 13}} console.log(target === targetCopy); // false |
可以看到改變targetCopy並沒有改變原始的target,繼承的屬性也沒有丟失,因此實現了基本的深拷貝。
但是用JSON.parse()和JSON.stringify()會有一個問題。
JSON.parse()和JSON.stringify()能正確處理的物件只有Number、String、Array等能夠被json表示的資料結構,因此函式這種不能被json表示的型別將不能被正確處理。
1 2 3 4 5 6 7 8 9 |
var target = { a: 1, b: 2, hello: function() { console.log("Hello, world!"); } }; var copy = JSON.parse(JSON.stringify(target)); console.log(copy); // {a: 1, b: 2} |
上面的例子可以看出,hello這個屬性由於是函式型別,使用JSON.parse()和JSON.stringify()後丟失了。
因此JSON.parse()和JSON.stringify()還是需要謹慎使用。
下篇文章我會繼續為大家說明深拷貝的各種實現。
未完待續……