Flux源码解析(一)

Flux源码解析 第一章

Flux是Facebook推出的一个新的Web架构,用来构建新一代的客户端的Web程序,今天我们来解析下其中的源码:Dispatcher.js和Invariant.js

准备工作

建议大家先了解一下什么是Flux架构,Facebook的官网上有非常详细的解释:链接点我

Invariant.js

首先让我们来看下Invariant.js的代码内容,非常短:

"use strict";

var invariant = function(condition, format, a, b, c, d, e, f) {
  1. 
  if (__DEV__) {
    if (format === undefined) {
      throw new Error('invariant requires an error message argument');
    }
  }

  2. 
  if (!condition) {
    var error;
    if (format === undefined) {
      error = new Error(
        'Minified exception occurred; use the non-minified dev environment ' +
        'for the full error message and additional helpful warnings.'
      );
    } else {
      var args = [a, b, c, d, e, f];
      var argIndex = 0;
      error = new Error(
        'Invariant Violation: ' +
        format.replace(/%s/g, function() { return args[argIndex++]; })
      );
    }

    error.framesToPop = 1; // we don't care about invariant's own frame
    throw error;
  }
};

module.exports = invariant;

‘use strict’ 不必多说,就是构建了一个严格的JS的执行环境。
整个Invariant.js其实就是定义了一个函数, 名字叫Invariant, 他的参数包含一个condition(判断条件),一个自定义的报错格式,以及一系列的自定义参数。

第一次读Flux.js源码的时候,这里采用的还是arguments可变参数的定义方式,今天写这篇文章的时候竟然就改写了,晕!

然后我们来分两个段落查看下其中的代码含义。

第一部分,定义了一个dev变量,这个变量就是一种类似于配置的环境变量。玩过JavaScript或者ruby的人可能都有所了解,比如非常著名的’development’, ‘product’, ‘test’就是与之类似的。
如果没有format是个未定义的变量(在这里就是没有传入第二个参数),那么就保个错。

第二部分也很简单,如果condition为假值,说明这个条件没被满足,那么执行报错的功能,报错的时候使用的提示格式就是传入的format, 参数则为自定义的a-f。

总之,这个Invariant.js的作用就是构建了一个类似iOS中NSAssert的东西。

哦,这里要指出一下 /%s/g这个正则表达式,这玩意啥意思呢。其实就类似于我们常常用的printf函数,可以简单理解为,把format中所有的%s, 替换成自定义的参数,g表示为global。

重点:Dispatcher.js

再让我们来看一看Dispatcher.js,闲话不多话,上代码。

'use strict';

var invariant = require('./invariant');

export type DispatchToken = string;

var _prefix = 'ID_';

class Dispatcher<TPayload> {
  _callbacks: {[key: DispatchToken]: (payload: TPayload) => void};
  _isDispatching: boolean;
  _isHandled: {[key: DispatchToken]: boolean};
  _isPending: {[key: DispatchToken]: boolean};
  _lastID: number;
  _pendingPayload: TPayload;

  constructor() {
    this._callbacks = {};
    this._isDispatching = false;
    this._isHandled = {};
    this._isPending = {};
    this._lastID = 1;
  }


  unregister(id: DispatchToken): void {
    invariant(
      this._callbacks[id],
      'Dispatcher.unregister(...): `%s` does not map to a registered callback.',
      id
    );
    delete this._callbacks[id];
  }

  waitFor(ids: Array<DispatchToken>): void {
    invariant(
      this._isDispatching,
      'Dispatcher.waitFor(...): Must be invoked while dispatching.'
    );
    for (var ii = 0; ii < ids.length; ii++) {
      var id = ids[ii];
      if (this._isPending[id]) {
        invariant(
          this._isHandled[id],
          'Dispatcher.waitFor(...): Circular dependency detected while ' +
          'waiting for `%s`.',
          id
        );
        continue;
      }
      invariant(
        this._callbacks[id],
        'Dispatcher.waitFor(...): `%s` does not map to a registered callback.',
        id
      );
      this._invokeCallback(id);
    }
  }

  dispatch(payload: TPayload): void {
    invariant(
      !this._isDispatching,
      'Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.'
    );
    this._startDispatching(payload);
    try {
      for (var id in this._callbacks) {
        if (this._isPending[id]) {
          continue;
        }
        this._invokeCallback(id);
      }
    } finally {
      this._stopDispatching();
    }
  }

  isDispatching(): boolean {
    return this._isDispatching;
  }

  _invokeCallback(id: DispatchToken): void {
    this._isPending[id] = true;
    this._callbacks[id](this._pendingPayload);
    this._isHandled[id] = true;
  }

  _startDispatching(payload: TPayload): void {
    for (var id in this._callbacks) {
      this._isPending[id] = false;
      this._isHandled[id] = false;
    }
    this._pendingPayload = payload;
    this._isDispatching = true;
  }

  _stopDispatching(): void {
    delete this._pendingPayload;
    this._isDispatching = false;
  }
}

module.exports = Dispatcher;

首先我们来看一下这句话:

export type DispatchToken = string;

这其实就是一个typedef,那么DispatchToken是什么意思呢?如果你本文最上方的Flux解答的话,你就可以了解到,其实一个Flux的回调代码块有唯一的一个token,这个token就是用来让别人Invokoe你代码的。

那么这个DispatchToken怎么确保唯一呢?看下面这句话:

var id = _prefix + this._lastID++;

第一感觉是不是想,我了个擦,怎么那么简单。后来想想,JavaScript在浏览器中执行就是单线程,这种自增ID的做法又快又安全呐。

下面我们来解释一下Dispatcher这个类,这个类的目的很简单,六个字归纳一下:唯一,中控,分配。
可以把他看成一个单例对象,所有的任务分发都必须由这个类统一控制。

那么,这个类中包含了哪些玩意呢?让我们赶快来一探究竟!

_callbacks: {[key: DispatchToken]: (payload: TPayload) => void};
_isDispatching: boolean;
_isHandled: {[key: DispatchToken]: boolean};
_isPending: {[key: DispatchToken]: boolean};
_lastID: number;
_pendingPayload: TPayload;
  • callbacks,就是DispatchToken和函数回调的一个Dictionary。
  • isDispatching,体现当前Dispatcher是否处于dispatch状态。
  • isHandled,通过token去检测一个函数是否被处理过了。
  • isPending,通过token去检测一个函数是否被提交Dispatcher了。
  • lastID,最近一次被加入Dispatcher的函数体的UniqueID,即DispatchToken。
  • pendingPayload,需要传递给调用函数的参数,iOS开发者可以理解为userInfo

下面我们一个个函数来看看。

register(callback: (payload: TPayload) => void): DispatchToken {
    var id = _prefix + this._lastID++;
    this._callbacks[id] = callback;
    return id;
}

这函数就是注册一个callback进入dispatcher,同时为这个callback生成DispatchToken,加入字典。

unregister(id: DispatchToken): void {
    invariant(
      this._callbacks[id],
      'Dispatcher.unregister(...): `%s` does not map to a registered callback.',
      id
    );
    delete this._callbacks[id];
}

有注册就有取消注册,unregister就是通过DispatchToken将注册的callback从字典中删除。当然了,这里使用了我们在上文中提过的Invariant来进行一个判断:即字典中必须包含对应这个DispatchToken的函数。

dispatch函数是Dispatcher用来分发payload的函数。

dispatch(payload: TPayload): void {
    invariant(
      !this._isDispatching,
      'Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.'
    );
    this._startDispatching(payload);
    try {
      for (var id in this._callbacks) {
        if (this._isPending[id]) {
          continue;
        }
        this._invokeCallback(id);
      }
    } finally {
      this._stopDispatching();
    }
  }

整个函数的含义就是,首先是判断当前Dispatcher是否已经处于Dispatching状态中了,如果是,不能打断。然后通过_startDispatching更新状态。更新状态结束以后,将非pending状态的callback进通过_invokeCallback执行(pending在这里的含义可以简单理解为,还没准备好或者被卡住了)。所有执行完了以后,通过_stopDispatching恢复状态。

接下来便让我一一来看看其中涉及的几个函数,首先是_startDispatching函数。

_startDispatching(payload: TPayload): void {
    for (var id in this._callbacks) {
      this._isPending[id] = false;
      this._isHandled[id] = false;
    }
    this._pendingPayload = payload;
    this._isDispatching = true;
  }

首先该函数将所有注册的callback的状态都清空,并标记Dispatcher的状态进入dispatching。

同样,与之对应的有的_stopDispatching,函数很简单,不具体解释了。

_stopDispatching(): void {
    delete this._pendingPayload;
    this._isDispatching = false;
}

而_invokeCallback函数也很简单,当真正调用callback之前将其状态设置为pending,执行完成之后设置为handled。
_invokeCallback(id: DispatchToken): void {
this._isPending[id] = true;
this._callbacksid;
this._isHandled[id] = true;
}

读到这里,有些人可能有些疑问,这个pending到底是用来做什么的呢?别着急,看了下面这个最重要的函数waitFor,你就会了解。

waitFor(ids: Array<DispatchToken>): void {
     1. 
    invariant(
      this._isDispatching,
      'Dispatcher.waitFor(...): Must be invoked while dispatching.'
    );

     2.
    for (var ii = 0; ii < ids.length; ii++) {
      var id = ids[ii];
      if (this._isPending[id]) {
        3. 
        invariant(
          this._isHandled[id],
          'Dispatcher.waitFor(...): Circular dependency detected while ' +
          'waiting for `%s`.',
          id
        );
        continue;
      }
       4.
      invariant(
        this._callbacks[id],
        'Dispatcher.waitFor(...): `%s` does not map to a registered callback.',
        id
      );

      5.
      this._invokeCallback(id);
    }
  }

这个waitFor啥意思呢?我们首先看下他的参数,一个数组,里面全是DispatchToken。再看下实现。

  • 首先是一个Invariant判断当前必须处于Dispatching状态。一开始比较难理解,简单来说就是,如果不处于Dispatching状态中,那么说明压根没有函数在执行,那你等谁呢?
  • 然后该函数从DispatchToken的数组中进行遍历,如果遍历到的DispatchToken处于pending状态,就暂时跳过他。
  • 但是,在这有个必要的循环以来的检查,试想如下情况,如果A函数以来B的Token, B函数依赖A的Token,就会造成“死锁”。所以,当一个函数依赖的对象处于pending,说明这个函数已经被开始执行了,但是如果同时该函数没有进入handled状态,说明该函数也被卡死了。
  • 检查token对应的callback是否存在
  • 调用这个token对应的函数。

从上诉步骤中,我们可以看到,waitFor就是一个等待函数,当B执行,执行到某些条件不满足的时候(我们称之为依赖没解决),就是等待依赖去完成,简单来说,就是如下函数代表的意思:

while (!satisfied) ; doSomething

到这里,差不多Dispatcher.js就解释完了,下次继续。