簡單說一下_.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上去。

相關文章
相關標籤/搜索