zoukankan      html  css  js  c++  java
  • 简单的基于hash和hashchange的前端路由

    hash定义

    hash这个玩意是地址栏上#及后面部分,代表网页中的一个位置,#后面部分为位置标识符。页面打开后,会自动滚动到指定位置处。
    位置标识符 ,一是使用锚点,比如<a name="demo"></a>,二是使用id属性,比如 <span id="demo" ></span>

    带hash的请求

    当打开http://www.example.com/#print服务器实际收到的请求地址是http://www.example.com/,是不带hash值的。
    那么你真想带#字符咋办,转义啊, #转义字符为%23。也许有人会说,我咋知道这个转义啊,呵呵哒encodeURIComponent。

    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的代码做个引子

    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函数

    • 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 定义添加,删除,加载,和初始化等方法

    • 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 公布函数

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

    4 页面怎么配置,简单的利用a标签href

    <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 注册,当然你也可以通过选择器批量注册

    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请求

    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

  • 相关阅读:
    信号量Semaphore
    回环屏障CyclicBarrier
    线程同步器CountDownLatch
    vue 类似微信通讯录格式实现网易云音乐的歌手字母查询
    vue使用element的多个表格使用v-if切换,使用 :key="Math.random()" 后,表格排序出现问题
    vue使用pinyin的npm包将文字转为大写首字母字母
    vue中使用better-scroll滚动无效
    vue 移动端 图片懒加载 lazy
    表格配合keepalive缓存
    在保存数据之后,不知道什么时候清空数据,不如试试beforeRouteEnter
  • 原文地址:https://www.cnblogs.com/cloud-/p/8311057.html
Copyright © 2011-2022 走看看