应用架构

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-05-21

单页面应用

单页应用程序 (SPA) 是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。浏览器一开始会加载必需的HTML、CSS和JavaScript,所有的操作都在这张页面上完成,都由JavaScript来控制。因此,对单页应用来说模块化的开发和设计显得相当重要。

  • MV*:经典MV*开发模式,前后端各负其责。
  • ajax:重前端,业务逻辑全部在本地操作,数据都需要通过AJAX同步、提交。
  • 路由:在URL中采用#号来作为当前视图的地址,改变#号后的参数,页面并不会重载。

MV* 架构

model

model 是一个用来存储数据的组件。这些数据通常从HTTP API请求过来并显示在view上。在一些框架中,model 包含了与网络服务进行通信的方法,而另一些框架中则有单独的模块负责与 HTTP API 通信,形式并不固定;

collection

collection 用来表示一组 model

view

view 负责将存储在 model 中的数据渲染成 HTML,view 通常依赖在构造函数、属性或设置中传入一个model、一个模板和一个容器。

  • model 和 模板用来生成 HTML
  • 容器通常是一个 DOM 元素选择器,被选中的 DOM 元素作为 HTML 的容器, HTML将会被插入或附加进去。

collection view

collection view 是一种特殊的 view,它与 view 的关系就好比 model 与 collection 的关系,collection view 通常依赖在构造函数、属性或者设置中 传入一个 collection、一个view和一个容器。

collection view 将迭代 collection 里面存储的 model,使用 view 去渲染它,然后将结果追加到容器尾部。

controller

一些框架提供了 controller 功能,controller 通常负责管理特定的 model 和相关 view 的生命周期,它的职责是实例化 model 和 collection,将它们关联起来,并与相关的 view 联系起来,在将控制权交给其他 controller 前销毁它们。

MVC 应用的交互是通过组织 controller 和它的方法。controller 在需要的时候有许多方法,而这些方法和用户的行为一一对应。

controller 中的方法不能有任何操作数据的行为(这是model的职责)或用户界面事件处理(这是view的职责)。理想状态下,controller中的方法应该只是发布一个或多个事件,让代码运行流从controller 到一个或多个 model中。

事件

事件是指被程序发现的行为或发生的事情,而且它可能会被程序处理,MV*框架通常区分两种事件。

  • 用户事件:程序允许用户通过触发和处理事件的形式沟通,比如单击一个按钮、滚动屏幕或提交一个表单,用户事件通常在 view 中处理。
  • 程序事件:应用自身也可以触发和处理一些事件。比如,一些程序在 view 渲染后触发 onRender 事件,或在 controller 的方法调用前触发 onBeforeRouting 事件。

程序事件是遵循 SOLID 原则中的 开/闭原则的一个好的方式,可以使用事件来允许开发者扩展框架,而不需要对框架做任何修改。程序事件也可以用来避免组件间的直接通信。

路由 和 hash(#)导航

路由负责观察URL的变更,并将程序的执行流切换到 controller 的相应方法上。主流框架使用了一种叫做 hash 导航的混合技术,它使用 HTML5 的 History API在不重载页面的情况下处理页面URL的变更。window.addEventListener('hashchange')

中介器

一些 MV* 框架引入一个叫做中介器的组件,中介器是一个简单的对象,所有其他的模块都通过它与其他部分进行通信。中介器通常实现于发布/订阅设计模式(pub/sub),这种模式可以让模块之间不用相互依赖,模块之间通过事件通信,而不是直接使用程序中其他的部分。

模块可以监听一个事件并处理它,也可以发布一个事件让其他模块响应这个事件,这保证了程序模块间的低耦合,也能轻松实现信息交换。

调度器

路由是在向调度器发送事件而不是 controller,调度器的职责就是创建新的 controller 和销毁旧的 controller,当路由完成对URL的解析后,它将会通过中介器向调度器传入一个新的路由实例,然后调度器会销毁旧的 controller 并创建一个新的 controller,并使用中介器调用 controller 上的方法。

客户端渲染和 Virtual DOM

何时渲染?

  • 第一种是使用定时器检测变更,这个操作有时候被称为脏检测
  • 第二种是使用 observable model

observable 的实现更高效,因为 observable 仅在变更发生的时候触发,而定时器会在时间符合条件的时候触发,不管是否有变更发生。

如何渲染?

  • 一些框架直接操作 DOM
  • 一些框架在内存中操作被称为 Virtual DOM 的 DOM 映射, Virtual DOM 更加高效,因为 JavaScript 对内存的操作比对 DOM 的操作更加迅速。

用户界面数据绑定

单向数据绑定是一种 UI 数据绑定的类型,这种类型的数据绑定仅单向传播变更。

双向数据绑定用来确保 model 和 view 中所有的变更都会传递给对方。

单向数据流

所有的 Action 都直接发送到 Dispatcher (类似调度器)中,然后 Dispatch 将执行流交给 Store (类似 Model),Store 用来存储和操作数据,当数据被修改时,它就会被传递给 View,View 负责将数据渲染成 HTML 并处理用户事件(Action),如果一个用户事件(Action)需要修改一些数据, View 会将这个 Action 送入 Dispatcher 中,而不是直接对 model 进行修改,而这种场景会在双向数据绑定的框架中发生。

Web component 和 shadow DOM

一些框架使用 Web component 术语来指代那些可以重用的 UI组件,Web component 允许用户自定义 HTML 元素。 Web component 可以单独引入它们自己的依赖并且使用 shadow DOM 的客户端模板渲染 HTML。 shadow DOM 让浏览器能在 Web component 中使用 HTML、CSS、JavaScript,它可以避免模块之间的 HTML、CSS、JavaScript冲突。

从零开始实现一个 MVC 框架

工作流程

1.程序组件初始化:调用 Dispatch (调度器)和 Router(路由)的初始化方法,订阅全局错误事件,并发布 app.initialize 事件。

2.1 路由的初始化方法:监听hash的变化,并订阅 app.initialize 事件和 app.route 事件。app.route事件允许其他组件导航到下一个路由(修改当前URL)

2.2 路由监听到 app.initialize 到事件后,将hash处理(parseRoute)成参数后 触发 app.dispatch 事件。

2.3 路由监听到 app.route 到事件后,会改变当前路由(hash),从而触发hashchange事件, 触发 app.dispatch 事件。

3.1 调度器初始化的时候,就会将用到的 controller 全部存储下来。

3.2 当接收到路由触发的 app.dispatch 事件后

3.2.1 如果controller不存在就抛错,否则就创建 controller 实例。 3.2.2 如果action不可用就抛错,否则就进行当前 currentController 的替换(当前没有 或者 有但是不是所需要的,就重新初始化)和 发布事件 app.controllers.controller.action,将执行流交给controller。

4.1 controller 初始化:进行 model 和 view 的初始化,并监听相关 action 事件。 4.2 controller 接收到 Dispatch 分发的事件:发布 app.model.xx.change 事件供 model 订阅,并将执行流交给model

5.model 初始化时,会订阅当前model的 app.model.selfModel.change 事件,当事件被触发时,进行数据的获取和处理,并触发 app.view.market.render 事件,将执行流交给 view。

6.view 初始化时,会订阅当前view 的 app.view.selfView.render事件,当事件被触发,就会去获取模板然后结合 model 传来的数据生成HTML插入到DOM中去,并根据业务来绑定对应的用户UI事件,当用户的UI事件,需要更换到别的路由时,触发 app.route事件,执行流回到2.3步。

程序组件

这是一个程序的根组件,负责初始化框架内所有的内部组件(中介器、路由和调度器)

interface IAppSettings {
  /** 是否打开调式模式 */
  isDebug: boolean;
  /** 默认 controller的名字 */
  defaultController: string;
  /** 默认 controller的方法名 */
  defaultAction: string;
  /** 可用的controller */
  controllers: Array<IControllerDetails>;
  /** 设置全局错误处理函数 */
  onErrorHandler: (o: Object) => void;
}

interface IControllerDetails {
  controllerName: string;

  controller: { new (...args: any[]): IController; };
}

// app.ts
import { Dispatcher } from './dispatcher';
import { Mediator } from './mediator'; 
import { AppEvent } from './app_event'; 
import { Router } from './router'; 

class App {
  private _dispatcher: IDispatcher;
  private _mediator: IMediator;
  private _router: Router;
  private _controllers: IControllerDetails[];
  private _onErrorHandler: (o: Object) => void;

  constructor(appSettings: IAppSettings) {
    this._controllers = appSettings.controllers;
    this._mediator = new Mediator(appSettings.isDebug || false);
    this._router = new Router(this._mediator, appSettings.defaultController, appSettings.defaultAction);
    this._dispatcher = new Dispatcher(this._mediator, this._controllers);
    this._onErrorHandler = appSettings.onErrorHandler;
  }

  // 初始化
  public initialize() {
    this._router.initialize();
    this._dispatcher.initialize();
    // 订阅全局错误事件
    this._mediator.subscribe(new AppEvent('app.error', null, (e: any, data?: any) => {
      this._onErrorHandler(data);
    }))
    // 发布初始化事件
    this._mediator.publish(new AppEvent('app.initialize', null, null));
  }
}

export { App };

中介器

中介器负责程序中所有其他模快间的通信

interface IMediator {
  /** 发布 */
  publish(e: IAppEvent): void;
  /** 订阅 */
  subscribe(e: IAppEvent): void;
  /** 取消订阅 */
  unsubscribe(e: IAppEvent): void;
}

class Mediator implements IMediator {
  private _$: JQuery;
  private _isDebug: boolean;

  constructor(isDebug: boolean = false) {
    this._$ = $({});
    this._isDebug = isDebug;
  }

  public publish(e: IAppEvent): void {
    if (this._isDebug) {
      console.log(new Date().getTime(), "PUBLISH", e.topic, e.data);
    }
    this._$.trigger(e.topic, e.data);
  }

  public subscribe(e: IAppEvent): void {
    if (this._isDebug) {
      console.log(new Date().getTime(), "SUBSCRIBE", e.topic, e.data);
    }
    this._$.on(e.topic, e.handler);
  }

  public unsubscribe(e: IAppEvent): void {
    if (this._isDebug) {
      console.log(new Date().getTime(), "UNSUBSCRIBE", e.topic, e.data);
    }
    this._$.off(e.topic);
  }
}

export { Mediator };

程序事件

程序事件被用来将信息从一个组件发送到另一个,组件可以发布程序事件也可以订阅或取消订阅一个程序事件。

interface IAppEvent {
  /** 标志 */
  topic: string;
  /** 数据 */
  data: any;
  /** 事件处理函数 */
  handler: (e: any, data: any): void;
}

class AppEvent implements IAppEvent {
  public guid: string;

  public topic: string;

  public data: any;

  public handler: (e: Object, data?: any) => void;

  constructor(topic: string, data: any, handler: (e: Object, data?: any) => void ) {
    this.topic = topic;
    this.data = data;
    this.handler = handler;
  }
}

export { AppEvent }

路由表

它被用来表示一个URL,URL命名规则可以指明哪一个 controller 的方法在特定路由下被调用。

interface IRoute {
  controllerName: string;
  actionName: string;
  args: Object[];
  serialize(): string;
}

class Route implements IRoute {
  public controllerName: string;
  public actionName: string;
  public args: Object[];

  constructor(controllerName: string, actionName: string, args: Object[]) {
    this.controllerName = controllerName;
    this.actionName = actionName;
    this.args = args;
  }

  public serialize(): string {
    var s, sargs;
    sargs = this.args.map(a => a.toString()).join('/');
    s = `${this.controllerName}/${this.actionName}/${sargs}`;
    return s;
  }
}

export { Route };

事件发射

EventEmitter类,整个框架中除了程序组件,每一个组件都将继承这个类。

我们已经知道,所有的组件都使用中介器与其他组件进行通信,中介器是一个单例,这意味着程序中的每一个组件都需要能访问中介器实例,EventEmitter类就是为了减少代码重复而实现的,并提供一些方便事件发布和订阅的帮助。

后面的 route 、 dispatch 、 controller 、 model 、 view 都继承于这个类,并将 mediator 通过 super() 传给 EventEmitter,以方便使用事件发布、订阅、触发。

interface IEventEmitter {
  triggerEvent(event: IAppEvent);
  subscribeToEvents(event: IAppEvent[]);
  unsubscribeToEvents(event: IAppEvent[]);
}

class EventEmitter implements IEventEmitter {
  protected _mediator: IMediator;
  
  /** 存储组件订阅的事件 */
  protected _events: IAppEvent[];

  constructor(mediator: IMediator) {
    this._mediator = mediator;
  }

  public triggerEvent(event: IAppEvent) {
    this._mediator.publish(event);
  }

  public subscribeToEvents(events: IAppEvent[]) {
    this._events = events;
    for (var i = 0; i < this._events.length; i++) {
      this._mediator.subscribe(this._events[i])
    }
  }

  public unsubscribeToEvents(events: IAppEvent[]) {
    this._events = events;
    for (var i = 0; i < this._events.length; i++) {
      this._mediator.unsubscribe(this._events[i])
    }
  }
}

export { EventEmitter }; 

路由

路由观察浏览器 URL 的变更,并在变更时创建一个 Route 实例,通过程序事件传递给调度器。

interface IRouter extends IEventEmitter {
  initialize(): void;
}

class Router extends EventEmitter implements IRouter {
  private _defaultController: string;
  private _defaultAction: string;

  constructor(mediator: IMediator, defaultController: string, defaultAction: string) {
    super(mediator);
    this._defaultController = defaultController || 'home';
    this._defaultAction = defaultAction || 'index';
  }

  public initialize() {
    // 监测URL被用户改变
    $(window).on('hashchange', () => {
      var r = this.getRoute();
      this.onRouteChange(r);
    });

    // 订阅事件
    this.subscribeToEvents([
      // 应用初始化时触发路由
      new AppEvent('app.initialize', null, (e: any, data?: any) => {
        this.onRouteChange(this.getRoute());
      }),

      // 用于从其他组件改变URL
      new AppEvent('app.route', null, (e: any, data?: any) => {
        this.setRoute(data);
      })
    ])
  }

  private getRoute() {
    var h = window.location.h;
    return this.parseRoute(h);
  }

  private parseRoute(hash: string) {
    var comp, controller, action, args, i;
    if (hash.endsWith('/')) {
      hash = hash.slice(0, -1);
    }

    comp = hash.replace('#', '').split('/');
    controller = comp[0] || this._defaultController;
    action = comp[1] || this._defaultAction;

    args = [];
    for (i = 2; i < comp.length; i++ ) {
      args.push(comp[i]);
    }

    return new Route(controller, action, args);
  }

  // 改变当前 hash, 会触发 hashchange事件,触发触发 app.dispatch事件
  private setRoute(route : Route) {
    var s = route.serialize();
    window.location.hash = s;
  }

  // 通过中介器将控制流转移给调度器
  private onRouteChange(route: Route) {
    this.triggerEvent(new AppEvent('app.dispatch', route, null));
  }
}

export { Router };

调度器

调度器接收一个 Route 类的实例,这个实例被用来指定依赖的 controller。如果需要的话,调度器会销毁上一个 controller 并新建一个。一旦 controller 被创建,调度器使用程序事件将程序执行流交给 controller。

interface IDispatcher extends IEventEmitter {
  initialize(): void;
}

class Dispatcher extends EventEmitter implements IDispatcher {
  private _controllersHashMap: Object;
  private _currentController: IController;
  private _currentControllerName: string;

  constructor(mediator: IMediator, controllers: IControllersDetails[]) {
    super(mediator);
    this._controllersHashMap = this.getController(controllers);
    this._currentController = null;
    this._currentControllerName = null;
  }

  public initialize() {
    this.subscribeToEvents([
      new AppEvent('app.dispatch', null, (e: any, data?: any) => {
        this.dispatch(data);
      })
    ])
  }

  private getController(controllers: IControllersDetails[]): Object {
    var hashMap, hashMapEntry, name, controller, l;
    hashMap = {};
    l = controllers.length;

    if (l <= 0) {
      this.triggerEvent(new AppEvent(
        'app.error',
        'Cannot create an application without at least one controller.',
        null
      ))
    }

    for (var i = 0; i < l, i++) {
      controller = controllers[i];
      name = controller.controllerName;
      hashMapEntry = hashMap[name];
      if (hashMapEntry) { // 已存在
        this.triggerEvent(new AppEvent(
          'app.error',
          'Two controller cannot use the same name.',
          null
        ))
      }
      // 不存在就设置
     hashMap[name] = controller.controller;
    }

    return hashMap;
  }

  // 负责创建、初始化、销毁一个 controller
  private dispatch(route: IRoute) {
    var Controller = this.__controllersHashMap[route.controllerName];
    if (!Controller) { // 不存在
      this.triggerEvent(new AppEvent(
        'app.error',
        `Controller not found: ${route.controllerName}`,
        null
      ))
    } else {
      // 创建一个 controller 实例
      var controller = new Controller(this._mediator);

      var a = controller[route.actionName];

      if (!a) { // 该行为不可用
        this.triggerEvent(new AppEvent(
          'app.error',
          `Action not found in controller: ${route.controllerName} + ${route.actionName}`,
          null
        ))
      } else { // 可用
        // 当前不存在 或 当前存在但是不是所需要的就 进行初始化
        if (!this._currentController) {
          this._currentControllerName = route.controllerName;
          this._currentController = controller;
          this._currentController.initialize();
        } else {
          if (this._currentControllerName !== route.controllerName) {
            this._currentController.dispose(); // 存在但不是所需要的就销毁再进行初始化
            this._currentControllerName = route.controllerName;
            this._currentController = controller;
            this._currentController.initialize();
          }
        }
        // 将流从调度器传递至 controller
        this.triggerEvent(new AppEvent(
          `app.controllers.${this._currentControllerName}.${route.actionName}`,
          route.args,
          null
        ));
      }
    }
  }
}

export { Dispatcher };

controller

controller 被用来初始化 view 和 model,一旦 view 和 model 初始化完成,controller 就将执行流交给一个或多个 model。

interface IController extends IEventEmitter {
  initialize(): void; // 初始化
  dispose(): void; // 销毁
}

// Controller 类本身不提供任何功能,这意味着开发者在开发程序的时候需要自己实现上面的功能。
class Controller extends EventEmitter implements IController {
  constructor(mediator: IMediator) {
    super(mediator);
  }

  public initialize() : void {
    throw new Error('Controller.prototype.initialize() is abstract you must implement it!');
  }

  public dispose() : void {
    throw new Error('Controller.prototype.dispose() is abstract you must implement it!');
  }
}

export { Controller };

举例:完成一个MarketController

class MarketController extends Controller implements IController {
  private _marketView : IView;
  private _nasdaqModel : IModel;
  private _nyseModel : IModel;

  constructor(mediator : IMediator) {
    super(mediator);
    this._marketView = new MarketView(mediator);
    this._nasdaqModel = new NasdaqModel(mediator);
    this._nyseModel = new NyseModel(mediator);
  }

  public initialize() : void {
    
    // 当前Controller的负责监听相关 action 并发布 model change 事件, 将事件流交给 model
    this.subscribeToEvents([
      new AppEvent("app.controller.market.nasdaq", null, (e, args : string[]) => { this.nasdaq(args); }),
      new AppEvent("app.controller.market.nyse", null, (e, args : string[]) => { this.nyse(args); })
    ]);

    // 初始化 model 和 view 
    this._marketView.initialize();
    this._nasdaqModel.initialize();
    this._nyseModel.initialize();
  }

  public dispose() : void {
    // 解绑事件  和 销毁 model 和 view
    this.unsubscribeToEvents();

    this._marketView.dispose();
    this._nasdaqModel.dispose();
    this._nyseModel.dispose();
  }

  public nasdaq(args : string[]) {
    this._mediator.publish(new AppEvent("app.model.nasdaq.change", null, null));
  }

  public nyse(args : string[]) {
    this._mediator.publish(new AppEvent("app.model.nyse.change", null, null));
  }
}

export { MarketController };

model

model 负责与 HTTP API 通信,并在内存中维护这些数据,这涉及数据的格式化和对数据的增减,一旦 model 完成了对数据的操作,它就将被传递到一个或者多个 view 中。

使用一个装饰器来为 model 提供网络服务的 URL,下面装饰器通过构造函数注入 URL

function ModelSettings(serviceUrl: string) {
  return function(target: any) {
    var original = target;

    // 一个用于生成类实例的工具函数 寄生式继承
    function constructor(constructor, args) {
      var c: any = function () {
        return constructor.apply(this, args);
      }
      c.prototype = constructor.prototype;
      var instance = new c();
      instance._serviceUrl = serviceUrl;
      return instance;
    }

    var f: any = function(...args) {
      return constructor(original, args)
    }

    f.prototype = original.prototype;

    return f;
  }
}

model 的实现

interface IModel extends IEventEmitter {
  initialize(): void;
  dispose(): void;
}

class Model extends EventEmitter implements IModel {
  private _serviceUrl: string;

  constructor(mediator: IMediator) {
    super(mediator);
  }

  public initialize() : void {
    throw new Error('Model.prototype.initialize() is abstract you must implement it!');
  }

  public dispose() : void {
    throw new Error('Model.prototype.dispose() is abstract you must implement it!');
  }

  protected requestAsync(method: string, dataType: string, data: any) {
    return Q.Promise((resolve: (r) => {}, reject: (e) => {}) => {
      $.ajax({
        method,
        url: this._serviceUrl, // 需要使用装饰器来设置
        data: data || {},
        dataType,
        success: (r) => {
          resolve(t)
        },
        error: (...args: any[]) {
          reject(args);
        }
      })
    })
  }

  // 查询请求封装
  protected getAsync(dataType: string, data: any) {
    return this.requestAsync('GET', dataType, data);
  }
  
  // 新增请求封装
  protected postAsync(dataType: string, data: any) {
    return this.requestAsync('POST', dataType, data);
  }

  // 更新请求封装
  protected putAsync(dataType: string, data: any) {
    return this.requestAsync('PUT', dataType, data);
  }

  // 删除请求封装
  protected deleteAsync(dataType: string, data: any) {
    return this.requestAsync('DELETE', dataType, data);
  }
}

export { Model };

举例:实现一个NasdaqModel

@ModelSettings("./data/nasdaq.json")
class NasdaqModel extends Model implements IModel {

  constructor(mediator : IMediator) {
    super(mediator);
  }

  // 订阅当前 model 的change事件。
  public initialize() {
    this.subscribeToEvents([
      new AppEvent("app.model.nasdaq.change", null, (e, args) => { this.onChange(args); })
    ]);
  }

  public dispose() {
    this.unsubscribeToEvents();
  }

  private onChange(args) : void {
    this.getAsync("json", args)
    .then((data) => {
      // 数据处理 并触发 view render 事件
      var stocks = { items : data, market : "NASDAQ" };
      this.triggerEvent(new AppEvent("app.view.market.render", stocks, null));
    })
    .catch((e) => {
      // 发布全局错误事件
      this.triggerEvent(new AppEvent("app.error", e, null));
    });
  }
}

export { NasdaqModel };

view

view 负责加载并编译模板,一旦模板编译完成,它就会等待 model 传入数据。当收到数据后,它会和模板一起被编译成 HTML 代码并插入 DOM,view 也负责绑定和解绑 UI 事件(click, focus)等。

使用装饰器为 view 提供一个模板的地址,与上面的 ModelSettings 类似,不同点在给实例增加属性的时候。

function ViewSettings(templateUrl: string, container: string) {
  // ...
  instance._templateUrl = templateUrl;
  instance._container = container;
  // ...
}

View 类的实现。

class View extends EventEmitter implements IView {
  protected _container: string;
  private _templateUrl: string;

  private _templateDelegate: HandlebarsTemplateDelegate;

  constructor(mediator: IMediator) {
    super(mediator)
  }

  // 以下具体的方法都需要派生类根据业务来自己实现。 初始化、销毁、绑定UI事件、解绑UI事件
  public initialize() {
    throw new Error('View.prototype.initialize() is abstract and must implemented.');
  }

  public dispose() {
    throw new Error('View.prototype.dispose() is abstract and must implemented.');
  }

  protected bindDomEvents(model : any) {
    throw new Error('View.prototype.bindDomEvents() is abstract and must implemented.');
  }

  protected unbindDomEvents() {
    throw new Error('View.prototype.unbindDomEvents() is abstract and must implemented.');
  }

  // 加载模板
  private loadTemplateAsync() {
    return Q.Promise((resolve : (r) => {}, reject : (e) => {}) => {
      $.ajax({
        method: "GET",
        url: this._templateUrl,
        dataType: "text",
        success: (response) => {
          resolve(response);
        },
        error : (...args : any[]) => {
          reject(args);
        }
      });
    });
  }

  // 异步编译模板
  private compileTemplateAsync(source : string) {
    return Q.Promise((resolve : (r) => {}, reject : (e) => {}) => {
      try {
        var template = Handlebars.compile(source);
        resolve(template);
      }
      catch(e) {
        reject(e);
      }
    });
  }

  // 异步加载和编译一个模板
  private getTemplateAsync() {
    return Q.Promise((resolve : (r) => {}, reject : (e) => {}) => {
      if(this._templateDelegate === undefined || this._templateDelegate === null) {
        this.loadTemplateAsync()
        .then((source) => {
          return this.compileTemplateAsync(source);
        })
        .then((templateDelegate) => {
          this._templateDelegate = templateDelegate;
          resolve(this._templateDelegate);
        })
        .catch((e) => { reject(e); });
      }
      else {
        resolve(this._templateDelegate);
      }
    });
  }

  // 异步渲染一个view
  protected renderAsync(model) {
    return Q.Promise((resolve : (r) => {}, reject : (e) => {}) => {
      this.getTemplateAsync()
      .then((templateDelegate) => {
        // 生成 HTML 并添加到 DOM 中
        var html = this._templateDelegate(model);
        $(this._container).html(html);

        // 将model作为参数传送给 Model
        // 让子视图 和 DOM 事件初始化
        resolve(model);
      })
      .catch((e) => { reject(e); });
    });
  }
}

export { View };

举例: 实现一个MarketView

@ViewSettings("./source/app/templates/market.hbs", "#outlet")
class MarketView extends View implements IView {

  constructor(mediator : IMediator) {
    super(mediator);
  }

  initialize() : void {
    this.subscribeToEvents([
      new AppEvent("app.view.market.render", null, (e, args : any) => {
        this.renderAsync(args)
        .then((model) => {
          this.bindDomEvents(model);
        })
        .catch((e) => {
          this.triggerEvent(new AppEvent("app.error", e, null));
        });
      }),
    ]);
  }

  public dispose() : void {
    this.unbindDomEvents();
    this.unsubscribeToEvents();
  }

  // 根据业务 进行UI事件的绑定
  protected bindDomEvents(model : any) {
    var scope = $(this._container);
    $(".getQuote").on('click', scope, (e) => {
      var symbol = $(e.currentTarget).data('symbol');
      this.getStockQuote(symbol);
    });
    $(scope).find('table').DataTable();
  }

  protected unbindDomEvents() {
    var scope = this._container;
    $(".getQuote").off('click', scope);
    var table = $(scope).find('table').DataTable();
    table.destroy();
  }

  // 根据用户操作改变当前 route,并触发 app.route 事件
  private getStockQuote(symbol : string) {
    this.triggerEvent(new AppEvent(
      "app.route",
      new Route("symbol", "quote", [symbol]),
      null
    ));
  }
}

export { MarketView };