不定期更新的源碼閱讀日常——lodash-2

  今天我們來讀lodash的深拷貝部分代碼。
  lodash的clonecloneDeep兩個方法分別對應淺拷貝深拷貝。兩個函數其實都依賴於baseClone方法,通過傳入一個標識來進行深拷貝或者淺拷貝的區分,我們來簡單看一下baseClone的入參。

/** Used to compose bitmasks for cloning. */
var CLONE_DEEP_FLAG = 1,
    CLONE_FLAT_FLAG = 2,
    CLONE_SYMBOLS_FLAG = 4;
  
function clone(value) {
  return baseClone(value, CLONE_SYMBOLS_FLAG);
}

function cloneDeep(value) {
  return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG);
}

/**
 * @param {*} value The value to clone.
 * @param {boolean} bitmask The bitmask flags.
 *  1 - Deep clone
 *  2 - Flatten inherited properties
 *  4 - Clone symbols
 * @param {Function} [customizer] The function to customize cloning.
 * @param {string} [key] The key of `value`.
 * @param {Object} [object] The parent object of `value`.
 * @param {Object} [stack] Tracks traversed objects and their clone counterparts.
 * @returns {*} Returns the cloned value.
 */
function baseClone(value, bitmask, customizer, key, object, stack) {
  // ... 暫時省略內容代碼
}

來看下函數baseClone的入參:

  1. value代表需要進行拷貝的對象。
  2. bitmask就是深淺拷貝的一個標識,我們可以看到clone傳入的是4,cloneDeep傳入的爲1 | 4爲5。
  3. customizer這個參數是爲cloneWith的api進行服務的,是自定義的一個clone函數,與深拷貝無關,這裏我們先忽略。
  4. key對應的爲當前拷貝對象的key,這是後續遞歸調用拷貝對象值的時候所用到的。
  5. object對應的爲value的所屬對象,也是後續遞歸所用到的。
  6. stack爲一個類似於Map數據結構的數組,它以一個二維數組的形式,保存了每次需要進行克隆的值和返回的值,即保存爲[value, result]的形式。具體的作用到函數代碼分析中去查看。

  baseClone的深拷貝部分邏輯總體思路很簡單,當判斷爲基本類型的時候,直接返回返回需要拷貝的對象。當爲引用類型的時候,根據爲數組或者對象,進行一個循環,遞歸使用baseClone繼續進行拷貝,直到基本的數據類型爲止。但這種思路會產生一個問題,當兩個對象循環引用的時候,代碼會無限循環下去,就如下面這種情況:

var a = {};
var b = { result: a };
a.result = b;
// a.result.result.result.result.result ....

  因此baseClone中使用stack維護了一個每次拷貝的value對象數組。每次拷貝值前,先判斷value是否已經存在過了,如果存在,不繼續進行遍歷拷貝,直接返回value的值。

  瞭解了深拷貝的總體思路和需要注意的點之後,我們來一點點看baseClone的代碼。首先是第一部分,標識的創建,和基本類型值的返回。

function baseClone(value, bitmask, customizer, key, object, stack) {
  var result,
      isDeep = bitmask & CLONE_DEEP_FLAG;
      isFlat = bitmask & CLONE_FLAT_FLAG,
      isFull = bitmask & CLONE_SYMBOLS_FLAG;
  if (!isObject(value)) {
    return value;
  }
  // ... 省略diamante
}

function isObject(value) {
  var type = typeof value;
  return value != null && (type == 'object' || type == 'function');
}

  這裏省略掉了源碼中關於customizer部分的邏輯影響,只留下深拷貝的邏輯。這裏初始化了一個result返回值,以及isDeep來標識當前函數是否是一個深拷貝的表示。這裏的CLONE_DEEP_FLAG我們在第一部分中得知值爲1。整數去& 1會根據整數爲奇偶,會得到0或者1的結果。奇數爲1,偶數爲0cloneDeep傳入的bitmask爲5,clone則爲4,這裏會根據對應的函數,生成一個0/1的isDeep的標識。接着通過isObject函數進行判斷,如果不是引用類型的值,直接返回。

  接下來就是根據數據類型進行對應的拷貝初始化。

function baseClone(value, bitmask, customizer, key, object, stack) {
  // ... 省略的第一部分邏輯
  var isArr = isArray(value);
  if (isArr) {
    // 數組進行數組拷貝初始化
    result = initCloneArray(value);
  } else {
    // 得到數據類型標識 如 undefined 就是 undefinedTag
    var tag = getTag(value),
        isFunc = tag == funcTag || tag == genTag;
    // isBuffer 類型直接cloneBuffer返回值
    if (isBuffer(value)) {
      return cloneBuffer(value, isDeep);
    }
    // 對象或者類數組對象(args/arrayLike),在這裏通過initCloneObject初始化克隆值
    if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
      result = (isFlat || isFunc) ? {} : initCloneObject(value);
    } else {
      // 如果不是能夠進行拷貝的數據類型,根據是否存在父對象,直接返回value或者空對象
      if (!cloneableTags[tag]) {
        return object ? value : {};
      }
      // 其他類型,如Set/Map根據tag去對應的初始化
      result = initCloneByTag(value, tag, isDeep);
    }
  }
  // ... 省略的下半部分邏輯
}

  這裏邏輯根據數據類型做了一個初始化值的操作。數組使用了initCloneArray,對象(包括類數組對象),使用了initCloneObjectisBuffer則通過cloneBuffer直接返回了結果。這裏的兩個初始化值其實是做了一些特殊邊界的兼容(如initCloneArray做了對RegExp#exec特殊值的處理)。這裏可以簡單的理解爲,initCloneArray就是創建了一個空數組,initCloneObject就是創建了一個空對象。

  接着便是對stack的操作,來解決循環引用無限遞歸的問題。

function baseClone(value, bitmask, customizer, key, object, stack) {
  // ... 省略的1,2部分邏輯
  // Check for circular references and return its corresponding clone.
  stack || (stack = new Stack);
  var stacked = stack.get(value);
  if (stacked) {
    return stacked;
  }
  stack.set(value, result);
  // ... 省略的下部分邏輯
}

  前面提到了,Stack是一個類似於Map的二維數組結構:[[value1, result1], [value2, result2]]。這裏當stack不存在的時候,會初始化一個Stack。然後查看是否存在當前valueresult了。如果存在,那麼直接返回result。否則就註冊一個[result, value]進入Stack

function baseClone(value, bitmask, customizer, key, object, stack) {
  // 省略上面部分代碼
  if (isSet(value)) {
    value.forEach(function(subValue) {
      result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack));
    });
    return result;
  }

  if (isMap(value)) {
    value.forEach(function(subValue, key) {
      result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack));
    });
    return result;
  }

  var keysFunc = isFull
    // 處理特殊對象(是否得到繼承的keys),把對象的keys生成一個數組
    // getAllKeysIn -> Creates an array of own and inherited enumerable property names and
    // getAllKeys -> Creates an array of own enumerable property names and symbols of `object`.
    ? (isFlat ? getAllKeysIn : getAllKeys)
    : (isFlat ? keysIn : keys);
  var props = isArr ? undefined : keysFunc(value);
  arrayEach(props || value, function(subValue, key) {
    if (props) {
      key = subValue;
      subValue = value[key];
    }
    // Recursively populate clone (susceptible to call stack limits).
    assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack));
  });
  return result;
}

  最後,根據set/map/object/array去轉換對應的數組循環格式,去循環遞歸的調用baseClone,直到到基本類型的值爲止,完成深拷貝的賦值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章