简单说一下_.clone(一)

clone相关的方法有4个,分别以下。javascript

  • clone
  • cloneDeep
  • cloneDeepWith
  • cloneWith

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对数组和对象有两种处理方式。数组会调用initCloneArraypromise

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

传入对象时简化后的baseClone =》 {a:1,b:2}

在看具体的代码以前,咱们要提早认识下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是原型对象

    • 调用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上去。

相关文章
相关标签/搜索