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)); // 走到这就返回了
  }

这里涉及到的到的两个方法是copySymbolbaseAssign

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


最普通的一个
301 声望41 粉丝

永远不要做你擅长的事。


引用和评论

0 条评论