今天我們來讀lodash的深拷貝部分代碼。
lodash的clone
和cloneDeep
兩個方法分別對應淺拷貝
和深拷貝
。兩個函數其實都依賴於baseClone
方法,通過傳入一個標識
來進行深拷貝或者淺拷貝的區分,我們來簡單看一下baseClone
的入參。
/** Used to compose bitmasks for cloning. */
var CLONE_DEEP_FLAG = 1,
CLONE_FLAT_FLAG = 2,
CLONE_SYMBOLS_FLAG = 4;
function clone(value) {
return baseClone(value, CLONE_SYMBOLS_FLAG);
}
function cloneDeep(value) {
return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG);
}
/**
* @param {*} value The value to clone.
* @param {boolean} bitmask The bitmask flags.
* 1 - Deep clone
* 2 - Flatten inherited properties
* 4 - Clone symbols
* @param {Function} [customizer] The function to customize cloning.
* @param {string} [key] The key of `value`.
* @param {Object} [object] The parent object of `value`.
* @param {Object} [stack] Tracks traversed objects and their clone counterparts.
* @returns {*} Returns the cloned value.
*/
function baseClone(value, bitmask, customizer, key, object, stack) {
// ... 暫時省略內容代碼
}
來看下函數baseClone
的入參:
value
代表需要進行拷貝的對象。bitmask
就是深淺拷貝的一個標識,我們可以看到clone
傳入的是4,cloneDeep
傳入的爲1 | 4
爲5。customizer
這個參數是爲cloneWith
的api進行服務的,是自定義的一個clone
函數,與深拷貝無關,這裏我們先忽略。key
對應的爲當前拷貝對象的key
,這是後續遞歸調用拷貝對象值的時候所用到的。object
對應的爲value
的所屬對象,也是後續遞歸所用到的。stack
爲一個類似於Map數據結構的數組,它以一個二維數組的形式,保存了每次需要進行克隆的值和返回的值,即保存爲[value, result]
的形式。具體的作用到函數代碼分析中去查看。
baseClone
的深拷貝部分邏輯總體思路很簡單,當判斷爲基本類型的時候,直接返回返回需要拷貝的對象。當爲引用類型的時候,根據爲數組或者對象,進行一個循環,遞歸使用baseClone
繼續進行拷貝,直到基本的數據類型爲止。但這種思路會產生一個問題,當兩個對象循環引用的時候,代碼會無限循環下去,就如下面這種情況:
var a = {};
var b = { result: a };
a.result = b;
// a.result.result.result.result.result ....
因此baseClone
中使用stack
維護了一個每次拷貝的value
對象數組。每次拷貝值前,先判斷value
是否已經存在過了,如果存在,不繼續進行遍歷拷貝,直接返回value
的值。
瞭解了深拷貝的總體思路和需要注意的點之後,我們來一點點看baseClone
的代碼。首先是第一部分,標識的創建,和基本類型值的返回。
function baseClone(value, bitmask, customizer, key, object, stack) {
var result,
isDeep = bitmask & CLONE_DEEP_FLAG;
isFlat = bitmask & CLONE_FLAT_FLAG,
isFull = bitmask & CLONE_SYMBOLS_FLAG;
if (!isObject(value)) {
return value;
}
// ... 省略diamante
}
function isObject(value) {
var type = typeof value;
return value != null && (type == 'object' || type == 'function');
}
這裏省略掉了源碼中關於customizer
部分的邏輯影響,只留下深拷貝的邏輯。這裏初始化了一個result
返回值,以及isDeep
來標識當前函數是否是一個深拷貝的表示。這裏的CLONE_DEEP_FLAG
我們在第一部分中得知值爲1
。整數去& 1
會根據整數爲奇偶,會得到0
或者1
的結果。奇數爲1
,偶數爲0
。cloneDeep
傳入的bitmask
爲5,clone
則爲4,這裏會根據對應的函數,生成一個0/1的isDeep
的標識。接着通過isObject
函數進行判斷,如果不是引用類型的值,直接返回。
接下來就是根據數據類型進行對應的拷貝初始化。
function baseClone(value, bitmask, customizer, key, object, stack) {
// ... 省略的第一部分邏輯
var isArr = isArray(value);
if (isArr) {
// 數組進行數組拷貝初始化
result = initCloneArray(value);
} else {
// 得到數據類型標識 如 undefined 就是 undefinedTag
var tag = getTag(value),
isFunc = tag == funcTag || tag == genTag;
// isBuffer 類型直接cloneBuffer返回值
if (isBuffer(value)) {
return cloneBuffer(value, isDeep);
}
// 對象或者類數組對象(args/arrayLike),在這裏通過initCloneObject初始化克隆值
if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
result = (isFlat || isFunc) ? {} : initCloneObject(value);
} else {
// 如果不是能夠進行拷貝的數據類型,根據是否存在父對象,直接返回value或者空對象
if (!cloneableTags[tag]) {
return object ? value : {};
}
// 其他類型,如Set/Map根據tag去對應的初始化
result = initCloneByTag(value, tag, isDeep);
}
}
// ... 省略的下半部分邏輯
}
這裏邏輯根據數據類型做了一個初始化值的操作。數組使用了initCloneArray
,對象(包括類數組對象),使用了initCloneObject
,isBuffer
則通過cloneBuffer
直接返回了結果。這裏的兩個初始化值其實是做了一些特殊邊界的兼容(如initCloneArray
做了對RegExp#exec
特殊值的處理)。這裏可以簡單的理解爲,initCloneArray
就是創建了一個空數組,initCloneObject
就是創建了一個空對象。
接着便是對stack
的操作,來解決循環引用無限遞歸的問題。
function baseClone(value, bitmask, customizer, key, object, stack) {
// ... 省略的1,2部分邏輯
// Check for circular references and return its corresponding clone.
stack || (stack = new Stack);
var stacked = stack.get(value);
if (stacked) {
return stacked;
}
stack.set(value, result);
// ... 省略的下部分邏輯
}
前面提到了,Stack是一個類似於Map的二維數組結構:[[value1, result1], [value2, result2]]
。這裏當stack
不存在的時候,會初始化一個Stack
。然後查看是否存在當前value
的result
了。如果存在,那麼直接返回result
。否則就註冊一個[result, value]
進入Stack
。
function baseClone(value, bitmask, customizer, key, object, stack) {
// 省略上面部分代碼
if (isSet(value)) {
value.forEach(function(subValue) {
result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack));
});
return result;
}
if (isMap(value)) {
value.forEach(function(subValue, key) {
result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack));
});
return result;
}
var keysFunc = isFull
// 處理特殊對象(是否得到繼承的keys),把對象的keys生成一個數組
// getAllKeysIn -> Creates an array of own and inherited enumerable property names and
// getAllKeys -> Creates an array of own enumerable property names and symbols of `object`.
? (isFlat ? getAllKeysIn : getAllKeys)
: (isFlat ? keysIn : keys);
var props = isArr ? undefined : keysFunc(value);
arrayEach(props || value, function(subValue, key) {
if (props) {
key = subValue;
subValue = value[key];
}
// Recursively populate clone (susceptible to call stack limits).
assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack));
});
return result;
}
最後,根據set/map/object/array
去轉換對應的數組循環格式,去循環遞歸的調用baseClone
,直到到基本類型的值爲止,完成深拷貝的賦值。