簡單的基於hash和hashchange的前端路由

hash定義

hash這個玩意是地址欄上#及後面部分,表明網頁中的一個位置,#後面部分爲位置標識符。頁面打開後,會自動滾動到指定位置處。
位置標識符 ,一是使用錨點,好比<a name="demo"></a>,二是使用id屬性,好比 <span id="demo" ></span>javascript

帶hash的請求

當打開http://www.example.com/#print服務器實際收到的請求地址是http://www.example.com/,是不帶hash值的。
那麼你真想帶#字符咋辦,轉義啊, #轉義字符爲%23。也許有人會說,我咋知道這個轉義啊,呵呵噠encodeURIComponent。html

hashchange事件

The HashChangeEvent interface能夠看到hashchange事件的參數HashChangeEvent繼承了Event,僅僅多了兩個屬性前端

  • oldURL 先前會話歷史記錄的URL
  • newURL 當前會話歷史記錄的URL
    簡單的調用方式
window.onhashchange = function(e){
        console.log('old URL:', e.oldURL)
        console.log('new URL', e.newURL)
    }

hash | CAN I USE 上能夠看到除了IE8一下和那個尷尬的Opera Mini,hashchange事件都是支持得很好。那麼怎麼作到兼容,用MDN的代碼作個引子vue

function(window) {  
  if ("onhashchange" in window) { return; }

  var location = window.location,
    oldURL = location.href,
    oldHash = location.hash;

  setInterval(function() {
    var newURL = location.href, newHash = location.hash;
    if (newHash != oldHash && typeof window.onhashchange === "function") {     
      window.onhashchange({
        type: "hashchange",
        oldURL: oldURL,
        newURL: newURL
      });

      oldURL = newURL;
      oldHash = newHash;
    }
  }, 100);
}

hash history 簡單版本實現

從上面能夠得知,咱們的實現思路就是監聽hashchange事件,這裏先拋開兼容性問題。
1 首先監聽hashchange事件,定義個RouterManager函數java

  • bind(this)讓函數this指向RouterManager實例
  • 取到oldURL和newURL,同時查找一下是否註冊,而後加載相關路由
function RouterManager(list, index) {
        if (!(this instanceof RouterManager)) {
            return new RouterManager(arguments)
        }
        this.list = {} || list
        this.index = index
        this.pre = null
        this.current = null

        win.addEventListener('hashchange', function (ev) {
            var pre = ev.oldURL.split('#')[1],
                cur = ev.newURL.split('#')[1],
                preR = this.getByUrlOrName(pre),
                curR = this.getByUrlOrName(cur)

            this.loadWithRouter(curR, preR)

        }.bind(this))
    }

2 定義添加,刪除,加載,和初始化等方法react

  • add的時候,判斷是否是string, 若是是,從新構造一個新的router實例配置
  • load這裏主要是用來還原直接輸入帶hash的地址,好比 http://ex.com/#music
  • loadWithRouter是最終渲染的入口
  • getByUrlOrName,你能夠經過名字和path查找路由,name是方便往後擴展
  • setIndex設置默認路由地址
  • go, back, forward同history的方法
  • init裏面會檢測地址是否是帶hash,而後走不通的邏輯。history.replaceState這是由於,若是不這麼作, http://ex.com/跳轉到http://ex.com/#/music會產生兩條歷史記錄,這是咱們不指望的。
RouterManager.prototype = {
        add: function (router, callback) {
            if (typeof router === 'string') {
                router = {
                    path: router,
                    name: router,
                    callback: callback
                }
            }
            this.list[router.name || router.path] = router
        },
        remove: function (name) {
            delete this.list[name]
        },
        get: function (name) {
            return this.getByUrlOrName(name)
        },
        load: function (name) {
            if (!name) {
                name = location.hash.slice(1)
            }
            var r = this.getByUrlOrName(name)
            this.loadWithRouter(r, null)
        },
        loadWithRouter(cur, pre) {
            if (cur && cur.callback) {
                this.pre = this.current || cur
                cur.callback(cur, pre)
                this.current = cur
            } else {
                this.NOTFOUND('未找到相關路由')
            }
        },
        getByUrlOrName: function (nameOrUrl) {
            var r = this.list[nameOrUrl]
            if (!r) {
                r = Object.values(this.list).find(rt => rt.name === nameOrUrl || rt.path === nameOrUrl)
            }
            return r
        },
        setIndex: function (nameOrUrl) {
            this.indexRouter = this.getByUrlOrName(nameOrUrl)
        },
        go: function (num) {
            win.history.go(num)
        },
        back: function () {
            win.history.back()
        },
        forward: function () {
            win.history.forward()
        },
        init: function () {
            // 直接輸入是帶hash的地址,還原
            if (win.location.hash) {
                /* 模擬事件
                var ev = document.createEvent('Event')
                ev.initEvent('hashchange', true, true)
                ev.oldURL = ev.newURL = location.href
                win.dispatchEvent(ev) */
                this.load()
            } else if (this.indexRouter) { // 是不帶hash的地址,跳轉到指定的首頁
                if ('replaceState' in win.history) {
                    // 替換地址
                    win.history.replaceState(null, null, win.location.href + '#' + this.indexRouter.path)
                } else {
                    win.location.hash = this.indexRouter.path
                }
            }
        }
    }

3 公佈函數git

RouterManager.prototype.use = RouterManager.prototype.add
    win.Router = RouterManager

4 頁面怎麼配置,簡單的利用a標籤hrefgithub

<ul>
    <li>
        <li>
            <a href="#/m1">菜單1</a>
        </li>
        <ul>
            <li>
                <a href="#/m11">菜單11</a>
            </li>
            <li>
                <a href="#/m12">菜單12</a>
            </li>
        </ul>
    </li>
    <li>
        <a href="#/m2">菜單2</a>
    </li>
    <li>
        <a href="#/m3">菜單3</a>
    </li>
</ul>

5 註冊,固然你也能夠經過選擇器批量註冊web

var router = new Router()
router.NOTFOUND = function (msg) {
    content.innerHTML = msg
}
router.use('/m1', function (r) {
    req(r.path.slice(1))
})
router.use('/m11', function (r) {
    req(r.path.slice(1))
})
router.use('/m12', function (r) {
    req(r.path.slice(1))
})
router.use('/m2', function (r) {
    req(r.path.slice(1))
})
router.use('/m3', function (r) {
    req(r.path.slice(1))
})
router.setIndex('/m1')
router.init()

爲了方便演示,定義req,ajax方法,模擬ajax請求ajax

function req(url) {
    ajax(url, function (res) {
        content.innerHTML = res
    })
}

function ajax(id, callback) {
    callback(
        {
            'm1': '菜單1的主區域內容',
            'm11': '菜單11的主區域內容',
            'm12': '菜單12的主區域內容',
            'm2': '菜單2的主區域內容',
            'm3': '菜單3的主區域內容'
        }[id] || '404 Not Found!')
}

6 demo地址

hash-Router1.0

7 源碼地址
簡單的前端hash路由

8 下一步
這就成了最簡單最基本的路由了。讓然還有不少要考慮,好比以下

  1. 動態路由匹配
  2. 嵌套路由
  3. 重定向和別名
  4. 錯誤捕捉
  5. 生命週期鉤子
  6. 等等等

hash | CAN I USE
The HashChangeEvent interface
onhashchange | MDN
window.location.hash 使用說明
JS單頁面應用實現前端路由(hash)
Ajax保留瀏覽器歷史的兩種解決方案(Hash&Pjax)
理解瀏覽器的歷史記錄
理解瀏覽器歷史記錄(2)-hashchange、pushState
Web開發中 前端路由 實現的幾種方式和適用場景
本身動手寫一個前端路由插件
vue-router
react-router

相關文章
相關標籤/搜索