1、引子
最近學用director.js,那是相當的簡單易學易使用。不過開始學的時候,搜搜過后,卻沒有發現相關的中文教程。於是決定硬啃E文,翻譯備用的同時也當是給自己上課並加深對它的理解。
director.js 的官方地址請點擊 這里 查看,本教程內容也主要來自於這里。
2、director.js是什么
director.js 按照我的理解就是客戶端的路由注冊/解析器,它在不刷新頁面的情況下,利用“#”符號組織不同的URL路徑,並根據不同的URL路徑來匹配不同的回調方法。通俗點說就是什么樣的路徑干什么樣的事情。
director.js 適用於客戶端瀏覽器以及node.js的服務器端應用,在兩者的應用場景里調用方法僅有少量的不同。它非常適合用來開發不需要刷新的單頁程序以及node.js應用。尤其單頁程序,對於不想介入手機APP開發的同學,可以利用這個實現類似APP的應用程序。
director.js 不依賴於任何其他的js庫,例如jquery等。但是它又和jquery能很好的融合在一起進行使用。
在本篇主要學習它在客戶端的應用,node.js服務器端暫不討論。
3、簡單的開始
上文簡單提到: director.js 是通過“#”符號進行路徑組織的,例如:
http://www.demo.com/#/show
http://www.demo.com/#/show/list
http://www.demo.com/#/show/single
路由注冊在URL里的體現是用“#”符號來標識路由的開始,再利用"/"分隔符(分隔符可自定義,后面會講到)來定義路由片段。客戶端路由其實就是通過URL來區分應用程序的不同狀態,並且定義在不同的狀態下應該做什么事情。當用戶訪問不同的URL時,director.js 會解析路由信息並告知應用程序需要做什么事情。
路由格式具體可參考下圖:
下面是一個簡單的例子:

<html> <head> <title>A Gentle Introduction</title> <script src="/js/director.min.js"></script> <script> var author = function () { console.log("author"); }, books = function () { console.log("books"); }, viewBook = function(bookId) { console.log("viewBook: bookId is populated: " + bookId); }; var routes = { '/author': author, '/books': [books, function() { console.log("An inline route handler."); }], '/books/view/:bookId': viewBook }; var router = Router(routes); router.init(); </script> </head> <body> <ul> <li><a href="#/author">#/author</a></li> <li><a href="#/books">#/books</a></li> <li><a href="#/books/view/1">#/books/view/1</a></li> </ul> </body> </html>
和jquery一起使用的列子:

<html> <head> <meta charset="utf-8"> <title>A Gentle Introduction 2</title> <script src="/js/jquery.min.js"></script> <script src="/js/director.min.js"></script> <script> $('document').ready(function(){ var showAuthorInfo = function () { console.log("showAuthorInfo"); }, listBooks = function () { console.log("listBooks"); }, allroutes = function() { var route = window.location.hash.slice(2), sections = $('section'), section; if ((section = sections.filter('[data-route=' + route + ']')).length) { sections.hide(250); section.show(250); } }; var routes = { '/author': showAuthorInfo, '/books': listBooks }; var router = Router(routes); router.configure({ on: allroutes }); router.init(); }); </script> </head> <body> <section data-route="author">Author Name</section> <section data-route="books">Book1, Book2, Book3</section> <ul> <li><a href="#/author">#/author</a></li> <li><a href="#/books">#/books</a></li> </ul> </body> </html>
下面將會就director.js 客戶端相關的API進行詳細的說明。
4、初始化及路由注冊
director.js 的主要對象是Router對象,構造方法如下:
var router = new Router(routes);
構造方法中傳入的routes參數是一個路由表對象,它是一個具有鍵值對結構的對象,路由允許多層的嵌套定義。
鍵值對的鍵對應URL中傳入的路徑,一般一個鍵對應按分隔符切割后的某一部分;而鍵值對的值則對應該路徑需要觸發的回調函數名,可以傳入一個或多個函數名,傳入多個函數名時請使用數組對象。一般來說,回調函數要在路由表對象使用前先聲明,否則js會報錯。
另外,回調函數除非特殊情況,一般不推薦使用匿名函數,請盡量先聲明后使用。
var routes = { '/dog': bark, '/cat': [meow, scratch] };
上面例子中,對應的URL分別為:#/dog 和 #/cat
聲明Router對象后,需要調用init()方法進行初始化,如下:
var routes = { '/dog': bark, '/cat': [meow, scratch] }; var router = Router(routes); router.init();
5、路由的即時注冊
當我們在開發一些規模比較大的應用的時候,一般做不到一開始就將需要的路徑和它對應的回調函數都預先准備好。很多時候,我們都是在做到某一功能時,或者是開發一些獨立性比較強耦合度比較低的模塊時,才知道我們需要什么樣的路徑和回調函數。這個時候我們就需要實時注冊路由的功能了。
director.js 通過“on”方法,提供對即時注冊功能的支持,示例如下:
var router = Router(routes).init(); .... router.on('/rabbit', function(){ ... })
6、路由事件
路由事件是路由注冊表中一個有固定命名的屬性,是指當路由方法router.dispatch()被調用時,路由匹配成功的時定義的需要觸發的回調方法(允許定義多個回調方法)。上文即時注冊功能里的"on"方法就是一個事件。具體信息如下:
- on :當路由匹配成功后,需要執行的方法
- before:在觸發“on”方法之前執行的方法
僅在客戶端有效的方法:
- after:當離開當前注冊路徑時,需要執行的方法
- once: 當前注冊路徑僅執行一次的方法
var routes = { "/about/:id": { before: function (id) { alert("direct to : /Home/About/" + id); }, on: function (id) { window.location = "/Home/About/" + id; } };
*以上示例中使用了匿名函數,僅因為做為示例比較方便,不推薦這種使用方式
7、配置參數
director.js 通過配置一些可選項的參數從而提升Router對象的靈活性。而這些參數的設置需要通過router.configure()方法實現。
var router = new director.Router(routes).configure(options);
具體的配置參數有:
- recurse:控制路由遞歸觸發方式的參數,可選值為"forward","backward"和"false",客戶端的默認值是"false",而服務端的默認值是"backward"
- strict:當值為"false"時,路徑允許以"/"結尾(也可以是其他自定義的分隔符);默認值是"true",說明默認不允許路徑以"/"結尾
- async:同步異步控制器,值為"ture","false",默認值為"false"
- delimiter:路由分隔符,默認值為"/"
- notfound:當路由方法router.dispatch()被調用時,沒有匹配到任何路由時觸發的方法
- on:當路由方法router.dispatch()被調用時,任何一個路由匹配成功后都需要執行的方法;與上文路由事件中的“on”事件的區別類似於全局和局部的概念,路由表中僅針對當前注冊的路由;而configure方法中的"on"則針對全局的所有路由
- before:當路由方法router.dispatch()被調用時,當任何一個路由匹配成功並在"on"執行之前需要執行的方法;與上文路由事件中的 “before” 事件的區別同上
僅在客戶端有效的參數:
- resource:用來進行回調函數綁定的基於字符串的對象。使用該參數能實現回調函數的延遲綁定(原詞是 "late-binding",后面有相關的詳細說明)
- after:當給定的路徑不再是當前激活的路徑時觸發的方法,可以理解為離開當前路徑后觸發的方法;與上文路由事件中的 “after” 事件的區別同上
*以上的配置參數在后面均有詳細的講解
*另外僅在客戶端起效的參數還有html5history和run_handler_in_init,這兩個參數是與html5相關,暫時也不討論
8、URL匹配
在路由事件那一節的示例里,有這么一個路由表達式"/about/:id",其中":"后面定義的部分表示實際路徑對應的這部分是傳入回調函數的參數,例如"#/about/5"中,5就是id參數的值。參數的匹配還可以用嵌套的方式來定義:
var router = Router({ '/about': { '/:id': { on: function (id) { console.log(id) } } } });
在實際應用的過程中,我們的路由可能會變得非常復雜,像"/about/:id"這樣簡單的表達式並不能滿足我們的需求。而director.js 支持利用正則表達式來匹配復雜的路由名稱,匹配到的值會作為參數傳給回調函數,例如:
var router = Router({ '/hello': { '/(\\w+)': { on: function (who) { console.log(who) } } } });
當URL傳入'#/hello/world',則回調函數的who=world
支持更為復雜的多參數的傳遞:
var router = Router({ '/hello': { '/world/?([^\/]*)\/([^\/]*)/?': function (a, b) { console.log(a, b); } } });
當URL傳入'#/hello/world/johny/appleseed',則回調函數的a=johny,b=applesee
9、路由的遞歸匹配
全局配置中的recurse參數決定了路徑在路由表中的命中方式以及命中順序。命中方式包括遞歸命中和精確命中;命中順序包括正序,反序和中斷。
當參數設定為“forward、backward”時,表明路由的命中方式是遞歸命中,並按照規定的順序命中,forward為正序,backward為反序。路由如果定義了多個路由片段,則它們對應的回調函數均可命中,也就是說一個路徑可以命中多個函數。
當沒有特別指定該參數的值時(即默認值"false"),則標明路由的命中方式為精確命中,需要完全匹配整個路徑后才可以命中,也就是說一個路徑只能命中一個函數。
當URL傳入"#/dog/angry"時,路由表注冊如下:
var routes = { '/dog': { '/angry': { on: growl }, on: bark } };
當沒有指定遞歸匹配的方式時,僅命中growl方法。
當指定遞歸匹配參數的值為backward時,首先命中growl方法,然后再命中bark方法,按照路徑的注冊順序反序命中。如下:
var router = Router(routes).configure({ recurse: 'backward' });
當指定遞歸匹配參數的值為forward時,首先命中bark方法,然后再命中growl方法,按照路徑的注冊順序正序命中。如下:
var router = Router(routes).configure({ recurse: 'forward' });
當指定了遞歸匹配參數后,如果命中的函數中使用了"return false;"的語句返回,則會中斷遞歸命中,直接返回。如下:
var routes = { '/dog': { '/angry': { on: function() { return false; } }, on: bark } }; var router = Router(routes).configure({ recurse: 'backward' });
無中斷時,首先會命中angry下的方法,然后命中bark方法;但是在使用return false語句之后,執行完angry命中的方法后將不再遞歸命中別的方法(bark)。
10、資源參數
全局配置中的resource參數僅在客戶端應用中可用,它是一個文本對象,其中的文本屬性值主要用來定義路由匹配命中后的回調方法名。它可以為程序提供更好的封裝性,以便更好的進行結構設計。
var router = Router({ '/hello': { '/usa': 'americas', '/china': 'asia' } }).configure({ resource: container }).init(); var container = { americas: function() { return true; }, china: function() { return true; } };
示例中,container對象定義在路由對象之后,我覺得這個就是之前說的“延遲綁定”功能。因為路由的回調函數綁定必須遵守先聲明后使用的方法,這個示例明顯是先使用后聲明。但是我測試了一下,這樣做會報錯,還是不能改變先聲明后使用的順序。因此延遲綁定的意思我還沒真正搞清楚。
11、小結
director.js 在客戶端應用的中文簡明教程暫時就是這些,有了這些知識做一個簡單的CRUD的DEMO程序是完全沒問題了。不過我在做這個DEMO的過程中,覺得還是沒有體會到director.js 使用的精髓。因為我在命中后調用了AJAX方法,我的問題是既然已經用了AJAX方法,由何必用director.js 呢?感覺還是得再詳細看一下《bootstrap + requireJS+ director+ knockout + web API = 一個時髦的單頁程序》這篇文章和它的示例才能體會director.js 的精髓。也感謝該文章作者的分享。
以后有時間再研究和補齊服務端應用的教程,以及在html5應用下的幾個配置參數和方法。需要了解更詳細信息的同學還是可以去它的 官方地址 啃E文。
Over!