backbone.js1.3.3------------------history和router

来源:http://www.cnblogs.com/wangwei1314/archive/2016/06/19/5595958.html
-Advertisement-
Play Games

backbone的router和history對象就是對window.history對象的操作。 學習backbone的router和history之前必須要學習window.history對象。html5給開發者添加了操作history的api。 這裡需要瞭解兩個概念: hash:個人理解,has ...


backbone的router和history對象就是對window.history對象的操作。

學習backbone的router和history之前必須要學習window.history對象。html5給開發者添加了操作history的api。

這裡需要瞭解兩個概念:

hash:個人理解,hash就是url最後#後面的東西,可以定位到頁面的某一個位置

state:是指push到window.history中的對象。

history可以在不刷新頁面的情況下修改url地址,這裡就使用了錨點。通過向history中添加錨點不同的url,這樣就可以做到通過瀏覽器的前進後退修改url而不刷新頁面(其實只是監聽事件後跳轉到錨點所在的位置)。

在ajax給我們帶來提高用戶體驗,減少http請求好處的同時,顯露出了一些不足:

1.無法使用瀏覽器的前進後退按鈕。

2.直接複製瀏覽器url,在新視窗中打開時不是我們想要的頁面。

3.單純的使用ajax不利於搜索引擎優化,因為搜索引擎無法獲取ajax請求的內容

可以利用history解決這個問題。

  1 // Backbone.Router
  2   // ---------------
  3 
  4   // Routers map faux-URLs to actions, and fire events when routes are
  5   // matched. Creating a new one sets its `routes` hash, if not set statically.
  6   var Router = Backbone.Router = function(options) {
  7     options || (options = {});
  8     this.preinitialize.apply(this, arguments);
  9     if (options.routes) this.routes = options.routes;
 10     this._bindRoutes();
 11     this.initialize.apply(this, arguments);
 12   };
 13 
 14   // Cached regular expressions for matching named param parts and splatted
 15   // parts of route strings.
 16   var optionalParam = /\((.*?)\)/g;
 17   var namedParam    = /(\(\?)?:\w+/g;
 18   var splatParam    = /\*\w+/g;
 19   var escapeRegExp  = /[\-{}\[\]+?.,\\\^$|#\s]/g;
 20 
 21   // Set up all inheritable **Backbone.Router** properties and methods.
 22   _.extend(Router.prototype, Events, {
 23 
 24     // preinitialize is an empty function by default. You can override it with a function
 25     // or object.  preinitialize will run before any instantiation logic is run in the Router.
 26     preinitialize: function(){},
 27 
 28     // Initialize is an empty function by default. Override it with your own
 29     // initialization logic.
 30     initialize: function(){},
 31 
 32     // Manually bind a single named route to a callback. For example:
 33     //
 34     //     this.route('search/:query/p:num', 'search', function(query, num) {
 35     //       ...
 36     //     });
 37     //
 38     //這裡調用了backbone.history.route方法將這個對象添加到了backbone.history的handlers中,這樣可在backbone.history的loadurl方法遍歷handlers並執行匹配的url
 39     route: function(route, name, callback) {
 40       if (!_.isRegExp(route)) route = this._routeToRegExp(route);
 41       if (_.isFunction(name)) {
 42         callback = name;
 43         name = '';
 44       }
 45       if (!callback) callback = this[name];
 46       var router = this;
 47       Backbone.history.route(route, function(fragment) {
 48         var args = router._extractParameters(route, fragment);
 49         if (router.execute(callback, args, name) !== false) {
 50           router.trigger.apply(router, ['route:' + name].concat(args));
 51           router.trigger('route', name, args);
 52           Backbone.history.trigger('route', router, name, args);
 53         }
 54       });
 55       return this;
 56     },
 57 
 58     // Execute a route handler with the provided parameters.  This is an
 59     // excellent place to do pre-route setup or post-route cleanup.
 60     execute: function(callback, args, name) {
 61       if (callback) callback.apply(this, args);
 62     },
 63 
 64     // Simple proxy to `Backbone.history` to save a fragment into the history.
 65     navigate: function(fragment, options) {
 66       Backbone.history.navigate(fragment, options);
 67       return this;
 68     },
 69 
 70     // Bind all defined routes to `Backbone.history`. We have to reverse the
 71     // order of the routes here to support behavior where the most general
 72     // routes can be defined at the bottom of the route map.
 73     _bindRoutes: function() {
 74       if (!this.routes) return;
 75       this.routes = _.result(this, 'routes');
 76       var route, routes = _.keys(this.routes);
 77       while ((route = routes.pop()) != null) {
 78         this.route(route, this.routes[route]);
 79       }
 80     },
 81 
 82     // Convert a route string into a regular expression, suitable for matching
 83     // against the current location hash.
 84     _routeToRegExp: function(route) {
 85       route = route.replace(escapeRegExp, '\\$&')
 86                    .replace(optionalParam, '(?:$1)?')
 87                    .replace(namedParam, function(match, optional) {
 88                      return optional ? match : '([^/?]+)';
 89                    })
 90                    .replace(splatParam, '([^?]*?)');
 91       return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
 92     },
 93 
 94     // Given a route, and a URL fragment that it matches, return the array of
 95     // extracted decoded parameters. Empty or unmatched parameters will be
 96     // treated as `null` to normalize cross-browser behavior.
 97     _extractParameters: function(route, fragment) {
 98       var params = route.exec(fragment).slice(1);
 99       return _.map(params, function(param, i) {
100         // Don't decode the search params.
101         if (i === params.length - 1) return param || null;
102         return param ? decodeURIComponent(param) : null;
103       });
104     }
105 
106   });
107 
108   // Backbone.History
109   // ----------------
110 
111   // Handles cross-browser history management, based on either
112   // [pushState](http://diveintohtml5.info/history.html) and real URLs, or
113   // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
114   // and URL fragments. If the browser supports neither (old IE, natch),
115   // falls back to polling.
116   var History = Backbone.History = function() {
117     this.handlers = [];
118     this.checkUrl = _.bind(this.checkUrl, this);
119 
120     // Ensure that `History` can be used outside of the browser.
121     if (typeof window !== 'undefined') {
122       this.location = window.location;
123       this.history = window.history;
124     }
125   };
126 
127   // Cached regex for stripping a leading hash/slash and trailing space.
128   var routeStripper = /^[#\/]|\s+$/g;
129 
130   // Cached regex for stripping leading and trailing slashes.
131   var rootStripper = /^\/+|\/+$/g;
132 
133   // Cached regex for stripping urls of hash.
134   var pathStripper = /#.*$/;
135 
136   // Has the history handling already been started?
137   History.started = false;
138 
139   // Set up all inheritable **Backbone.History** properties and methods.
140   _.extend(History.prototype, Events, {
141 
142     // The default interval to poll for hash changes, if necessary, is
143     // twenty times a second.
144     interval: 50,
145 
146     // Are we at the app root?
147     atRoot: function() {
148       var path = this.location.pathname.replace(/[^\/]$/, '$&/');
149       return path === this.root && !this.getSearch();
150     },
151 
152     // Does the pathname match the root?
153     matchRoot: function() {
154       var path = this.decodeFragment(this.location.pathname);
155       var rootPath = path.slice(0, this.root.length - 1) + '/';
156       return rootPath === this.root;
157     },
158 
159     // Unicode characters in `location.pathname` are percent encoded so they're
160     // decoded for comparison. `%25` should not be decoded since it may be part
161     // of an encoded parameter.
162     decodeFragment: function(fragment) {
163       return decodeURI(fragment.replace(/%25/g, '%2525'));
164     },
165 
166     // In IE6, the hash fragment and search params are incorrect if the
167     // fragment contains `?`.
168     getSearch: function() {
169       var match = this.location.href.replace(/#.*/, '').match(/\?.+/);
170       return match ? match[0] : '';
171     },
172 
173     // Gets the true hash value. Cannot use location.hash directly due to bug
174     // in Firefox where location.hash will always be decoded.
175     getHash: function(window) {
176       var match = (window || this).location.href.match(/#(.*)$/);
177       return match ? match[1] : '';
178     },
179 
180     // Get the pathname and search params, without the root.
181     getPath: function() {
182       var path = this.decodeFragment(
183         this.location.pathname + this.getSearch()
184       ).slice(this.root.length - 1);
185       return path.charAt(0) === '/' ? path.slice(1) : path;
186     },
187 
188     // Get the cross-browser normalized URL fragment from the path or hash.
189     getFragment: function(fragment) {
190       if (fragment == null) {
191         if (this._usePushState || !this._wantsHashChange) {
192           fragment = this.getPath();
193         } else {
194           fragment = this.getHash();
195         }
196       }
197       return fragment.replace(routeStripper, '');
198     },
199 
200     // Start the hash change handling, returning `true` if the current URL matches
201     // an existing route, and `false` otherwise.
202     //完成以下事情:
203     //options={root:'',
204     //hashChange:''預設為true,
205     //pushState:''預設為true}
206     //1.解析option初始化參數。
207     //2.監聽popstate和hashchange事件觸發則執行checkurl。若不支持,則定時執行checkurl
208     start: function(options) {
209       if (History.started) throw new Error('Backbone.history has already been started');
210       History.started = true;
211       // Figure out the initial configuration. Do we need an iframe?
212       // Is pushState desired ... is it available?
213       this.options          = _.extend({root: '/'}, this.options, options);
214       this.root             = this.options.root;
215       this._wantsHashChange = this.options.hashChange !== false;
216       this._hasHashChange   = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7);
217       this._useHashChange   = this._wantsHashChange && this._hasHashChange;
218       this._wantsPushState  = !!this.options.pushState;
219       this._hasPushState    = !!(this.history && this.history.pushState);
220       this._usePushState    = this._wantsPushState && this._hasPushState;
221       this.fragment         = this.getFragment();
222 
223       // Normalize root to always include a leading and trailing slash.
224       this.root = ('/' + this.root + '/').replace(rootStripper, '/');
225 
226       // Transition from hashChange to pushState or vice versa if both are
227       // requested.
228       if (this._wantsHashChange && this._wantsPushState) {
229 
230         // If we've started off with a route from a `pushState`-enabled
231         // browser, but we're currently in a browser that doesn't support it...
232         if (!this._hasPushState && !this.atRoot()) {
233           var rootPath = this.root.slice(0, -1) || '/';
234           this.location.replace(rootPath + '#' + this.getPath());
235           // Return immediately as browser will do redirect to new url
236           return true;
237 
238         // Or if we've started out with a hash-based route, but we're currently
239         // in a browser where it could be `pushState`-based instead...
240         } else if (this._hasPushState && this.atRoot()) {
241           this.navigate(this.getHash(), {replace: true});
242         }
243 
244       }
245 
246       // Proxy an iframe to handle location events if the browser doesn't
247       // support the `hashchange` event, HTML5 history, or the user wants
248       // `hashChange` but not `pushState`.
249       if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) {
250         this.iframe = document.createElement('iframe');
251         this.iframe.src = 'javascript:0';
252         this.iframe.style.display = 'none';
253         this.iframe.tabIndex = -1;
254         var body = document.body;
255         // Using `appendChild` will throw on IE < 9 if the document is not ready.
256         var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow;
257         iWindow.document.open();
258         iWindow.document.close();
259         iWindow.location.hash = '#' + this.fragment;
260       }
261 
262       // Add a cross-platform `addEventListener` shim for older browsers.
263       var addEventListener = window.addEventListener || function(eventName, listener) {
264         return attachEvent('on' + eventName, listener);
265       };
266 
267       // Depending on whether we're using pushState or hashes, and whether
268       // 'onhashchange' is supported, determine how we check the URL state.
269       if (this._usePushState) {
270         addEventListener('popstate', this.checkUrl, false);
271       } else if (this._useHashChange && !this.iframe) {
272         addEventListener('hashchange', this.checkUrl, false);
273       } else if (this._wantsHashChange) {
274         this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
275       }
276 
277       if (!this.options.silent) return this.loadUrl();
278     },
279 
280     // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
281     // but possibly useful for unit testing Routers.
282     stop: function() {
283       // Add a cross-platform `removeEventListener` shim for older browsers.
284       var removeEventListener = window.removeEventListener || function(eventName, listener) {
285         return detachEvent('on' + eventName, listener);
286       };
287 
288       // Remove window listeners.
289       if (this._usePushState) {
290         removeEventListener('popstate', this.checkUrl, false);
291       } else if (this._useHashChange && !this.iframe) {
292         removeEventListener('hashchange', this.checkUrl, false);
293       }
294 
295       // Clean up the iframe if necessary.
296       if (this.iframe) {
297         document.body.removeChild(this.iframe);
298         this.iframe = null;
299       }
300 
301       // Some environments will throw when clearing an undefined interval.
302       if (this._checkUrlInterval) clearInterval(this._checkUrlInterval);
303       History.started = false;
304     },
305 
306     // Add a route to be tested when the fragment changes. Routes added later
307     // may override previous routes.
308     route: function(route, callback) {
309       this.handlers.unshift({route: route, callback: callback});
310     },
311 
312     // Checks the current URL to see if it has changed, and if it has,
313     // calls `loadUrl`, normalizing across the hidden iframe.
314     //最終會執行loadURL
315     checkUrl: function(e) {
316       var current = this.getFragment();
317 
318       // If the user pressed the back button, the iframe's hash will have
319       // changed and we should use that for comparison.
320       if (current === this.fragment && this.iframe) {
321         current = this.getHash(this.iframe.contentWindow);
322       }
323 
324       if (current === this.fragment) return false;
325       if (this.iframe) this.navigate(current);
326       this.loadUrl();
327     },
328 
329     // Attempt to load the current URL fragment. If a route succeeds with a
330     // match, returns `true`. If no defined routes matches the fragment,
331     // returns `false`.
332     //在router中尋找
333     loadUrl: function(fragment) {
334       // If the root doesn't match, no routes can match either.
335       if (!this.matchRoot()) return false;
336       fragment = this.fragment = this.getFragment(fragment);
337       return _.some(this.handlers, function(handler) {
338         if (handler.route.test(fragment)) {
339           handler.callback(fragment);
340           return true;
341         }
342       });
343     },
344 
345     // Save a fragment into the hash history, or replace the URL state if the
346     // 'replace' option is passed. You are responsible for properly URL-encoding
347     // the fragment in advance.
348     //
349     // The options object can contain `trigger: true` if you wish to have the
350     // route callback be fired (not usually desirable), or `replace: true`, if
351     // you wish to modify the current URL without adding an entry to the history.
352     navigate: function(fragment, options) {
353       if (!History.started) return false;
354       if (!options || options === true) options = {trigger: !!options};
355 
356       // Normalize the fragment.
357       fragment = this.getFragment(fragment || '');
358 
359       // Don't include a trailing slash on the root.
360       var rootPath = this.root;
361       if (fragment === '' || fragment.charAt(0) === '?') {
362         rootPath = rootPath.slice(0, -1) || '/';
363       }
364       var url = rootPath + fragment;
365 
366       // Strip the fragment of the query and hash for matching.
367       fragment = fragment.replace(pathStripper, '');
368 
369       // Decode for matching.
370       var decodedFragment = this.decodeFragment(fragment);
371 
372       if (this.fragment === decodedFragment) return;
373       this.fragment = decodedFragment;
374 
375       // If pushState is available, we use it to set the fragment as a real URL.
376       if (this._usePushState) {
377         this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
378 
379       // If hash changes haven't been explicitly disabled, update the hash
380       // fragment to store history.
381       } else if (this._wantsHashChange) {
382         this._updateHash(this.location, fragment, options.replace);
383         if (this.iframe && fragment !== this.getHash(this.iframe.contentWindow)) {
384           var iWindow = this.iframe.contentWindow;
385 
386           // Opening and closing the iframe tricks IE7 and earlier to push a
387           // history entry on hash-tag change.  When replace is true, we don't
388           // want this.
389           if (!options.replace) {
390             iWindow.document.open();
391             iWindow.document.close();
392           }
393 
394           this._updateHash(iWindow.location, fragment, options.replace);
395         }
396 
397       // If you've told us that you explicitly don't want fallback hashchange-
398       // based history, then `navigate` becomes a page refresh.
399       } else {
400         return this.location.assign(url);
401       }
402       if (options.trigger) return this.loadUrl(fragment);
403     },
404 
405     // Update the hash location, either replacing the current entry, or adding
406     // a new one to the browser history.
407     _updateHash: function(location, fragment, replace) {
408       if (replace) {
409         var href = location.href.replace(/(javascript:|#).*$/, '');
410         location.replace(href + '#' + fragment);
411       } else {
412         // Some browsers require that `hash` contains a leading #.
413         location.hash = '#' + fragment;
414       }
415     }
416 
417   });
418 
419   // Create the default Backbone.history.
420   Backbone.history = new History;

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 作者:Sungeek 出處:http://www.cnblogs.com/Sungeek/ 歡迎轉載,也請保留這段聲明。謝謝! 簡介: Nginx ("engine x") 是一個高性能的 HTTP 和 反向代理 伺服器,也是一個 IMAP/POP3/SMTP 伺服器。 Nginx 是由 Igor ...
  • 1、 打開eclipse,在File上New,然後選擇Dynamic Web Project 2、 彈出的頁面中如下圖,在Project name中輸入項目名稱JavaWeb01,點擊Next 3、 Eclipse新建web項目時編譯輸出目錄跟MyEclipse是不一樣的,eclipse中新建的we ...
  • 以上是try...catch的語法; try語句嘗試執行一個語句塊,遇到錯誤則退出try語句塊而不是中斷aardio程式。如果使用了catch語句塊就可以捕獲異常(catch語句塊是可選的)。 以上是在aardio使用手冊中的一段話。 也許在使用try catch的過程中我們常常想出現錯誤的時候直接 ...
  • 1.自定義模板 在IAR系統中,edit->code templates 可以編輯與使用自定義模板 通過使用生成 H文件與C文件來生成各個功能模塊 2.模塊文件 #####################################################################... ...
  • 棧(stack)是一種線性存儲結構,有以下特點: 1.棧中數據是按照先進後出的方式進出棧的 2.向棧中添加刪除元素時,只能從棧頂進行操作 使用數組實現棧 定義一個類ArrayStack 實現入棧方法push() 實現出棧方法pop() 實現返回棧頂元素方法peek() ...
  • 隊列是一種線性存儲結構,他有以下特點: 1.隊列中數據是按照“先進先出”方式進出隊列的 2.隊列只允許在“隊首”進行刪除操作,在“隊尾”進行插入操作 3.隊列通常包含兩種操作:入隊列和出隊列 使用數組實現隊列 定義一個類ArrayQueue 實現入隊列方法push() 實現出隊列方法pop() 實現 ...
  • 本文是《緩存在分散式系統中的應用》第三篇文章。上次主要給大家分享了,緩存在分散式系統中的應用,主要從不同的場景,介紹了CDN,反向代理,分散式緩存,本地緩存的常規架構和基本原理。因為時間關於,原計劃分享《緩存常見問題》的內容,沒有講。本次主要針對緩存的常見個問題,做一個介紹。 ...
  • 先簡要概述一下video標簽: video:嵌入視頻到頁面中 1. 聲明video標簽 單個視頻的時候使用src: Your browser does not support the video element. 多個視頻的時候使用標簽: Your browser does not support ... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...