likes
comments
collection
share

浅谈前端路由的实现 hash&history

作者站长头像
站长
· 阅读数 27

引言

在现代Web开发中,单页应用程序(SPA)因其快速加载和流畅的用户体验而广受欢迎。且在面试中,前端路由的底层原理也是高频题。本文将详细讲解vue前端的两种路由【hash&history】的不同,并通过手动实现一个Hash Router来理解其工作原理。

传统HTTP请求

传统的HTTP协议是无状态的,服务器无法主动向浏览器推送内容,浏览器只能通过URL改变向服务器发送请求以获取新的资源。每次请求新的页面时,服务器都会返回完整的HTML,这会导致页面刷新。而SPA带来了全新的用户体验:

  • 加载快:无需每次都请求完整的页面,只需加载变化的部分。
  • 无白屏:URL改变但不会刷新整个页面,页面更新更快。
  • 动态更新:通过JavaScript和DOM操作实现页面的动态更新。

Hash 与 History的不同

在前端路由中,有两种常见的方式:History模式和Hash模式。它们的主要区别在于URL的形式和对浏览器历史记录的影响。

  • History模式:使用真实的URL路径(如/page2),可以通过HTML5 History API来管理路由。这种模式需要后端服务器的支持来处理这些真实路径,以防止404错误。当点击跳转history路由的链接则会致使整个页面刷新。

  • Hash模式:使用带有#符号的URL路径(如#/page2)。Hash部分的变化不会触发整个页面的重新加载,因此适合单页应用(SPA)。Hash模式不需要服务器端特殊配置,是前端完全控制的一种路由方式。

如下代码所示:

<nav id="nav">
            <ul>
                <!-- 有#号的是哈希路由 -->
                <li><a href="#/page1">page1</a></li>
                <li><a href="/page2">page2</a></li>
                <li><a href="#/page3">page3</a></li>
            </ul>
        </nav>

页面效果如下

浅谈前端路由的实现 hash&history

当我们点击page1和page3链接后,页面没刷新,且URL里加了#

浅谈前端路由的实现 hash&history

浅谈前端路由的实现 hash&history 而当我们点击page2的链接后

浅谈前端路由的实现 hash&history

则URL无#,跳转的是真实路径,且重新加载了页面,导致了错误。 这也是history路由的不足。 所以推荐使用SPA和hash路由。 单页应用程序(SPA)带来了全新的用户体验:

  1. 快速加载:页面不会完全刷新,只会动态更新必要部分。
  2. URL管理:URL改变但不会刷新整个页面,例如#/page2
  3. 锚链接:使用hash可以实现页面内导航。
  4. 事件监听:通过hashchange事件监听URL变化,结合ajaxDOM操作动态更新页面内容。

在SPA中,使用Hash Router可以实现URL管理而不刷新页面。这种技术也常用于移动应用程序中。

手搓一个哈希路由

我们新建一个index.html文件,包含三个a标签。通常情况下,点击a标签会引起页面的刷新,但浏览器的机制是哈希值的变更不会引起页面刷新。因此,我们可以利用哈希值来实现页面的切换而不刷新整个页面。

 <nav id="nav">
            <ul>
                <!-- 有#号的是哈希路由 -->
                <li><a href="#/page1">page1</a></li>
                <li><a href="#/page2">page2</a></li>
                <li><a href="#/page3">page3</a></li>
            </ul>
        </nav>
        <!-- router-view -->
        <div id="container"></div>

接着封装一个HashRouter类

 // #/page1  page1 -> container
        // #/page2  page2 -> container

        class HashRouter {
               constructor(){
                this.routes = {};  //page => Component
                window.addEventListener('hashchange',this.load.bind(this),false)  // 动态指定函数内部this的指向,bind会返回一个全新的函数
               }
               register (hash,callback = function(){}){
                    this.routes[hash]=callback
               }

               registerIndex(callback=function(){}){  // 首页的注册函数
                this.routes['index']=callback;
               }

            load(){
                console.log(location.hash);  //BOM
                let hash = location.hash.slice(1) //去掉第一个字符# 方是路由
                console.log(hash,'////');
                let handler;
                if (!hash){
                        // 首页
                        handler=this.routes['index']
                }
                else {
                    // 相应页面
                    handler = this.routes[hash]

                }
                handler && handler.call(this)
            }

        }

在构造函数constructor()中初始化路由表,并绑定hashchange事件监听器。

此处需要注意为了确保事件处理函数中的this指向正确的对象(即HashRouter实例),我们使用bind方法来显式地绑定函数的this值。在这种情况下,this.load.bind(this)创建了一个新的函数,其this值永久地绑定到HashRouter实例,而不会受到事件触发时的影响。bind的用法将在下文详细讲述。

在register方法中注册特定路径的路由及其对应的回调函数,registerIndex方法为注册首页路由及其对应的回调函数。

load方法根据当前的哈希值,执行对应的回调函数来更新页面内容。

hashchange事件

当URL的哈希部分变化时,触发hashchange事件,调用load方法更新页面内容。

回调函数

在回调函数中,动态更新页面的内容。

注册路由

  let router = new HashRouter();
        let container = document.getElementById('container') 
        router.registerIndex(()=> container.innerHTML = '我是首页')
        router.register('/page1',()=> container.innerHTML = '我是Page1')
        router.register('/page2',function() {
            console.log(this,this.routes);
            container.innerHTML = '我是Page2'})
        router.register('/page3',()=> container.innerHTML = '我是Page3')
        console.log(router.routes);
        router.load();

先将三个链接的路由都注册好,当触发hashchange事件后渲染对应的hash的页面内容。

成果如下:

首页

浅谈前端路由的实现 hash&history

点击page1

浅谈前端路由的实现 hash&history 这就实现了一个简单hash router的功能!

回顾call,apply,bind

在JavaScript中,call, applybind都是用来改变函数运行时的上下文(即this指向)的方法。

  • call方法:立即调用函数,传入一个新的this值和一系列参数。格式为func.call(newThis, arg1, arg2, ...).
  • apply方法:立即调用函数,传入一个新的this值和一个参数数组。格式为func.apply(newThis, [arg1, arg2, ...]).
  • bind方法:返回一个新的函数,this值被绑定为传入的对象,但不会立即执行。格式为let newFunc = func.bind(newThis).

结合以下示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        let obj = {
            name:'xyao',
            getName: function(...args){
                console.log(args);
                console.log(this.name);
            }
        }
        let obj2 = {
            name:'04'
        }
        obj.getName();
        obj.getName.call(obj2,1,2)   // 参数一个一个传的
        obj.getName.apply(obj2,[1,2])   // 参数以数组形式传
        
        let func = obj.getName
        // 未来执行用bind,call和apply是立即执行
        func.bind(obj2)() 
    </script>
</body>
</html>

打印结果如下:

浅谈前端路由的实现 hash&history

而在手搓的hashrouter类中,我们使用这些方法来确保在HashRouter类中,load方法中的this指向正确。

总结

本文介绍了前端的两种路由【hash&history】,并通过手动实现一个简单的Hash Router,详细介绍了如何在单页应用程序(SPA)中使用哈希路由管理页面导航。同时,解释了JavaScript中的bind, call, 和apply方法的使用。

转载自:https://juejin.cn/post/7389650655427837962
评论
请登录