js中的數據類型
在介紹javascript中的對象的拷貝之前,我先介紹一個基礎的東西,javascript中的數據類型。
我們做前端的應該都知到在es6 之前,javascript中的數據類型Boolean
、 Number
、 String
、 Undefined
、Object
、Null
,后來在es6 中又引入了一種新的數據類型為:Symbol
。而這些數據類型又被分為基本數據類型和引用數據類型,基本數據類型存儲在棧中;引用數據類型存儲在堆中。其中基本數據類型包括有:Boolean
、Number
、String
、Undefined
、Null
以及剛剛提到的新的數據類型Symbol
,引用類型包括:Object
、Function
、Array
。我為甚么會提到Function
和Array
主要是因為我么在對象的深拷貝過程中需要對這兩種數據類型進行特殊處理。
什么是對象的拷貝
介紹完了js中的數據類型之后,來看一下拷貝,什么是對象的拷貝,說白了就是復制,將原來的東西給復制一份。就比如說,將磁盤上的一個文件給拷貝一份,就是將磁盤中的文件給復制了一個一模一樣的新的文件。就比如說下面的例子;
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];
}
}
對象的拷貝又被分為深拷貝和淺拷貝。
淺拷貝
就上面的代碼來解釋,我們看最后一個拷貝,就是我們將變量c
中所有的屬性然后賦值給變量e
,這種情況不出問題的前提就是我們定義的對象a
中的所有的成員都為基本類型,而非引用類型,一旦存在有引用類型的成員,這個時候的拷貝是將成員變量的地址賦給拷貝過去了,而,成員變量地址指向的真實的引用依舊是同一引用,因此,當指向中的引用內容發生變化時,同樣會兩個對象中的成員也會發生同樣的改變;
比如說下面的這個例子:
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
相對應的內容也發生了變化。並未發生實際上的拷貝,這就是淺拷貝。
深拷貝
在了解了淺拷貝的基礎上,我們再來深入的了解一下深拷貝,有些時候我們是需要進行深拷貝的,這個時候復制的就不僅僅是一個引用地址,而是一個真實的引用內容。基於這個理論,就可以在復制的過程中加入一個判斷,判斷所要復制的量是引用類型還是基本類型,如果是基本類型的話,就直接賦值過去,如果是引用類型的話,就需要繼續對引用類型進行拷貝。
因此我們將對上面淺拷貝的代碼進修改如下:
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);
我們能夠看到,最后的輸出和淺拷貝的輸出是不一樣的,輸出的依舊是之前的之前的值,而不是發生變化的值。當然上面的這個深拷貝的例子還僅僅制止一個基礎的,還不夠完善,僅僅能夠完成基本的對象拷貝。具體的實現我們可以參考的還有很多。
實現深拷貝的常用方法
-
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; };
具體的解析變就不做過於深入的介紹,這里的思路其實就是我們上面所介紹的思路的一個優化,將各種情況都幫我們給考慮清楚了。