解读 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

相关文章
相关标签/搜索