標籤:空間 多個 options 串連數 script length def loop 結構
拷貝,在js中,分為淺拷貝和深拷貝。這兩者是如何區分的呢?又是如何?的呢?
深淺拷貝的區分
首先說下,在js中,分為基礎資料類型和複雜資料類型,
基礎資料類型:number、string、boolean、undefined、null、sysmbol
複雜資料類型:Object、Array、Function、Date等
基礎資料類型值,儲存在棧(stack)中,拷貝的話,會重新在棧中開闢一個相同的空間儲存資料。而複雜資料類型,值儲存在堆(heap)中,棧中儲存對值的引用地址。深淺拷貝,只針對複雜資料類型來說的。
淺拷貝ShallowCopy,是一個對象的逐位副本。建立一個新對象,該對象具有原始對象中的完全重複項。如果對象的任何欄位是對其他對象的引用,則只複製引用地址,即只複製記憶體位址,而不複製對象本身,新舊對象還是共用同一塊堆記憶體。改變其中一個對象,另一個也會受影響。如果有修改,會失去未經處理資料。
深拷貝DeepCopy,複製出一個全新的對象執行個體,新對象跟原對象不共用記憶體,兩者操作互不影響。
簡單點區分,
淺拷貝拷貝引用;
深拷貝拷貝執行個體。
ShallowCopy淺拷貝的實現方式1. 賦值
先來說說,簡單的賦值情況,
var o1 = { a : 1, b : 2 }var o2 = o1console.log(o2 === o1) // trueo1.a = 2console.log(o1) // {a: 2, b: 2}console.log(o2) // {a: 2, b: 2}
賦值,這裡是對對象地址的引用,改變一個對象的值,拷貝的另一個對象的值也跟著變化,所以這是淺拷貝。
2. Array.concat()
concat方法用於合并兩個或多個數組。該方法不會更改現有的數組,而僅僅會返回被串連數組的一個副本。
var o1 = [1, [2], 3]var o2 = o1.concat([]) // 這裡會返回一個o1對象的淺拷貝對象console.log(o2) // [1, [2], 3]console.log(o1 === o2) // false
o2數組就是一個新數組。如果改變o1數組對象,會不會影響o2數組對象呢?
o1[0] = 11console.log(o1) // [11, [2], 3]console.log(o2) // [1, [2], 3]
以上這種情況,沒有改變o2數組值。這是因為,o2中第一個元素和o1中的第一個元素,不是同一個記憶體位址。
o1[1][0] = 22console.log(o1) // [11, [22], 3]console.log(o2) // [1, [22], 3]
而修改o1變數中的引用的值,o2數組值也跟隨著變化。這說明,o2中第二個元素和o1中的第二個元素引用相同的記憶體位址。
根據以上的說明,可以得出結論,如果數組是一維數組,則可以算是深拷貝。如果是多維陣列,則是淺拷貝。
3. Array.slice()
slice方法可從已有的數組中返回選定的元素。
var o1 = [1, [2], 3]var o2 = o1.slice(0)console.log(o1) // [1, [2], 3]console.log(o2) // [1, [2], 3]
該方法不會修改數組,而是返回一個子數組。
o1[0] = 11console.log(o1) // [11, [2], 3]console.log(o2) // [1, [2], 3]
從結果看出,只修改了o1的值,o2的值沒有修改。
o1[1][0] = 22console.log(o1) // [11, [22], 3]console.log(o2) // [1, [22], 3]
從結果看出,o1、o2兩個變數的值,都發生了變化。說明,兩者引用指向同一個記憶體位址。
以上,說明是淺拷貝。
4. Object.assign()
Object.assign()方法用於將所有可枚舉的自有屬性的值從一個或多個來源物件複製到目標對象。 它將返回目標對象
var o1 = { a : 1, b : { c : 2, d : 3} }var o2 = Object.assign({}, o1)console.log(o1) // { a : 1, b : { c : 2, d : 3} }console.log(o2) // { a : 1, b : { c : 2, d : 3} }console.log(o2 === o1) // false 說明實現了淺拷貝o1.a = 11console.log(o2) // { a : 1, b : { c : 2, d : 3} } o1和o2內部包含的基本類型值,拷貝的是其執行個體,不會相互影響o1.b.c = 22console.log(o1) // { a : 11, b : { c : 22, d : 3} }console.log(o2) // { a : 1, b : { c : 22, d : 3} } o1和o2內部包含的參考型別值,拷貝的是其引用,會相互影響
5. 使用jQuery中的extend函數
// Shallow copyjQuery.extend({},OriginalObject)// Deep copy jQuery.extend(true, {},OriginalObject)
jQuery.extend( [deep ], target, object1 [, objectN ] ),其中deep為Boolean類型,如果是true,則進行深拷貝。
var $ = require('jquery')var o1 = { a : 1, b : { c : 2 } }var o2 = $.extend({}, o1)console.log(o1.b === o2.b) // trueconsole.log(o1.a === o1.a) // false
6. lodash中的 _.clone()
利用結構化拷貝演算法。支援拷貝arrays,array buffers,booleans, data objects, maps,
numbers, Object objects, regexes, sets, strings, symbols, and typed
arrays. arguments對象的可枚舉屬性被拷貝為普通對象。
為不可拷貝的值(如錯誤對象、函數、DOM節點和弱映射)返回一個Null 物件。
淺拷貝: _.clone()
深拷貝:_.cloneDeep()
var objects = [{ 'a': 1 }, { 'b': 2 }]; var shallow = _.clone(objects);console.log(shallow[0] === objects[0]); // trueobjects[0].a = 11console.log(shallow[0]) // { a : 11}
DeepCopy深拷貝的實現方式1. 手動複製
要實現拷貝出來的副本,不受原本影響,那麼可以這麼實現
var o1 = { a : 1, b : 2 }var o2 = { a : o1.a, b : o1.b }console.log(o2 === o1) // falseo1.a = 2console.log(o1) // {a: 2, b: 2}console.log(o2) // {a: 1, b: 2}
將每個引用對象都通過複製值來實現深拷貝。
2. JSON.parse(JSON.stringify(object_array))
var o1 = { a : 1, b : { c : 2} }var o2 = JSON.parse(JSON.stringify(o1))console.log(o1 === o2) // falseconsole.log(o1.b === o2.b) // falseo1.b.c = 22o1.a = 11console.log(o1) // { a : 11, b : { c : 22} }console.log(o2) // { a : 1, b : { c : 2} }
這種方式,只針對可以轉換為JSON對象的類型,比如Array,Object。如果遇到Function就不適用了。
3. 再次遇見jQuery.extend()方法
jQuery.extend( [deep ], target, object1 [, objectN ] ),其中deep為Boolean類型,如果是true,則進行深拷貝。
// jQuery.extend()源碼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 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 // 如果只傳遞一個參數,則擴充jQuery本身 if ( i === length ) { target = this; i--; } for ( ; i < length; i++ ) { // Only deal with non-null/undefined values // 只處理non-null/undefined的值 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;};
4. lodash中的_.cloneDeep()
利用第三方庫lodash,它的深拷貝函數cloneDeep(),這個函數還是比較靠譜的,大多數需求都能滿足。
var o1 = { a : 1, b : { c : 2} }var o2 = _.cloneDeep(o1)console.log(o1 === o2) // falseo1.a = 11o1.b.c = 22console.log(o1) // { a : 11, b : { c : 22} }console.log(o2) // { a : 1, b : { c : 2} }
5. 自己實現深拷貝
針對Array和Object兩種複雜類型,自己實現深拷貝。自己實現的深拷貝,對比jquery的。沒有考慮undefined和null值。
// 檢測資料類型的函數function typeString(obj) { var cons = Object.prototype.toString.call(obj).slice(8, -1) return (cons === 'Array' || cons === 'Object')}// 實現深度拷貝 Array/Objectfunction deepClone(oldObj) { if(typeTrue(oldObj)) { var newObj = oldObj.constructor() for(let i in oldObj) { if (oldObj.hasOwnProperty(i)) { newObj[i] = typeTrue(oldObj[i]) ? deepClone(oldObj[i]) : oldObj[i] } } return newObj; } else { return oldObj }}// 測試var o1 = [1, 2, [3, 4]]var o2 = deepClone(o1)console.log(o1 === o2) // falseo1[2][0] = 2018console.log(o2) // [1, 2, [3, 4]]console.log(o1) // [1, 2, [2018, 4]]
深淺拷貝總結
拷貝之後,是否會相互影響,是個重要的指標。以上討論的深淺拷貝,針對的範圍比較小,大部分只考慮了Object和Array類型,但是能在大多數場合使用。
Function、Data、RegExp等沒有考慮。如果需要考慮這些,可以針對特定的情況,來具體實現,比如lodash的_.clone,就是使用的結構化拷貝演算法,針對不同情況來拷貝的。
javascript中的淺拷貝ShallowCopy與深拷貝DeepCopy