本身动手实现一个前端路由

前前言

最近工做比较忙,写的比较慢,当我写完这一篇准备发布的时候,发现掘金恰好也有一篇关于前端路由的,并且写的比我详细,不过辛辛苦苦写的总不能删掉吧,再说个人路由风格是纯我的思路实现的,因此仍是硬着头皮发了哈。javascript

前言

用过现代前端框架的同窗,对前端路由必定不陌生, vue, react, angular 都有本身的 router, 那么你对 router 的工做原理了解吗? 若是还不了解, 那么请跟我一块儿来手写一个简单的前端路由, 顺便了解一下.css

实现路由的2种方式

  1. hash模式
  2. history模式

缺点:html

hash:丑(地址栏要多一个#), 某些特殊场景有坑 ,如微信支付回调前端

history: 兼容性较差, 直接访问会400 ,除非后端或者服务器有作处理vue

基本原理

hash是基于 监听 hashchange 事件实现的,history 是基于 pushState 和 popState,java

实现

因为history兼容性较差,并且实现方式基本没多大区别,本文就以hash模式来实现,history的实现方式只实现不一样的部分。react

<html>
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<input type="button" onclick="hashPush('index')" value="goto index">
</body>
<script> // 页面跳转 function hashPush(url) { location.hash = "#" + url } // 监听hash的变更 window.addEventListener('hashchange', function (e) { console.log('当前的hash地址', location.hash.slice(1) || "/") }) </script>
</html>
复制代码

以上就是hash模式的最简单实现,查看控制台,能够看到不论是点击跳转按钮,仍是点击浏览器的前进/后退按钮,控制台都会输出当前页面对应的 'url',有了 'url',咱们就能够对内容作条件渲染了git

咱们在上面的代码的基础上,稍做修改一下,增长2个div,一个是login,一个是indexgithub

<html>
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style> body{ margin: 0; padding: 0; } #login, #index{ width: 100%; height: 100%; display: none; } #login{ background: #f5f5f5; display: block; } #index{ background: #aaddff; } </style>
</head>
<body>

<!--登陆页-->
<div id="login" class="component">
    <input type="button" onclick="hashPush('index')" value="登录">
</div>

<!--首页-->
<div id="index" class="component">
    <input type="button" onclick="hashPush('login')" value="退出登录">
</div>

</body>
<script> // 页面跳转 function hashPush(url) { location.hash = "#" + url } // 监听hash的变更 window.addEventListener('hashchange', function (e) { let url = location.hash.slice(1) || "/" console.log('当前的hash地址', url) setVisible(url) }) // 显示跟路由地址对应的内容,隐藏其余内容 function setVisible(url) { let components = Array.from(document.body.querySelectorAll(".component")) components.map(item => { if(item.id===url) { console.log('显示',item.id) item.style.display = 'block' } else { console.log('隐藏',item.id) item.style.display = 'none' } }) } </script>
</html>

复制代码

这样,咱们就能根据不一样的hash地址,显示不一样的内容,是否是已经有点路由的味道了呢?可是目前还没法传參,传參的方式有不少种,最多见最通俗的,应该是query string 了吧? query string 其实很简单,解析url便可。vuex

// 监听hash的变更
    window.addEventListener('hashchange', function (e) {
        let url = location.hash.slice(1) || "index"
        // 解析url
        let questionIndex = url.indexOf("?")
        let path, query
        if(questionIndex >= 0){
            path = url.substr(0,questionIndex)
            let queryString = url.substr(questionIndex+1)
            let queryArray = queryString.split("&")
            let queryObject = {}
            queryArray.map(str => {
                let equalIndex = str.indexOf("=")
                if(equalIndex > 0) {
                    let key = str.substr(0,equalIndex)
                    let value = str.substr(equalIndex+1)
                    queryObject[key] = value
                }
            })
            query = queryObject
        } else {
            path = url
            query = {}
        }
        
        console.log('接收到的参数', query)
        setVisible(path)
    })
复制代码

这样,咱们就能获取到URL上传递的query string , 还顺便解决了传參会致使路由解析不正确的bug.

传參其实还有个更简单的方法,就是 设置一个全局变量 param,而后在须要传值的时候,直接对param 赋值便可

// 设置一个全局变量
    var param = {}
复制代码
// 页面跳转
    // 这个多了个参数 args
    function hashPush(url, args) {
        location.hash = "#" + url
        param = args
    }
复制代码

因为是全局变量,因此能够在任意位置使用 param,不过这样直接使用全局变量的方式,也有它的弱点,就是点击返回按钮的时候没法保存变量,而query string 由于是在 url 里面的,因此返回的时候,能够拿到上个页面的 query string, 那咱们有没有办法让全局变量的方式也能保存上一个页面的参数呢? 咱们来稍微修改一下代码

// 设置一个全局变量
    var param = {}
复制代码
// 页面跳转
    // 这个多了个参数 args
    function hashPush(url, args) {
        location.hash = "#" + url
        // 若是有传args参数,就在 param 对象下建一个名字跟当前url同样的对象,并把args赋值给它
        if(args) {
            param[url] = args
        }
    }
复制代码

那咱们在 hashchange 的时候,能够根据url来定位该页面的参数。

// 监听hash的变更
    window.addEventListener('hashchange', function (e) {
        // 省略其余代码
        args = param[path] || {}
        // 省略其余代码
    })
复制代码

这样,咱们就初步实现了一个hash路由,那么接下来,咱们来看看history路由怎么实现。

history 模式

history 模式主要依靠 调用 history.pushState() 方法 和 监听 popstate 事件。

history.pushState() 方法接收3个参数:

  1. 要传递的数据(参数)
  2. 给页面设置的标题(兼容性差,几乎没用)
  3. url

咱们看看调用实例

history.pushState({id:1}, '我是页面标题', url)
复制代码

须要注意的是 pushState 的时候,并不会触发 popstate 事件,只有在前进后退的时候,才会触发,因此 pushState 以后,须要手动去设置页面的相关状态。如上面的例子,咱们须要这样作

history.pushState({id:1}, '我是页面标题', url)
    setVisible(url)
复制代码

而后监听 popstate 事件,捕获 前进/后退

window.addEventListener('popstate',function (e) {
     // e.state 就是你 pushState 的时候,传的第一个参数
     let state = e.state || {}  
     let url = state.target.location.pathName
     // 根据参数 作一些其余操做
  })
复制代码

最后

本文最终实现代码已经放在 github上,想要直接看效果的同窗,能够上去直接copy,运行。

若是以为本文对您有用,请给本文的github加个star,万分感谢

另外,github上还有其余一些关于前端的教程和组件,有兴趣的童鞋能够看看,大家的支持就是我最大的动力。


其余连接

  1. 用proxy实现一个更优雅的vue
  2. 3行代码实现一个简易版promise
  3. vuex其实超简单,只需3步
  4. vuex其实超简单,喝完这3步,还有3步
  5. 从零开始徒手撸一个vue的toast弹窗组件
  6. JS中的继承(上)
  7. JS中的继承(下)
  8. JS中的原型对象
  9. JS中建立对象的方法
  10. moment过重? 那就试试miment--一个超轻量级的js时间库
  11. 美团小程序框架mpvue入门教程
  12. 美团小程序框架mpvue(花名:没朋友)蹲坑指南
  13. 一个基于mpvue的toast弹窗组件mptoast
相关文章
相关标签/搜索