ThinkJS 中的 Behavior 介紹

jopen 9年前發布 | 15K 次閱讀 JavaScript開發 thinkjs


ThinkJS( 官網 | GitHub )的核心架構是 CBD(核心 Core + 行為 Behavior + 驅動 Driver)模式。核心包含通用函數庫、系統默認配置、核心類庫等運行 ThinkJS 必不可靠的部分;行為是 ThinkJS 擴展機制中很關鍵的一部分,也是本文重點要介紹的內容;除了核心和行為之外,ThinkJS 其他功能都是通過驅動來實現的,例如 Cache、Session、DB 等。

ThinkJS 把響應用戶請求整個流程分成了很多個階段,對應不同的標簽位(Tag)。每個標簽位都可以調用一系列 Behavior。使用 Behavior 可以方便地擴展 ThinkJS,實現自己想要的功能。ThinkJS 框架自身很多功能也都是通過它來實現的。

系統標簽位

先來了解一下 ThinkJS 內置了哪些標簽位。根據 官方文檔 ,ThinkJS 在響應用戶請求過程中,會依次執行以下這些操作:

  • 執行標簽位form_parse;
  • 發送 X-Powered-By 響應頭信息,值為 thinkjs 的版本號;
  • 執行標簽位app_init;
  • 執行標簽位resource_check,判斷當前請求是否是靜態資源類請求。靜態資源類請求執行標簽位resource_output;
  • 執行標簽位path_info,獲取修改后的 pathname;
  • 執行標簽位route_check,進行路由檢測,識別對應的 Group、Controller、Action;
  • 執行標簽位app_begin,檢測當前請求是否有靜態化緩存;
  • 執行標簽位action_init,實例化 Controller;
  • 調用 __before 方法,如果存在的話;
  • 調用對應的 action 方法;
  • 調用 __after 方法,如果存在的話;
  • 執行標簽位view_init,初始化模版引擎;
  • 執行標簽位view_template, 查到模版文件的具體路徑;
  • 執行標簽位view_parse,解析模版內容;
  • 執行標簽位view_filter,對解析后的內容進行過濾;
  • 執行標簽位view_end,模版渲染結束;
  • 執行標簽位app_end, 應用調用結束;

這個完整的請求響應流程,包含了 ThinkJS 所有內置的標簽位。

內置 Behavior

接下來看看 ThinkJS 針對系統標簽位分別配置了哪些 Behavior。打開/thinkjs/lib/Conf/tag.js,可以看到如下配置:

module.exports = {
  app_init: [], //應用初始化
  form_parse: [jsonParse], //表單數據解析
  path_info: [], //pathinfo解析
  resource_check: ['CheckResource'], //靜態資源請求檢測
  resource_output: [resourceOutput], //靜態資源輸出
  route_check: ['CheckRoute'], //路由檢測
  app_begin: ['ReadHtmlCache'], //應用開始
  action_init: [], //action執行初始化
  view_init: [], //模版解析初始化
  view_template: ['LocateTemplate'], //定位模版文件
  view_parse: ['ParseTemplate'], //模版解析
  view_filter: [], //模版內容過濾
  view_end: ['WriteHtmlCache'], //模版解析結束
  action_end: [], //action結束
  app_end: [closeDbConnect], //應用結束
  content_write: [] //內容輸出
};

ThinkJS 內置 Behavior 使用了兩種形式:字符串形式需要定義在外部文件中,存放于/thinkjs/lib/Lib/Behavior目錄,文件名以Behavior.js結尾;函數形式直接定義在了當前文件里。

實現 Behavior

要實現自己的 Behavior 很簡單,只需要兩步:1)在/App/Lib/Behavior/中編寫 Behavior 的具體邏輯;2)在App/Conf/tag.js中啟用它。當然也可以直接把邏輯實現在tag.js里,不過為了更方便地啟用 / 禁用 Behavior,還是推薦定義在外部。

下面這樣就可以定義一個新的 Behavior,框架會自動調用它的run方法。

module.exports = Behavior(function(){
    return {
        run: function() {
        }
    }
})

run方法內部,可以使用this.http獲得 ThinkJS 的 HTTP 對象,這個對象是 ThinkJS 對 Request 和 Response 對象的封裝,提供了很多方法用來獲取和改變請求和響應信息。具體內容可以直接查看/thinkjs/lib/Lib/Core/Http.js文件。

位于某些標簽位的 Behavior 還會收到額外的參數。例如框架執行view_template標簽位時,會把 templateFile 傳過來作為run方法的參數;view_filter中的 Behavior 會收到content參數,內容是經過模板渲染后的內容。

run方法可以返回具體的值,也可以返回 Promise。如果不希望執行后續流程,需要返回 Pendding Promise。Pendding Promise 永遠沒有機會被 Resolve,后續流程自然不會執行,這時需要在 Behavior 里手動結束響應,否則請求會被卡住結束不了。具體代碼在后面貼出。

寫好了 Behavior,在App/Conf/tag.js(沒有就新建一個)啟用它就可以了。類似這樣:

module.exports = { app_begin: ["XXX"]
}

直接在tag.js定義函數來實現 Behavior 是類似的,只是多了第一個參數:http。這里略過。

執行順序

默認情況下,自定義 Behavior 會在框架默認 Behavior 之后運行。可以通過數組的第一個值來改變這個策略,規則如下:

  • 將數據第一個值設置為true,表示用自定義 Behavior 替換默認的;
  • 將數據第一個值設置為false,表示自定義 Behavior 會在框架默認 Behavior 之前運行;

Behavior 示例

下面通過兩個示例演示 Behavior 的實際用法。

首先,我要實現的 Behavior 是用來在博客維護時,訪問任何頁面都只顯示一段提示文字,但 RSS 功能以及管理后臺需要可以正常工作,不能受影響。最終實現代碼如下:

// App/Lib/Behavior/MaintenanceBehavior.js
module.exports = Behavior(function(){
  return {
    run: function(content) {
      if('Blog' !== this.http.group || 'Misc' == this.http.controller) {
        return false;
      }
      var http = this.http;
      http.echo('網站維護中,請稍后再來看看...').then(function() {
        http.end();
      });
      return getDefer().promise;
    }
  }
});

我通過http.group和http.controller這兩個變量確保只修改需要的頁面;然后通過http.echo輸出自定義內容,并通過http.end結束當前請求;最后還需要返回 Pendding Promise,確保后續流程不會執行。

插件寫好之后,在/App/Conf/tag.js中啟用它就可以了。由于這個 Behavior 目的是修改輸出,需要盡早執行,又因為它用到了路由信息,需要放在route_check之后,所以app_begin是最合適的位置:

module.exports = { app_begin: ['Maintenance']
}

上一篇文章里,我為了實現 HTTP/2 的 Server Push,需要把要推送的 css 外鏈找出來放在響應頭里。這也是通過 Behavior 實現的,代碼如下:

// App/Lib/Behavior/ServerPushBehavior.js
module.exports = Behavior(function(){
  return {
    run: function(content){
      content = content.substr(0, 1024);
      var matches = content.match(/<link.{0,100}rel=['"]?stylesheet['"]?.{0,100}>/i);
      if(matches) {
        var href = matches[0].replace(/<link.{0,100}href=['"]?([^'"\s]+)['"]?.{0,100}>/i, '$1');
        this.http.setHeader('link', '<'+ href +'>; rel=preload; as=stylesheet');
      }
    }
  }
})

我從模板渲染后的 content 中截取了前 1024 個字符,從中找到第一個 css 外鏈,并通過http.setHeader添加自定義頭。這個 Behavior 需要用到 html 內容,所以需要配置在view_filter標簽位中:

module.exports = { view_filter: ['ServerPush']
}

其實,ThinkJS 內置的 Behavior 就是最好的代碼示例,可以 點這個鏈接 查看。另外,據說在 ThinkJS 2.0 里,Behavior 已經被middleware取代,到底會有什么變化呢?我們拭目以待。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!