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

    简单的调用方式,

    ```js

    window.onhashchange = function(e){

    console.log('old URL:', e.oldURL)

    console.log('new URL', e.newURL)

    }

[hash | CAN I USE](https://caniuse.com/#search=hash) 上可以看到除了IE8一下和那个尴尬的Opera Mini,hashchange事件都是支持得很好。那么怎么做到兼容,用MDN的代码做个引子js

;(function(window) {

// exit if the browser implements that event

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

var location = window.location,

oldURL = location.href,

oldHash = location.hash;

// check the location hash on a 100ms interval

setInterval(function() {

var newURL = location.href,

newHash = location.hash;

// if the hash has changed and a handler has been bound...
if (newHash != oldHash && typeof window.onhashchange === "function") {
  // execute the handler
  window.onhashchange({
    type: "hashchange",
    oldURL: oldURL,
    newURL: newURL
  });

  oldURL = newURL;
  oldHash = newHash;
}

}, 100);

})(window);

```

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

最新文章

  1. 7.Struts2复杂类型数据的接受
  2. what is difference in (int)a,(int&amp;)a,&amp;a,int(&amp;a) ?
  3. MySQL之运算符与函数、自定义函数
  4. 分数的加减法——C语言初学者代码中的常见错误与瑕疵(12)
  5. 中国特色社会主义的体制中有这样的现象:地方省政府要坚持党的领导和按 照国务院的指示进行安全生产。请编写一个java应用程序描述上述的体制现象。
  6. insert 另外一种用法
  7. OAuth 2.0 开发完全详解
  8. LNNVL函数使用
  9. IIS的WebGarden、WebFarm和StateServer
  10. Rest之路 -- 从第二个Rest application里面分析 Rest 方法
  11. 以太坊RLP用法-go-ethereum学习
  12. 【Spring Cloud笔记】 断路器-hystrix
  13. vue-resource post请求后台接口报400(跨域问题解决方法)
  14. Docker 容器状态查看 - 五
  15. HADOOP security
  16. Codeforces Round #276 (Div. 1) E. Sign on Fence (二分答案 主席树 区间合并)
  17. Spring Boot 2.0 利用 Spring Security 实现简单的OAuth2.0认证方式1
  18. ios开发之--sizeToFit的用法
  19. 小米范工具系列之十:小米范SSH批量命令执行工具
  20. 【debian】给用户添加sudo权限

热门文章

  1. iBATIS使用$和#的一些理解
  2. 智能合约语言 Solidity 教程系列5 - 数组介绍
  3. HTML5 进阶系列:拖放 API 实现拖放排序(转载)
  4. 使用SplitContainer来实现隐藏窗口的部分内容(转)
  5. [UWP]了解IValueConverter
  6. PHP判断手机号运营商(详细介绍附代码)
  7. 在表格中,th scope=&quot;row&quot;和th scope=&quot;col&quot;中的scope属性的用法及意义
  8. Ubuntu(Linux)下如何用源码文件安装软件
  9. Android中菜单图标等系统自带的图标
  10. JavaScript的DOM编程--11--插入节点