简单的基于hash和hashchange的前端路由

小编 2026-06-05 阅读:280 评论:0
hash定义hash这个玩意是地址栏上#及后面部分,代表网页中的一个位置,#后面部分为位置标识符...

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

版权声明

本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。

热门文章
  • 机房智能化温湿度解决方式之POE供电以太网温湿度传感器

    机房智能化温湿度解决方式之POE供电以太网温湿度传感器
    机房智能化温湿度解决方式之POE供电以太网温湿度传感器 北京盈创力和电子科技有限公司 智能型TCP网口温湿度记录仪 北京IP网络温湿度记录仪厂家,北京盈创力和 北京智能型TCP网口温湿度记录仪IP网络温湿度记录仪是一种新型的基于TCP/IP协议双绞线以太网标准温湿度采集模块,利用它可以实现现场温度值、相对湿度值的采集,同时利用其自身的RJ45通信接口可以方便地和机房监控主机或交换机集线器进行联网。 工作于-40℃~85℃工业级带...
  • Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering

    Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering
    Problem Statement 我们考虑一个具有马尔可夫性质、非线性、非高斯的状态空间模型(State Space Model):对于一个时间序列上的观测结果{yt,t∈N}\\{ y_t , t \\in N \\}{yt​,t∈N},我们认为每个观测结果yty_tyt​的生成依赖于一个无法直接观察的隐变量xt∈{xt,t∈N}x_t \\in \\{x_t , t \\in N \\}xt​∈{xt​,t∈N},即:p(...
  • HTTP状态保持的原理

    HTTP状态保持的原理
    a)在用户登录之后,浏览器返回响应的时候会在响应中添加上cookieb)浏览器接收到cookie之后会自动保存c)当用户再次请求同一服务器中的其他网页的时候,浏览器会自动带上之前保存的cookied)服务接收到请求之后可以请 request 对象中取到cookie 判断当前用户是否登录  Http是无状态的,就是连接时数据互通,关闭后...
  • Hive 系统函数及示例

    Hive 系统函数及示例
    查看所有系统函数 show functions; 函数分类 内置函数【系统函数】 数学函数: floor、round、ceil、cos、log2等 字符串函数: length、reverse、trim、lower、get_json_object、repeat等 收集函数: size 转换函数: cast 日期函数: year、month、datediff、date、date_add等 条件函数: coalesce、case…w...
  • CSRF的原理和防范措施

    CSRF的原理和防范措施
    a)攻击原理:i.用户C访问正常网站A时进行登录,浏览器保存A的cookieii.用户C再访问攻击网站B,网站B上有某个隐藏的链接或者图片标签会自动请求网站A的URL地址,例如表单提交,传指定的参数iii.而攻击网站B在访问网站A的时候,浏览器会自动带上网站A的cookieiv.所以网站A在接收到请求之后可判断当前用户是登录状态,所以...
标签列表