不依赖框架,如何用 JS 实现一个完整的前端路由系统

摘要

现在几乎所有前端项目都离不开"SPA"(单页应用)。它让整个网页加载体验更顺滑、内容切换更快。而这背后的关键技术之一,就是前端路由。本篇文章从原理出发,通过手写一个基础的前端路由类,配合实际页面,带你一步步理解前端路由的机制,并扩展到参数、404 页面等实战应用。

引言

传统的网页开发是"多页应用"(MPA):每次点击链接都会重新加载一个完整的 HTML 页面。但这会让体验变得卡顿、不连续。而 SPA 则只在第一次加载 HTML,后续切换都由 JavaScript 接管,动态渲染页面内容。

现代框架(React、Vue、Angular)都内置了前端路由功能,比如 react-routervue-router,但理解它们底层原理,不仅对学习框架有帮助,也能在遇到路由问题时更有思路。

手写一个最小可用的前端路由类

思路分析

核心任务:

  • 拦截浏览器地址栏变化(通过 pushState() 修改地址)
  • 监听前进/后退(通过 popstate 事件)
  • 根据路径匹配回调,渲染页面内容

实现路由类:代码 + 注释

js 复制代码
class Router {
    constructor() {
        this.routes = {}; // 用于存储 path -> callback 的映射
        this.notFound = null;

        // 监听浏览器前进/后退事件
        window.addEventListener('popstate', this.handlePopState.bind(this));
    }

    // 注册路径和对应的回调函数
    addRoute(path, callback) {
        this.routes[path] = callback;
    }

    // 注册404页面
    setNotFound(callback) {
        this.notFound = callback;
    }

    // 跳转到指定路径
    navigate(path) {
        history.pushState({}, '', path); // 修改地址栏,但不刷新页面
        this.handlePopState(); // 手动触发一次路由处理
    }

    // 处理路径变化:找到并执行对应回调
    handlePopState() {
        const path = window.location.pathname;

        // 如果路径存在,则执行对应的渲染函数
        if (this.routes[path]) {
            this.routes[path]();
        } else if (this.notFound) {
            this.notFound();
        } else {
            console.warn(`路径不存在: ${path}`);
        }
    }
}

实战:页面导航和内容渲染

页面HTML结构

html 复制代码
<div id="nav">
    <a href="/home" onclick="goTo('/home')">首页</a>
    <a href="/about" onclick="goTo('/about')">关于我们</a>
    <a href="/contact" onclick="goTo('/contact')">联系我们</a>
    <a href="/notfound" onclick="goTo('/notfound')">未知页面</a>
</div>
<hr />
<div id="content">这里是默认内容</div>

配置路由和回调

js 复制代码
const router = new Router();

// 注册路由
router.addRoute('/home', () => {
    document.getElementById('content').innerHTML = '<h2>欢迎来到首页</h2>';
});

router.addRoute('/about', () => {
    document.getElementById('content').innerHTML = '<h2>我们是一个前端开发团队</h2>';
});

router.addRoute('/contact', () => {
    document.getElementById('content').innerHTML = '<h2>请通过邮箱联系我</h2>';
});

// 设置 404 页面
router.setNotFound(() => {
    document.getElementById('content').innerHTML = '<h2>404 页面未找到</h2>';
});

// 点击链接时跳转
function goTo(path) {
    event.preventDefault(); // 阻止 a 标签默认行为
    router.navigate(path);
}

// 页面刷新时保持当前路由内容
window.addEventListener('DOMContentLoaded', () => {
    router.handlePopState();
});

高阶功能拓展

场景一:支持路径参数(比如 /user/123

我们改造 Router 类,让它支持参数路由:

js 复制代码
class Router {
    constructor() {
        this.routes = [];
    }

    addRoute(path, callback) {
        const paramNames = [];
        const regex = path.replace(/:([^\/]+)/g, (_, key) => {
            paramNames.push(key);
            return '([^\/]+)';
        });

        const pattern = new RegExp(`^${regex}$`);
        this.routes.push({ pattern, paramNames, callback });
    }

    navigate(path) {
        history.pushState({}, '', path);
        this.handlePopState();
    }

    handlePopState() {
        const path = window.location.pathname;

        for (const route of this.routes) {
            const match = route.pattern.exec(path);
            if (match) {
                const params = {};
                route.paramNames.forEach((key, i) => {
                    params[key] = match[i + 1];
                });
                route.callback(params);
                return;
            }
        }

        document.getElementById('content').innerHTML = '<h2>404 Not Found</h2>';
    }
}

使用方式如下:

js 复制代码
const router = new Router();

router.addRoute('/user/:id', ({ id }) => {
    document.getElementById('content').innerHTML = `<h2>当前用户ID:${id}</h2>`;
});

现在访问 /user/123 会显示 当前用户ID:123

QA 问答环节

Q1:前端路由和后端路由有什么区别?

前端路由是在浏览器端用 JavaScript 控制地址栏和页面内容;后端路由是在服务器上解析 URL,返回不同页面或数据。

Q2:为什么刷新页面会 404?

因为浏览器发起了真实的 HTTP 请求,访问的是 /about 路径,而服务器没有设置"返回 index.html",所以报错。你需要在服务器配置中添加"任意路径都返回 index.html"。

Q3:为什么不直接用 hash (#) 路由?

早期前端路由是靠 window.location.hash 实现的(比如 /#/home),兼容性好,但不好看也不能完全模拟真实路径。pushState 更现代、真实,还能配合服务端渲染。

总结

我们从最简单的原理出发,手写了一个可以运行的前端路由系统,并支持:

  • 多路径匹配
  • 浏览器前进/后退按钮
  • 404 页面
  • 参数化路径(如 /user/:id

这个 Router 类虽然功能简单,但已经足够支撑一个轻量级 SPA 应用的页面跳转。如果你刚入门前端,强烈建议自己动手实现一遍,加深理解。

如果你对嵌套路由、懒加载路由组件、路由守卫等更高级玩法感兴趣,也欢迎告诉我,我可以继续带你深入挖掘。

相关推荐
fie88893 分钟前
浅谈几种js设计模式
开发语言·javascript·设计模式
谦行10 分钟前
深度神经网络训练过程与常见概念
前端
Simon_He39 分钟前
一个免费的在线压缩网站超越了付费的压缩软件
前端·开源·图片资源
巴巴_羊1 小时前
React Ref使用
前端·javascript·react.js
拾光拾趣录2 小时前
CSS常见问题深度解析与解决方案(第三波)
前端·css
徊忆羽菲2 小时前
Echarts3D柱状图-圆柱体-文字在柱体上垂直显示的实现方法
javascript·ecmascript·echarts
轻语呢喃2 小时前
JavaScript :字符串模板——优雅编程的基石
前端·javascript·后端
杨进军2 小时前
React 协调器 render 阶段
前端·react.js·前端框架
中微子2 小时前
Blob 对象及 Base64 转换指南
前端
风铃喵游2 小时前
让大模型调用MCP服务变得超级简单
前端·人工智能