解讀 React 的 pooledClass.js

前言

在學習 React 事件系統的時候,在事件分發的 dispatch方法發現了調用了一個 pooledClass 方法,一時半會沒看明白這個方法的用意。數據庫

咱們先看一下是怎麼用的:segmentfault

// step1
function TopLevelCallbackBookKeeping(topLevelType, nativeEvent) {
  this.topLevelType = topLevelType;
  this.nativeEvent = nativeEvent;
  this.ancestors = [];
}
Object.assign(TopLevelCallbackBookKeeping.prototype, {
  destructor: function() {
    this.topLevelType = null;
    this.nativeEvent = null;
    this.ancestors.length = 0;
  },
});
PooledClass.addPoolingTo(
  TopLevelCallbackBookKeeping,
  PooledClass.twoArgumentPooler
);

// step2
var bookKeeping = TopLevelCallbackBookKeeping.getPooled(
  topLevelType,
  nativeEvent
);
// bookKeeping 是 TopLevelCallbackBookKeeping 的實例
try {
  // Event queue being processed in the same cycle allows
  // `preventDefault`.
  ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
} finally {
  //釋放
  TopLevelCallbackBookKeeping.release(bookKeeping);
}

那麼這裏爲何不直接 new 一個 TopLevelCallbackBookKeeping, 而要經過這個 PooledClass 來返回 TopLevelCallbackBookKeeping 的實例呢緩存

對象池

單例模式是限制了一個類只能有一個實例,對象池模式則是限制一個類實例的個數。對象池類就像是一個對象管理員,它以Static列表(也就是裝對象的池子)的形式存存儲某個實例數受限的類的實例,每個實例還要加一個標記,標記該實例是否被佔用。當類初始化的時候,這個對象池就被初始化了,實例就被建立出來。而後,用戶能夠向這個類索取實例,若是池中全部的實例都已經被佔用了,那麼拋出異常。用戶用完之後,還要把實例「還」回來,即釋放佔用。對象池類的成員應該都是靜態的。用戶也不該該能訪問池子裏裝着的對象的構造函數,以防用戶繞開對象池建立實例。書上說這個模式會用在數據庫鏈接的管理上。好比,每一個用戶的鏈接數是有限的,這樣每一個鏈接就是一個池子裏的一個對象,「鏈接池」類就能夠控制鏈接數了。
若是說每次觸發 dispatch 的時候都用 new TopLevelCallbackBookKeeping 來 new 一個對象,那麼當觸發不少次 dispatch 的時候,就會致使生成多個對象沒法銷燬(多個bookKeeping的引用次數一直爲1),致使內存溢出。

對象池技術的基本原理

對象池技術基本原理的核心有兩點:緩存和共享,即對於那些被頻繁使用的對象,在使用完後,不當即將它們釋放,而是將它們緩存起來,以供後續的應用程序重複使用,從而減小建立對象和釋放對象的次數,進而改善應用程序的性能。事實上,因爲對象池技術將對象限制在必定的數量,也有效地減小了應用程序內存上的開銷。函數

對象池使用的基本思路是

將用過的對象保存起來,等下一次須要這種對象的時候,再拿出來重複使用,從而在必定程度上減小頻繁建立對象所形成的開銷。React 的 pooledClass.js 就是一個例子:性能

var invariant = require('invariant');

/**
 * Static poolers. Several custom versions for each potential number of
 * arguments. A completely generic pooler is easy to implement, but would
 * require accessing the `arguments` object. In each of these, `this` refers to
 * the Class itself, not an instance. If any others are needed, simply add them
 * here, or in their own files.
 */
var oneArgumentPooler = function(copyFieldsFrom) {
  var Klass = this;
  if (Klass.instancePool.length) {
    var instance = Klass.instancePool.pop();
    Klass.call(instance, copyFieldsFrom);
    return instance;
  } else {
    return new Klass(copyFieldsFrom);
  }
};

...
var standardReleaser = function(instance) {
  var Klass = this;
  invariant(
    instance instanceof Klass,
    'Trying to release an instance into a pool of a different type.'
  );
  instance.destructor();
  if (Klass.instancePool.length < Klass.poolSize) {
    Klass.instancePool.push(instance);
  }
};

var DEFAULT_POOL_SIZE = 10;
var DEFAULT_POOLER = oneArgumentPooler;

/**
 * Augments `CopyConstructor` to be a poolable class, augmenting only the class
 * itself (statically) not adding any prototypical fields. Any CopyConstructor
 * you give this may have a `poolSize` property, and will look for a
 * prototypical `destructor` on instances (optional).
 *
 * @param {Function} CopyConstructor Constructor that can be used to reset.
 * @param {Function} pooler Customizable pooler.
 */
var addPoolingTo = function(CopyConstructor, pooler) {
  var NewKlass = CopyConstructor;
  NewKlass.instancePool = [];
  NewKlass.getPooled = pooler || DEFAULT_POOLER;
  if (!NewKlass.poolSize) {
    NewKlass.poolSize = DEFAULT_POOL_SIZE;
  }
  NewKlass.release = standardReleaser;
  return NewKlass;
};

var PooledClass = {
  addPoolingTo: addPoolingTo,
  oneArgumentPooler: oneArgumentPooler,
  twoArgumentPooler: twoArgumentPooler,
  threeArgumentPooler: threeArgumentPooler,
  fourArgumentPooler: fourArgumentPooler,
  fiveArgumentPooler: fiveArgumentPooler,
};

module.exports = PooledClass;

具體分爲三步學習

  1. addPoolingTo 添加對象到池子
  2. 調用的時候發現是否有緩存,有緩存就pop()出來用, 沒有緩存就新增一個
  3. 使用完成以後,釋放對象,緩存進去

說的再簡單一點就是ui

  • 建立對象 addPoolingTo()
  • 借取對象 getPooled()
  • 歸還對象 release()

總結

並不是全部對象都適合拿來池化――由於維護對象池也要形成必定開銷。對生成時開銷不大的對象進行池化,反而可能會出現「維護對象池的開銷」大於「生成新對象的開銷」,從而使性能下降的狀況。可是對於生成時開銷可觀的對象,池化技術就是提升性能的有效策略了。this

相關文章
相關標籤/搜索