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上去。