clone相關的方法有4個,分別以下。javascript
clone是淺拷貝(shallow copy),cloneDeep是深拷貝(deep copy)。java
在js中,數組或者對象這類引用類型的值,好比我有一個數組a,當我把數組a等於b的狀況下,b的副本保存的是一個指向a的指針。改變其中一a或者b的值,另外一個也會受到影響。node
function clone(value) { return baseClone(value, false, true); }
毫無例外的,在clone等方法之上,有一層更抽象的baseClone。當下咱們只是爲了分析clone,2,3參數已經肯定傳入false和true,簡化一下相關的代碼。segmentfault
在isDeep爲false,isFull爲true狀況下下考慮傳入value數組
function baseClone(value, isDeep = false, isFull= true, customizer, key, object, stack) { var result; if (!isObject(value)) {// 不是對象,默認就直接返回了。 return value; } var isArr = isArray(value); // 判斷是數組 if (isArr) { result = initCloneArray(value); if (!isDeep) { return copyArray(value, result); } }.... // }
baseClone對數組和對象有兩種處理方式。數組會調用initCloneArray
promise
function initCloneArray(array) { var length = array.length, result = array.constructor(length); // Add properties assigned by `RegExp#exec`. 下邊這幾行一臉懵逼。。 if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) { result.index = array.index; result.input = array.input; } return result; }
initCloneArray初始化一個數組克隆。若是你傳入的是一個長度爲4的數組。返回的是一個長度爲4的空數組。prototype
接下來經過返回copyArray(value, result)
的結果。不出所料的話,copyArray是將value中的元素copy到result中,並返回result。指針
function copyArray(source, array) { var index = -1, length = source.length; array || (array = Array(length)); while (++index < length) { array[index] = source[index]; } return array; }
經過遍歷,將source中的每一個元素複製到array中。接下來,咱們要考慮當傳入的參數是一個對象。code
在看具體的代碼以前,咱們要提早認識下getTag 和相應的Tag表明哪些內置類型。regexp
var getTag = val => Object.prototype.toString(val)
getTag根據不一樣的輸入返回的內容是不一樣的,經常使用來判斷具體的類型
/** `Object#toString` result references. */ var argsTag = '[object Arguments]', arrayTag = '[object Array]', boolTag = '[object Boolean]', dateTag = '[object Date]', errorTag = '[object Error]', funcTag = '[object Function]', genTag = '[object GeneratorFunction]', mapTag = '[object Map]', numberTag = '[object Number]', objectTag = '[object Object]', promiseTag = '[object Promise]', regexpTag = '[object RegExp]', setTag = '[object Set]', stringTag = '[object String]', symbolTag = '[object Symbol]', weakMapTag = '[object WeakMap]', weakSetTag = '[object WeakSet]';
瞭解瞭如上的各類Tag,咱們在去看簡化的baseClone。
注意:Buffer是node環境下的,在這裏暫時不討論,先移除掉相關代碼。可是不要忘了,lodash在node下一樣強大
function baseClone(value, isDeep, isFull, customizer, key, object, stack) { var result; var tag = getTag(value), isFunc = tag == funcTag || tag == genTag; if (tag == objectTag || tag == argsTag || (isFunc && !object)) { result = initCloneObject(isFunc ? {} : value); if (!isDeep) { return copySymbols(value, baseAssign(result, value)); // 走到這就返回了 } } else { if (!cloneableTags[tag]) { return object ? value : {}; } result = initCloneByTag(value, tag, baseClone, isDeep); } }
initCloneObject初始化一個克隆對象。
function initCloneObject(object) { return (typeof object.constructor == 'function' && !isPrototype(object)) ? baseCreate(getPrototype(object)) : {}; }
isPrototype:判斷是不是一個原型對象。(也就是說,傳入的不是Object.prototype這種類型的對象)
此時,代碼邏輯會調用的是baseCreate(getPrototype(object))
.
getPrototype = overArg(Object.getPrototypeOf, Object), // transform用來改變arg的類型,在getPrototype中傳入的Object,將getPrototype的參數類型強制轉換爲對象。從而確保Object.getPrototype的可執行性。 function overArg(func, transform) { return function(arg) { return func(transform(arg)); }; }
Object.getPrototypeOf: 返回對象的原型。
在initCloneObject
,getPrototype(object),返回的是object的原型。baseCreate更簡單了。
function baseCreate(proto) { // isObject判斷是否爲對象,objectCreate是Object.create方法的引用。 return isObject(proto) ? objectCreate(proto) : {}; }
綜上,咱們總結下initCloneObject作了什麼。
判斷參數object是原型對象
baseClone接下來走到的邏輯是
// 咱們前置的默認條件isDeep爲false,程序走到return就中止了。 if (!isDeep) { return copySymbols(value, baseAssign(result, value)); // 走到這就返回了 }
這裏涉及到的到的兩個方法是copySymbol
和baseAssign
/** * Copies own symbol properties of `source` to `object`. * symbol表示獨一無二的值,symbol properties即是獨一無二的屬性。 * * @private * @param {Object} source The object to copy symbols from. 來源 * @param {Object} [object={}] The object to copy symbols to. copy進的對象 * @returns {Object} Returns `object`. */ function copySymbols(source, object) { return copyObject(source, getSymbols(source), object); }
baseAssign能夠查看這一篇,copy也在這一章內。。
copyObject的第二個參數,接收的是一個數組,數組裏的元素爲咱們指望copy屬性s,getSymbols
顯然作的就是獲取source上全部的Symbol
屬性。
看一下
/** * Creates an array of the own enumerable symbol properties of `object`. * 以數組形式返回自身可遍歷symbol屬性。 * @private * @param {Object} object The object to query. * @returns {Array} Returns the array of symbols. */ var nativeGetSymbols = Object.getOwnPropertySymbols, var getSymbols = nativeGetSymbols ? overArg(nativeGetSymbols, Object) : stubArray;
你可能會想 copyObject(source, getSymbols(source), object)
爲什麼要getSymbols(source)執行一次,那是由於`
baseAssign(result, value)這一句執行的時候,baseAssign的第二個參數,實際上執行的是
kyes(value),是獲取不到
Symbols`屬性的。
實際上
if (!isDeep) { return copySymbols(value, baseAssign(result, value)); // 走到這就返回了 } // 能夠理解爲以下 if (!isDeep) { result = baseAssign(result, value); copyObject(result, getSymbols(value), value); }
解釋下上邊,若是value是有Symbol屬性的,第一步執行出的result是沒法Assigin上去的,因此有了第二步copyObject(result, getSymbols(value), value)
。先判斷value上是否有symbol屬性,若是有,就copy上去。