原生JavaScript路由实现指南:History API与popstate事件详解

作者:流萤

时间:2026-10-24

浏览:558

本文详解如何使用原生JavaScript实现前端路由,无需依赖React等框架。核心在于利用History API的pushState方法修改URL而不刷新页面,并通过监听popstate事件响应浏览器前进后退操作。文章涵盖路由配置、状态管理及DOM更新逻辑,帮助开发者构建轻量级、低依赖的单页应用导航系统。

文章目录

前言

提及路由时,人们往往联想到React等框架。然而,这些框架底层依然依赖原生JavaScript(vanilla JavaScript)。本文将探讨如何基于原生JS实现路由功能。

本教程旨在指导读者掌握使用原生JavaScript编写自定义路由的方法。

简介

许多开发者出于特定需求希望自建路由系统。如果你阅读此文,很可能正面临此类需求。

使用原生JS构建路由的核心优势在于降低对重型框架的依赖。

只要掌握实现路由所需的各个组成部分,在原生JavaScript中构建路由系统并非难事。

开发JS路由时需掌握以下关键点:

1. 原生JS路由的核心机制依赖于location.pathname属性。

2. 需监听“popstate”事件以响应pathname的变化。当用户在地址栏输入新URL或点击浏览器导航按钮而不希望刷新页面时,该事件触发,此时需通过加载新内容来更新视图。

3. 可选择将路由规则存储在routes[]数组中。

4. 必须使用JavaScript正则表达式(RegEx)来解析URL。

5. 若要将路由集成到浏览器原生架构中,理解history对象及history.pushState(JavaScript History API)至关重要。

首先,我们将处理History API。

JavaScript的History API

许多Vanilla JS路由教程忽略了JavaScript History API,这并不恰当。因为浏览器的“后退”和“前进”按钮与浏览历史记录中的URL导航直接相关。脱离History API,路由无从谈起。

1. history.back()等同于history.go(-1),对应于用户点击浏览器“后退”按钮时的行为。两种方法效果一致。

2. 当用户点击“前进”按钮时,执行history.forward(),这等同于history.go(1)。

3. go()方法与.back()和forward()类似,但允许指定在浏览器历史记录栈中前进或后退的具体步数。

4. pushState()用于将新状态推送到History API中。

5. .length属性表示会话历史记录中的元素数量。

6. .state属性用于获取当前状态,无需监听“popstate”事件即可访问。

实现自己的原生JS路由

基于History API的Vanilla JS路由设置

我们先深入研究构建URL切换器所需的最少代码(无需刷新页面),随后通过GIF动图展示其工作原理。


<html>
    <head>
        <title>Hello</title>
        <script type = "module">
            function select_tab(id) {
                // remove selected class from all buttons
                document.querySelectorAll(".route").forEach(item => item.classList.remove('selected'));
                // select clicked element (visually)
                document.querySelectorAll("#" + id).forEach(item => item.classList.add('selected'));
            }
            function load_content(id) {
                // Update text "Content loading for {id}..."
                // Of course, here you would do you content loading magic
                // Perhaps run Fetch API to update resources
                document.querySelector("#content").innerHTML = 'Content loading for /' + id + '...';
            }
            function push(event) {
                // Get id attribute of the box or button or link clicked
                let id = event.target.id;
                // Visually select the clicked button/tab/box
                select_tab(id);
                // Update Title in Window's Tab
                document.title = id;
                // Load content for this tab/page
                loadContent(id);
                // Finally push state change to the address bar
                window.history.pushState({id}, `${id}`, `/page/${id}`);
            }
            window.onload = event => {
                // Add history push() event when boxes are clicked
                window["home"].addEventListener("click", event => push(event))
                window["about"].addEventListener("click", event => push(event))
                window["gallery"].addEventListener("click", event => push(event))
                window["contact"].addEventListener("click", event => push(event))
                window["help"].addEventListener("click", event => push(event))
            }
            // Listen for PopStateEvent (Back or Forward buttons are clicked)
            window.addEventListener("popstate", event => {
                // Grab the history state id
                let stateId = event.state.id;
                // Show clicked id in console (just for fun)
                console.log("stateId = ", stateId);
                // Visually select the clicked button/tab/box
                select_tab(stateId);
                // Load content for this tab/page
                loadContent(id);
            });
        </script>
        <style>
            * { /* global font */
                font-family: Verdana;
                font-size: 18px;
            }
            #root { display: flex; flex-direction: row; }
            #content { display: flex;
                display: block;
                width: 800px;
                height: 250px;
                /* vertically centered text */
                line-height: 250px;
                border: 2px solid #555;
                margin: 32px;
                text-align: center;
            }
            .route {
                cursor: pointer;
                justify-content: center;
                width: 150px;
                height: 50px;
                /* vertically centered text */
                line-height: 50px;
                position: relative;
                border: 2px solid #555;
                background: white;
                text-align: center;
                margin: 16px;
            }
            .route.selected { background: yellow; }
        </style>
    </head>

    <body>

        <section id = "root">
            <section class = "route" id = "home">/home</section>
            <section class = "route" id = "about">/about</section>
            <section class = "route" id = "gallery">/gallery</section>
            <section class = "route" id = "contact">/contact</section>
            <section class = "route" id = "help">/help</section>
        </section>

        <main id = "content">Content loading...</main>
    
    </body>

</html>

核心在于调用window.history.pushState({id}, ${id}, /page/${id});

第一个参数是状态的唯一ID,第二个参数是“标签标题”文本,第三个参数是地址栏中显示的路径。这使得浏览器无需重新加载页面即可更改URL。

结果:每次点击按钮时,浏览器地址栏中的URL实际上会发生变化,同时内容框也会更新。

我们的原生JS路由开始运行。请注意,每次点击按钮都会触发history.pushState。我们只需将存储在元素id属性中的点击元素ID(如home、about、gallery等)传递给它即可。这些ID应与实际导航到的页面一致。当然,存储页面名称并非只有这一种方式,也可以使用数组[]或其他方法。本例采用此方式。

当然,我们还需要从服务器加载该位置的布局和资源。具体实现取决于你的程序,可以是任何形式。

使“后退”和“前进”按钮起作用

使用history.pushState会自动使Back和Forward按钮导航到上一个或下一个状态。这将触发popstate事件。你需要在此处再次更新视图(第一次更新发生在点击按钮时)。

由于该事件包含点击的id,因此当用户点击Back或Forward时,刷新视图并重新加载内容变得容易。

这里未使用React或Vue,因此在我的源代码中,load_content负责直接在DOM中更新视图。此区域可以填充由API加载的内容。由于这仅是“前端”示例,无法展示更多后端细节。但这展示了客户端的工作方式。

初始化服务器端的路由负载

将它们结合起来还需要执行一个步骤。在我的例子中,只使用了router.html。当你在PWA中首次加载此路由时,必须确保如果直接在地址栏中输入/page/home,路由也能正常工作。

到目前为止,我们仅从前端更改了路由器地址。假设每次你导航到路由按钮上出现的URL时,该URL实际上是从服务器单独加载的。

因此,你有责任确保/page/about将路由器和页面加载到应用程序的根视图中。它还应高亮显示“current”按钮。

实施完毕后,你的路由即告完成。你如何选择重新加载#content元素中的内容完全取决于你自己和你的后端设计。

以上就是如何制作自己的原生JavaScript路由的详细内容,更多关于制作原生JavaScript路由的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章