在介紹javascript中的對象的拷貝以前,我先介紹一個基礎的東西,javascript中的數據類型。javascript
咱們作前端的應該都知到在es6 以前,javascript中的數據類型Boolean
、 Number
、 String
、 Undefined
、Object
、Null
,後來在es6 中又引入了一種新的數據類型爲:Symbol
。而這些數據類型又被分爲基本數據類型和引用數據類型,基本數據類型存儲在棧中;引用數據類型存儲在堆中。其中基本數據類型包括有:Boolean
、Number
、String
、Undefined
、Null
以及剛剛提到的新的數據類型Symbol
,引用類型包括:Object
、Function
、Array
。我爲甚麼會提到Function
和Array
主要是由於我麼在對象的深拷貝過程當中須要對這兩種數據類型進行特殊處理。前端
介紹完了js中的數據類型以後,來看一下拷貝,什麼是對象的拷貝,說白了就是複製,將原來的東西給複製一份。就好比說,將磁盤上的一個文件給拷貝一份,就是將磁盤中的文件給複製了一個如出一轍的新的文件。就好比說下面的例子;java
var a = 123; var b = a; var c = {name: 'zhangsan', age: 18}; var d = c; var e = {}; for (var key in c) { if (c.hasOwnProperty(key)) { e[key] = c[key]; } }
對象的拷貝又被分爲深拷貝和淺拷貝。es6
就上面的代碼來解釋,咱們看最後一個拷貝,就是咱們將變量c
中全部的屬性而後賦值給變量e
,這種狀況不出問題的前提就是咱們定義的對象a
中的全部的成員都爲基本類型,而非引用類型,一旦存在有引用類型的成員,這個時候的拷貝是將成員變量的地址賦給拷貝過去了,而,成員變量地址指向的真實的引用依舊是同一引用,所以,當指向中的引用內容發生變化時,一樣會兩個對象中的成員也會發生一樣的改變;函數
好比說下面的這個例子:oop
var a = { name: 'zhangsan', age: 28, children: [1,2,3,4,5], son: { name: 'zhangsi', age: 1 } } var shallowCopy = function (obj) { var newObj = {}; for (var key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = obj[key]; } } return newObj; } var b = shallowCopy(a); console.log(a.children[0]); console.log(a.son.name); console.log(b.children[0]); console.log(b.son.name); a.children[0] = 22; a.son.name = 'name'; console.log(b.children[0]); console.log(b.son.name);
在上面的這個例子中,我在後面改變了a
的children
以及a.som.name
,咱們會發現這個狀況下面,b
相對應的內容也發生了變化。並未發生實際上的拷貝,這就是淺拷貝。優化
在瞭解了淺拷貝的基礎上,咱們再來深刻的瞭解一下深拷貝,有些時候咱們是須要進行深拷貝的,這個時候複製的就不單單是一個引用地址,而是一個真實的引用內容。基於這個理論,就能夠在複製的過程當中加入一個判斷,判斷所要複製的量是引用類型仍是基本類型,若是是基本類型的話,就直接賦值過去,若是是引用類型的話,就須要繼續對引用類型進行拷貝。this
所以咱們將對上面淺拷貝的代碼進修改以下:spa
var a = { name: 'zhangsan', age: 28, children: [1,2,3,4,5], son: { name: 'zhangsi', age: 1 } } var deepCopy = function(obj) { if (typeof obj !== 'object') return; var newObj = obj instanceof Array ? [] : {}; for (var key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key]; } } return newObj; } var b = deepCopy(a); console.log(a.children[0]); console.log(a.son.name); console.log(b.children[0]); console.log(b.son.name); a.children[0] = 22; a.son.name = 'name'; console.log(b.children[0]); console.log(b.son.name);
咱們可以看到,最後的輸出和淺拷貝的輸出是不同的,輸出的依舊是以前的以前的值,而不是發生變化的值。固然上面的這個深拷貝的例子還僅僅制止一個基礎的,還不夠完善,僅僅可以完成基本的對象拷貝。具體的實現咱們能夠參考的還有不少。prototype
jQuery 中的實現
在jQuery中,深淺拷貝是使用的同一個方法就是extend
,這個函數的第一個參數是表示此次的拷貝是深拷貝仍是淺拷貝,若是要進行深拷貝的話,則傳入true
,不然不傳或者是傳false
。具體的實現內容以下:
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" && typeof target !== "function" ) { 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 ) { copy = options[ name ]; // Prevent Object.prototype pollution // Prevent never-ending loop if ( name === "__proto__" || target === copy ) { continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject( copy ) || ( copyIsArray = Array.isArray( copy ) ) ) ) { src = target[ name ]; // Ensure proper type for the source value if ( copyIsArray && !Array.isArray( src ) ) { clone = []; } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { clone = {}; } else { clone = src; } copyIsArray = false; // 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; };
具體的解析變就不作過於深刻的介紹,這裏的思路其實就是咱們上面所介紹的思路的一個優化,將各類狀況都幫咱們給考慮清楚了。