clone相关的方法有4个,分别如下。
- clone
- cloneDeep
- cloneDeepWith
- cloneWith
clone是浅拷贝(shallow copy),cloneDeep是深拷贝(deep copy)。
在js中,数组或者对象这类引用类型的值,比如我有一个数组a,当我把数组a等于b的情况下,b的副本保存的是一个指向a的指针。改变其中一a或者b的值,另一个也会受到影响。
开始吧
function clone(value) {
return baseClone(value, false, true);
}
毫无例外的,在clone等方法之上,有一层更抽象的baseClone。当下我们只是为了分析clone,2,3参数已经确定传入false和true,简化一下相关的代码。
在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
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的空数组。
接下来通过返回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中。接下来,我们要考虑当传入的参数是一个对象。
传入对象时简化后的baseClone =》 {a:1,b:2}
在看具体的代码之前,我们要提前认识下getTag 和相应的Tag代表哪些内置类型。
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是原型对象
- 调用baseCreate以Object.prototype为模板创建一个新的对象
- 返回这个对象
- 如果不是,返回一个{}.
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上去。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。