“This article has participated in the call for good writing activities, click to view: the back end, the big front end double track submission, 20,000 yuan prize pool waiting for you to challenge!”

Concepts of routing

In fact, in the early years, there was no concept of front-end routing, but with the development of the front-end, there was front-end routing. (Lu Xun said)

In the early days of template engine development, you would see a page URL like this: http://test.xxx.help.cn/bbs/sage.php or http://home.xxx.cn/bbs/sage.html to like this. PHP or are. The path of the HTML, is through the server side rendering, returned to a page directly. While this is good for SEO search, the code is not easy to maintain, and it also puts pressure on the server.

Ajax and SPA

The development of front-end routing is inseparable from the popularity of Ajax. Back in the HTTP0.9 (and earlier) era, you returned an HTML page directly. Each update requires a page refresh, which affects the interactive experience. With the development of the network, there is an urgent need for a scheme to improve the situation. With Ajax, user interaction doesn’t have to refresh the page every time, and the experience is vastly improved. Along came the single page application SPA.

What is SPA?

To put it simply; In SPA application, there is only one hTML page, and the hTML page contains an APP, that is, a placeholder. The switching of the page is within the APP, that is, the switching of view. As shown in figure:

The page can be changed by switching the view. But there is a problem: simply by using JAVASCRIPT and HTML, the page URL does not change, the browser does not remember the user’s actions. The user’s back, forward operations are invalid, very embarrassing. And the URL does not change, a page only a URL, is not conducive to SEO. Egg pain.. In order to solve the above problems, the concept of front-end routing is introduced. As long as you can listen for URL changes without sending requests, you can do a simple front-end routing. There are two ways to implement front-end routing: Hash and History.

Hash pattern

The earliest routing solution for hash, www.baidu/#/xx/xx, changes the URL by changing the hash value after the anchor point # without refreshing the page. Using window.location.hash, you can get the hash value and then listen for the hashChange event. When the hash value is updated when the hasChange event is triggered, the front-end routing function can be implemented. Ok, not much bb. Let’s implement it:

Implement a hash

The idea is to maintain an object that stores path(for matching hash) and View (for rendering when matching hash).

    class MyHashRouter {
           constructor() {
               this.router = {}; // Store the registered route
           }
           window.addEventListener('hashChange'.this.loadView.bind(this), false);
    }
Copy the code

Quite simply, loadView is triggered by listening for changes in the hashChange event (by routing matches the View). Note the Router object:

    router {
        path: "/XX".// Route path
        callBack: () = > {} // An execution function (to display the view)
    }
Copy the code

Implement the registered route:

    registerRouter(path, callBack = ()=>{}) {
        this.router[path] = callBack; 
                    // A route corresponds to a view(a turnip and a pit)
    }
Copy the code

One caveat: if path does not exist, we default to /, which is the home page.

    registerIndexRouter(callBack = () => {}) {
        this.router['index'] = callBack;   // index is the home page by default
    }
Copy the code

ok! Simple route registration is complete. Now let’s think about route switching view switching. A tag is used to implement the HASH change of the URL. During the hash change, we obtain the hash and match the registered route with the hash. Code to go:!

    loadView() {
        let hash = window.location.hash.slice(1); // remove the # sign
        let cb;  // Execute function (view)
        hash ? cb = this.router[hash] : cb = this.router.index;
        // Hash exists: find corresponding view, otherwise corresponding index
        cb.call(this);  / / execution
    }
Copy the code

Such a simple hash route is complete. Note one problem: If the route is not matched, the page 404 is redirected by default.

    hashNotFound() {
        if (!this.router.hasOwnProperty(hash) {
             cb = this.routers['404'] | |function(){}; }}Copy the code

Code is as follows:

    class MyHashRouter {
        constructor(){
            this.router = {};
            window.addEventListener('hashChange'.this.loadView.bind(this), false);
        }
        registerIndexRouter(callBack = () => {}) {
            this.router['index'] = callBack;
        }
        registerRouter(path, callBack = () => {}) {
            this.router[path] = callBack;
        }
        registerNotFound(callback = () => {}){
            this.routers['404'] = callback;   / / registered 404
        }
        loadView() {
            let hash = window.location.hash.slice(1);
            let cb;
            if(! hash) { cb =this.router.index;
            }
            else if (!this.router.hasOwnProperty(hash) && hash) {
                cb = this.router['404'];
            }
            else {
                cb = this.router.hash;
            }
            cb.call(this); }}Copy the code

We can play with it simply:

            let router = new MyHashRouter();
            let app = document.getElementById('app');

            / / home page
            router.registerIndexRouter(() = > app.innerHTML = 'home');

            // Register other views
            router.registerRouter('/path1'.() = > app.innerHTML = 'view1');
            router.registerRouter('/path2'.() = > app.innerHTML = 'view2');
            router.registerRouter('/path3'.() = > app.innerHTML = 'view3');
            router.registerRouter('/path4'.() = > app.innerHTML = 'view4');
            router.registerNotFound(() = > app.innerHTML = 'Page not found'); / / 404
            router.loadView();  // Load the view
Copy the code

The history mode

history api

The DOM Window object provides access to the browser’s session history through the History object (not to be confused with WebExtensions History). It exposes a number of useful methods and properties, allows you to jump forward and backward through a user’s browsing history, and — starting with HTML5 — provides manipulation of the contents of the History stack. –MDN

                window.history.back() === Browser rollbackwindow.history.forward() === browser forwardwindow.history.go(-1) === move one page back === back()window.history.go(1) === move a page forward === forward() go()2/-2) === jumps to a point specified in historyCopy the code

New attributes history.pushState() and history.replacEstate () have been added to HTML5 to add and modify history entries, respectively. Note: This method works with window.onpopState.

History. pushState(state, title, url) and history.replaceState(state, title, url) accept three arguments:

  1. State: A legal Javascript object that can be used in popState events
  2. Null (…)
  3. Url: Any valid URL used to update the browser’s address bar

PushState () stores the existing URL on the stack. Calling pushState() is similar to setting window.location = “#foo”; both create and activate a new history on the current page. But pushState has the following advantages:

  1. The new URL can be any URL of the same origin as the current URL.
  2. If you don’t want to change the URL, don’t change it. Instead, set window.location = “#foo”; A new history entry can only be created if the current hash is not #foo.

ReplaceState () replaces the current page history in history with the URL. Ok! We now know that replaceState() and pushState() can change the URL without a page refresh. So we can use the history properties to implement front-end routing. Here’s the problem:

In hash mode, we change the URL by listening for the hashChange event, change the view. But in History mode, there is no way to listen for history. How to do ah!!

The way to do it is, we intercept all the methods that cause history changes by listening for them. In this way, you can intercept history in disguise.

In HTML5, history changes can be triggered in the following ways:

  1. A Label click to jump
  2. Browser rollback, forward event.
  3. The pushState() and replaceState() methods are triggered.

Ok, now let’s briefly implement the history schema. The hash mode for registering router objects is basically the same as the previous one, but there are only more BB’s here. Basically intercepting methods that can trigger a history change.

Generic processing paths and views

There’s nothing to say. Path changes view changes.

    commonDealPath(path) {
        let view;
        if(!this.router.hasOwnProperty(path)){
            view = this.routers['404'] | |function(){};
        }
        else{
            view = this.routers[path];   // There is a corresponding path
        }
        view.call(this);
    }
Copy the code

Listen for link A globally

1. Get the href value of the a tag. 2. Block the default event of tag A.

    listenerAClick() {
        window.addEventListener('click'.(e) = > {
           if (e && e.target) {
               let target = e.target;  // Get the click event
               if(target.tagName.toUpperCase() === 'A' && target.getAttribute('href')) {let path = target.getAttribute('href')); // Get the href attribute of the a tag.
                   history.pushState({path}, null, path);   // Change the URL with pushState.
                   this.commonDealPath(path);  / / triggers commonDealPath}}}}Copy the code

Listening for Browser events

Listening for browser forward and backward, that is, listening for popState events.

    listenPop() {
        window.addEventListener('popState'.() = > {
            let state = e.state || {};
            let path = state.path || "";
            this.commonDealPath(path);  / / triggers commonDealPath
        }, false)}Copy the code

Intercept pushState() and replaceState()

So let’s do these two methods separately. Trigger view changes while listening.

    pathpush(path) {
        history.pushState({path},null,path);
        this.commonDealPath(path);
    }
    
    pathReplace(path) {
        history.replaceState({path},null,path);
        this.commonDealPath(path);
    }
Copy the code

ok! Simple Histroy mode is done! In a word: Listen for the method that triggers the history change, then change View.

compatibility

hash:

history:

Hash is more compatible than Hsitory.

The last

Simple analysis of a wave of hash and history. The Vue router and react Router will be shared next time. Thank you!!!!!