博客 / 詳情

返回

Backbone源碼解讀(三)

注意:強烈建議一邊閲讀源碼一邊閲讀本文。

終於到了backbone源碼解讀的最後一篇,這一篇和前面幾篇時間上有一定的間隔(因為要回學校有一堆亂七八糟的事...)。在這一篇裏面會講解Bakcbonesync & router & histrorysync比較簡單,但是路由的部分就比較複雜了。個人覺得是整個backbone源碼裏面最不好懂的一個部分,這個部分也使得backbone可以方便實現可以“返回”的單頁面應用。個人覺得這個部分其實並沒有很MVC有很密切的關係,但是它非常重要。讀過源碼就會發現,其實這一個部分與其他的模塊(ModelCollection & View)相對獨立。如果不希望使用backbone但希望能用到這個路由系統的話估計拆出來難度也不會很大。

總體來説路由模塊由RouterHistrory組成,而Router事實上可以看成是對History的一個封裝。用户直接操作的部分時Router,相關函數的處理(路由匹配時調用的函數)也是在Router中完成。History主要是處理一個更加棘手的問題,就是有關鏈接的問題。這裏面有關於跨瀏覽器的解決方案是非常值得學習的。

1. Router

Router是對History的封裝,也是給用户定義路由的接口。一般來説,用户在使用Router的時候會定義一個routers的對象,裏面是想關路由與處理函數組成的key-valueRouter代碼中主要做兩件事,一件是對正則表達式的操作(可以看見裏面很多令人痛苦的正則表達式),另一件事就是事件相關的綁定了。Router的相關代碼不多,寫得比較精簡。

1.1 函數綁定與執行

Router裏面定義了兩種方法來定義路由事件,一個方法是用户定義routers,通過初始化調用_bindRoutes函數;一個方法是用自帶的route函數。其實兩個方法本質上都是調用了route函數_bindRoutes裏面實際上也是通過循環來調用route函數。因此路由函數重點在於route函數。

route函數裏面,一開始是進行一些參數的整理。然後就是調用了History模塊的route函數,把正則(匹配參數用的)和一個回調函數傳了進去。在回調函數裏面就是做 執行函數——觸發router的事件——觸發history的事件這幾個步驟,在Historyroute函數裏面,只是簡單的插入地把keycallback組成對象插入handlers數組裏面而已。

1.2. 正則表達式轉化與使用

Router函數裏面最難懂也非常重要的部分是格式的轉換。在Router裏面有兩個重要的函數_routeToRegExp函數和_extractParameters兩個,這兩個函數與正則密切相關。

_extractParameters函數的作用是利用正則表達式,把傳進來的url片段fragment分割成片段存進數組當中。這些片段是真實的已經匹配出來的參數,在route函數裏面會把這些參數傳給用户定義的函數裏面,供用户使用。

_routeToRegExp函數是一個簡單,但要完全理解很難的函數。這個函數的作用就是返回一個RegExp對象,通過這一個對象來匹配當前的鏈接,然後從中得到參數。進入這個函數之後會通過字符串的replace函數,匹配出路由的是哪幾(或一)種情況,並且替代成可以捕獲參數的正則字符串。比如説把路由定義裏的/:page或者*fragment這樣的字符串通過事先定義好的幾個正則匹配到,然後換成帶()的可以捕獲的形式,然後在創建正則去捕獲真正需要的常數。

2. History

backbone中對於History模塊的使用是通過用構造方式調用(new)返回一個可以使用prototype方法的對象來實現的。Backbone.history = new History; 這個模塊非常重要,而且在整個backbone裏面可以説是最難完全讀懂的。下面我會從三個方面來講:一個方面是有關於路徑格式處理的問題,在這方面也有很多和正則表達式相關的函數;另一個方面是最關鍵的一個方面,就是History檢測瀏覽器來使用不同的路由控制方式;最後一個方面就是通過具體的函數來講解它是如何實現第二點各方面所説的控制的。

History從接口的角度來説有start函數作為初始化的設置,還有通過Router模塊封裝的navigate方法。Router裏面的很多處理需要調用到這個模塊的方法。

History事實上也是對location/history一定程度上的封裝。很多時候是通過location模塊來讀取匹配,通過history的一些方法來進行路由控制。

2.1 路徑格式處理

2.1.1 root

用户可以設置root,作為根路徑。這個根路徑在模塊中有一些判斷和處理的地方。比方説確定當前是否在根路徑,或者在當前URL提取出相應的錨點等等都需要用到root這個內部變量。

2.1.2 getFragment

在這個函數裏面我們可以看到URL的格式分為了兩種。一種是hash方式,一種是search方式(主要是兼容較老的瀏覽器)。在這裏通過判斷來進行瀏覽器能力檢測。對於大部分現代瀏覽器來説,事實上大都是使用hash方式獲取錨點#後面的URL片段。

2.2 路由控制的三種方法(核心)

2.2.1 onhashchange方式

這種方式是通過監聽'hashchange'事件,然後觸發事件,用location.hash.replace方法來改變路由。

2.2.2 pushstate方式

這種方式是最為推薦的HTML5方式。使用historypushState方法修改history裏的記錄,然後也可以通過監聽popstate來觸發一些相關的事件。

2.2.3 iframe方式

這是一個非常巧妙但是從某種程度上非常“醜陋”的方法。醜陋是在它比較吃性能,一方面它有很多dom的操作來設置iframe,最重要的方面是它還用了定時器每隔一小段時間就檢測,然後就觸發函數,判斷是否改變等等。插入一個空的iframe(經過屬性設置)的作用在這裏是存儲hash的值和存儲hash改變記錄。在這裏我遇到了一個問題:存儲hash的值完全可以通過一個全局變量來完成,為什麼要大費周折創建一個iframe呢?下面是個人的一些猜測:

Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.

這是源碼中的一句註釋。用iframe的理由可能是為了通過開關iframe來存儲記錄。説實話具體是什麼原理還不清楚,如果有人瞭解的話歡迎指教~

// 開關`iframe`
iWindow.document.open();
iWindow.document.close();

2.3 實現(start, navigate & 事件)

2.3.1 start

start主要做如下操作:

  • 進入start函數之後會把started設置為true防止重複出發。

  • 設置各種參數,用於後期判斷使用哪一種路由控制方式。

  • 如果有hashchange事件,但沒有pushState方法,就用location.replace方法來改變路由。如果兩者都有就調用navigate函數,裏面可以通過pushState改變並記錄路由。如果兩者都沒有就設置iframe並啓動,通過設置iframehash參數來改變路由。

  • 綁定事件,用hashChange方法的綁定hashchange事件,用pushState方法的綁定popstate事件,用iframe的使用setInterval來監聽。

2.3.2 navigate

進入函數之後首先是進行“組合”,“組合”出url。這個過程需要有rootfragment,後者需要調用getFragment函數,前者需要根據是path還是hash來對root進行處理。如果是hash就不需要加/,如果是path就要加/。解碼後判斷當前的this.fragment和有沒有發生變化,沒有不管,有就更新。

根據瀏覽器使用不同的方法。注意這裏使用的判斷的依據是在start函數裏面就定義好的。

  • 如果有pushState或者replaceState就用;

  • 如果有hashchange就僅僅只調用_upadateHash,傳入當前的location bom對象,裏面用了location.replace,更新當前的href或者hash

  • 如果沒有hashchange就需要把當前locationiframe.location對象分別傳入_updateHash,然後更新當前href或者hash

    還有一個需要注意的是是否replace,這是一個傳入參數,判斷時候要影響history

2.3.3 事件

關於路由觸發事件是通過兩個函數來完成的,它們分別是checkUrlloadUrl, 前者會檢測路由是否發生了改變,如果改變了就會觸發navigate函數並調用loadUrl函數,而後者會通過路由片段來找到handlers相關的事件函數來觸發。這就實現了用户在routes對象裏面設置的事件了。

3. Sync

最後來個簡單的Sync的講解吧~有關ajax的部分在backbone中其實是通過Backbone.ajax函數來代理jquery或者其他可以發起ajax的庫的。而Sync函數事實上主要的工作就是部署ajax參數,最後調用這個Backbone.ajax發起請求。通過源碼可以看到,其中為params設置了type, dataType, url, contentType, data, (processData)屬性來作為發起ajax的參數。其中也為options設置了beforeSend, error方法作為ajax的回調(success函數寫在其他模塊中,詳情可以看我之前的幾篇文章)。

其中還需要主要的有兩個參數emulateJSON & emulateHTTP。在文檔中的介紹非常詳細,個人覺得在大部分時候都不會用到。

4. 最後的話

終於把最後的第三篇文章寫出來了...花了很長時間...還是覺得如果要真正完全讀懂backbone源碼要多讀代碼(慚愧,自認為還沒完全達到),多查資料,多讀一些源碼解析。有關於routerhistory我覺得這篇文章還是很棒的。這一個部分個人感覺確實不是很好懂,但是可以學習到很多有關路由的處理的相關知識,其實是非常有益的~

backbone被稱為框架的框架。這個框架的思想比起使用更有意義,畢竟現在有更多功能強大的框架。新東西要學,但是經典也是不應該被落下的。

如果這篇文章有什麼錯誤的地方請輕噴~互相學習!謝謝大家。

下面是全部的文章:
基於 Backbone + node 的個人簡歷生成器(個人學習總結)
Backbone源碼解讀(一)
Backbone源碼解讀(二)
Backbone源碼解讀(三)

user avatar zzd41 頭像 peter-wilson 頭像 huishou 頭像 ziyeliufeng 頭像 dujing_5b7edb9db0b1c 頭像 flymon 頭像 sunhengzhe 頭像 uncletong_doge 頭像 201926 頭像
9 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.