解读React的pooledClass.js_对象池技术的原理/思路

时间: 2018-09-20阅读: 2013标签: react

前言

在学习 react事件系统的时候,在事件分发的 dispatch方法发现了调用了一个 pooledClass 方法,一时半会没看明白这个方法的用意。

我们先看一下是怎么用的:

// 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. 使用完成之后,释放对象,缓存进去

说的再简单一点就是

  • 创建对象 addPoolingTo()
  • 借取对象 getPooled()
  • 归还对象 release()


总结

并非所有对象都适合拿来池化――因为维护对象池也要造成一定开销。对生成时开销不大的对象进行池化,反而可能会出现“维护对象池的开销”大于“生成新对象的开销”,从而使性能降低的情况。但是对于生成时开销可观的对象,池化技术就是提高性能的有效策略了。

来源:https://segmentfault.com/a/1190000016462336

站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

链接: http://www.fly63.com/article/detial/1100

Vue和React有什么区别与优劣?我到底应该选择哪谁?

前端开发框架从最开始的jquery时代,到后来backbone,angular1,再到现在vue和react两分天下,也才用了不到十年的光景。

不要过度使用React.useCallback()

我博客的一位读者在Facebook上联系到我,提出了一个有趣的问题。他说,他的队友不管在什么情况下,都会把每一个回调函数封装在 useCallback() 里面。

react 高阶组件的 理解和应用

react 高阶组件简单的理解是:一个包装了另一个基础组件的组件。高阶组件的两种形式:属性代理(Props Proxy)、反向继承 (Inheritance Inversion)

React Server Component 可能并没有那么香

前段时间 React 团队发布了一项用于解决 React 页面在多接口请求下的性能问题的解决方案 React Server Components。当然该方案目前还在草案阶段,官方也只是发了视频和一个示例 demo 来说明这个草案。

为什么我会选择React+Next.js,而不是Vue或Angular?

本文的目的不是要对 React、Vue 和 Angular 三者进行比较,已经有许多人对这个话题进行了比较深入的探讨。每个人都有自己的偏好。与其他库和框架相比,我更喜欢使用 React 构建用户界面。 对我来说,这是构建用户界面唯一正确的方法,它让我爱上了 React。

react中实现可拖动div

把拖动div功能用react封装成class,在页面直接引入该class即可使用。title为可拖动区域。panel为要实现拖动的容器。优化了拖动框超出页面范围的情况,也优化了拖动太快时鼠标超出可拖动区域的情况

React高级性能优化

当大家考虑在项目中使用 React 的时候,第一个问题往往是他们的应用的速度和响应是否能和非 React 版一样,每当状态改变的时候就重新渲染组件的整个子树,让大家怀疑这会不会对性能造成负面影响

为什么 React 源码不用 TypeScript 来写?

Facebook 是一家技术很厉害的公司,能够超前做一些外界没有的东西,但等外界把这个东西做出来了, Facebook 就发现自己迁移不过去了,被自己过去超前做的技术锁定了,因为迁移成本太高

函数式的React

React 是现在最流行的 JavaScript 库之一。使用 React 可以非常轻松地创建 Web 用户交互界面。 它的成功有很多因素,但也许其中一个因素是清晰有效的编程方法。在 React 的世界中,UI 是由一个一个组件所组成的。

React新Context API在前端状态管理的实践

众所周知,React的单向数据流模式导致状态只能一级一级的由父组件传递到子组件,在大中型应用中较为繁琐不好管理,通常我们需要使用Redux来帮助我们进行管理,然而随着React 16.3的发布,新context api成为了新的选择。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!