react源码阅读笔记(4)Pool与CallbackQueue

本文使用的是react 15.6.1的代码

上篇介绍batchedUpdates时,最后flushBatchedUpdates方法中使用了PoolCallbackQueus中的方法,这次我们就详细介绍一下这两个js。

Pool

承接上文,先来看看当时是怎么使用的。

var flushBatchedUpdates = function() {
  while (dirtyComponents.length || asapEnqueued) {
    if (dirtyComponents.length) {
      // 从缓存池中获取ReactUpdatesFlushTransaction对象
      var transaction = ReactUpdatesFlushTransaction.getPooled();
      // 调用runBatchedUpdates
      transaction.perform(runBatchedUpdates, null, transaction);
      ReactUpdatesFlushTransaction.release(transaction);
    }

    if (asapEnqueued) {
      asapEnqueued = false;
      var queue = asapCallbackQueue;
      asapCallbackQueue = CallbackQueue.getPooled();
      queue.notifyAll();
      CallbackQueue.release(queue);
    }
  }
};

在这里,首先发现,这里的transaction和 前文ReactDefaultBatchingStrategyTransaction不同,前文是通过new 的方式实例化的对象,而这里是调用ReactUpdatesFlushTransaction.getPooled()获取,朔本清源,看看ReactUpdatesFlushTransactionReactDefaultBatchingStrategyTransaction具体有什么区别


var NESTED_UPDATES = {
  initialize: function() {
    this.dirtyComponentsLength = dirtyComponents.length;
  },
  close: function() {
    // 在批量更新,如果有新的dirtyComponents被push,那么,需要再一次批量更新,从新加入的dirtyComponents开始
    if (this.dirtyComponentsLength !== dirtyComponents.length) {
      dirtyComponents.splice(0, this.dirtyComponentsLength);
      flushBatchedUpdates();
    } else {
      dirtyComponents.length = 0;
    }
  },
};

var UPDATE_QUEUEING = {
  initialize: function() {
    // 重置回调队列
    this.callbackQueue.reset();
  },
  close: function() {
    // 执行回调方法
    this.callbackQueue.notifyAll();
  },
};

var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];

// ReactUpdatesFlushTransaction构造函数
function ReactUpdatesFlushTransaction() {
  // 调用reinitializeTransaction
  this.reinitializeTransaction();
  this.dirtyComponentsLength = null;
  //获取callbackQueue,reconcileTransaction实例;
  this.callbackQueue = CallbackQueue.getPooled();
  this.reconcileTransaction = ReactUpdates.ReactReconcileTransaction.getPooled(
    /* useCreateElement */ true,
  );
}

// 继承,覆盖相关方法
Object.assign(ReactUpdatesFlushTransaction.prototype, Transaction, {
  // 覆盖getTransactionWrappers方法
  getTransactionWrappers: function() {
    return TRANSACTION_WRAPPERS;
  },

  // 覆盖destructor方法,该方法会在放回缓存池中调用
  destructor: function() {
    this.dirtyComponentsLength = null;
    CallbackQueue.release(this.callbackQueue);
    this.callbackQueue = null;
    ReactUpdates.ReactReconcileTransaction.release(this.reconcileTransaction);
    this.reconcileTransaction = null;
  },

  perform: function(method, scope, a) {
    // Essentially calls `this.reconcileTransaction.perform(method, scope, a)`
    // with this transaction's wrappers around it.
    return Transaction.perform.call(
      this,
      this.reconcileTransaction.perform,
      this.reconcileTransaction,
      method,
      scope,
      a,
    );
  },
});

//加入缓存池
PooledClass.addPoolingTo(ReactUpdatesFlushTransaction);

和普通的transation一样,似乎并没有什么不同,都有对应的wrappers getTransactionWrappers,对应构造函数,以及都重写了getTransactionWrappers方法。但是细细发现后,发现这个transaction重写了destructor方法,同时执行了命令PooledClass.addPoolingTo(ReactUpdatesFlushTransaction);,那么,做这些是有什么作用呢?这里就引出了React的一个类库PooledClass,来看看他的实现吧!

var oneArgumentPooler = function(copyFieldsFrom) {
  var Klass = this;
  // 如果缓存池长度不为0,即存在实体对象,
  if (Klass.instancePool.length) {
    //那么直接从缓存池返回对应的对象
    var instance = Klass.instancePool.pop();
    // 同时调用一次该类构造函数,对该instance进行初始化
    Klass.call(instance, copyFieldsFrom);
    return instance;
  } else {
  // 如果没有缓存,则new一个出来
    return new Klass(copyFieldsFrom);
  }
}

var standardReleaser = function(instance) {
  var Klass = this;
  // 调用实例的destructor方法
  instance.destructor();
  // 将实例压入池子
  if (Klass.instancePool.length < Klass.poolSize) {
    Klass.instancePool.push(instance);
  }
};

  
var DEFAULT_POOL_SIZE = 10;
var DEFAULT_POOLER = oneArgumentPooler;

type Pooler = any;

//重点代码
var addPoolingTo = function<T>(
  CopyConstructor: Class<T>,
  pooler: Pooler,
): Class<T> & {
  getPooled(): /* arguments of the constructor */ T,
  release(): void,
} {
  // Casting as any so that flow ignores the actual implementation and trusts
  // it to match the type we declared
  var NewKlass = (CopyConstructor: any);
  // 该类的具体实例缓存池
  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: Pooler),
  twoArgumentPooler: (twoArgumentPooler: Pooler),
  threeArgumentPooler: (threeArgumentPooler: Pooler),
  fourArgumentPooler: (fourArgumentPooler: Pooler),
};

代码很简单,主要是提供了 addPoolingTo这个方法,在页面调用该方法后,会将getPooled以及release方法以及instancePool挂载到对应的类上,当调用getPooled方法后,会优先从instancePool去寻找是否有已经生成的实例,如果有,将其初始化(执行class的构造函数),没有的话,则new一个出来。当对象使用完毕后,调用一下release方法,这时,会调用实例的destructor方法去销毁实例中的对象等数据,同时放入缓存池中等待下一次调用,为什么react中要用这样的方法而不是直接new呢。因为new 一个function的时候,其会在原型链去查找属性(比较耗时),
详细见 继承与原型链

CallbackQueue

CallbackQueue代码比较简单,我们直接看看实现吧

class CallbackQueue<T> {
  // 回调队列
  _callbacks: ?Array<() => void>;
  //上下文
  _contexts: ?Array<T>;
  _arg: ?mixed;

  constructor(arg) {
    this._callbacks = null;
    this._contexts = null;
    this._arg = arg;
  }

  /**
   * Enqueues a callback to be invoked when `notifyAll` is invoked.
   *
   * @param {function} callback Invoked when `notifyAll` is invoked.
   * @param {?object} context Context to call `callback` with.
   * @internal
   */
  enqueue(callback: () => void, context: T) {
    //将回调和上下文塞入队列中
    this._callbacks = this._callbacks || [];
    this._callbacks.push(callback);
    this._contexts = this._contexts || [];
    this._contexts.push(context);
  }

  /**
   * 执行队列中所有回调函数
   *
   * @internal
   */
  notifyAll
  () {
    var callbacks = this._callbacks;
    var contexts = this._contexts;
    var arg = this._arg;
    if (callbacks && contexts) {
      this._callbacks = null;
      this._contexts = null;
      for (var i = 0; i < callbacks.length; i++) {
        callbacks[i].call(contexts[i], arg);
      }
      callbacks.length = 0;
      contexts.length = 0;
    }
  }

  /**
   * 检查点,返回队列长度
   * @returns {number}
   */
  checkpoint() {
    return this._callbacks ? this._callbacks.length : 0;
  }

  /**
   *
   * @param len
   */
  rollback(len: number) {
    if (this._callbacks && this._contexts) {
      this._callbacks.length = len;
      this._contexts.length = len;
    }
  }

  /**
   * 重置队列以及上下文
   *
   * @internal
   */
  reset() {
    this._callbacks = null;
    this._contexts = null;
  }

  /**
   * Pool调用release后会执行
   */
  destructor() {
    this.reset();
  }
}

module.exports = PooledClass.addPoolingTo(CallbackQueue);

代码比较简单,用flow来编写。不过从设计思路上可以发现,在react中,往往是把上下文以及组件回调先存入这个统一的队列中,最后执行notifyAll来顺序执行回调。同时队列中提供了这样几个API供外部调用分别是

  1. enqueue : 将回调和上下文塞入队列的方法
  2. notifyAll: 通知调用回调队列的方法
  3. checkpoint: 检查点,返回队列长度
  4. rollback: 设置队列长度为指定数字
  5. reset: 重置队列以及上下文
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,043评论 25 707
  • ​ React Native(以下简称RN)的目标是用基于react的JavaScript写代码,在iOS/A...
    Iceguest阅读 3,571评论 0 10
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,621评论 18 399
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,649评论 18 139
  • 《拖延心理学》学而思day1 今天一早才听了《拖延心理学》的第一,二天带读。这不就是典型拖延症么。一直拖到晚上才写...
    真我_14d6阅读 227评论 0 0