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


    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;
  • 相关阅读:
    重力感应GSensor 方向介绍
    php图片保存、下载
    AJAX技术在PHP开发中的简单应用
    php 面向对象基础
    用PHP处理多个同名复选框
    去掉codeigniter地址中的index.php
    PHP中如何运用ini_set和ini_get()
    Windows 7下PHP配置环境
    zend_application 说明
    PHP写的域名查询系统whois
  • 原文地址:https://www.cnblogs.com/wangwei1314/p/5595958.html
Copyright © 2020-2023  润新知