如何使用原生 JavaScript 构建路由器

2025-05-25

如何使用原生 JavaScript 构建路由器

很多时候,我们可能想把 JavaScript 代码写在一个文件中,但希望只有匹配特定路由时才执行这段代码。你可以借助路由器来实现这一点,下载路由器库或自己编写代码即可。

今天,我将指导您使用原生 JavaScript 构建一个非常基本的路由器功能。我将使用一些ES6 特性JavaScript 正则表达式来构建此路由器,因此您必须熟悉这些特性才能更好地理解。

概念

编程的好处在于,你可以用任何你喜欢的方法或风格来解决问题,但你必须避免一些不好的做法。

以下是我们将采用的构建路由器的方法。

  • 创建路由器类
  • 创建一个方法,将路由逻辑及其相应的回调函数存储在数组中。
  • 创建一个方法来处理这些逻辑,如果逻辑为真,则返回相应的回调函数。

这是我们想要的图片。

const router = new RouterClass();

// the get() method would store the '/' logic and callback in an array;
router.get('/', function(){
   // code to be executed if '/' is matched
});

// here get() method would push '/another-page' and the callback to the existing array
router.get('/another-page', function(){
   // code to be executed if '/another-page' is matched
); 

router.init(); // this method will process the logics
Enter fullscreen mode Exit fullscreen mode

构建我们的路由器

步骤 1 - 创建路由器类

我们将创建一个名为 Router 的类,它将使用new关键字来调用。

class Router {

}
Enter fullscreen mode Exit fullscreen mode

第 2 步 - 添加构造函数

构造函数是使用 new 关键字实例化 Router 类时执行的方法。在构造函数方法中,我们将创建一个名为 的属性routes,并为其分配一个空数组。

routes 属性会将所有路由及其回调函数存储在一个数组中。

class Router {
    constructor(){
       this.routes = [];
    }
}
Enter fullscreen mode Exit fullscreen mode

您还可以将options参数传递给构造函数方法并为路由器类设置一些选项,但为了简单起见,我们将跳过这一步。

步骤 3 - 创建存储路线的方法

我们将创建一个名为“get”的方法,get()用于存储路由及其回调。get 方法应该包含两个参数uricallback

class Router {
    constructor(){
       this.routes = [];
    }

    get(uri, callback){

    }
}
Enter fullscreen mode Exit fullscreen mode

我命名这个方法是get为了方便阅读。因此,router.get(uri, callback);它应该表示:获取一个特定的 URI 并返回一个回调。你可以用你自己的方式命名它。或许,router.if(uri, callback);

步骤 4 - 验证 get 方法的参数

在这种方法中,我们将验证我们的参数,以确保在使用路由器时不会错误地传递错误类型的变量作为参数。

class Router {

    constructor(){
       this.routes = [];
    }

    get(uri, callback){
        // ensure that the parameters are not empty
        if(!uri || !callback) throw new Error('uri or callback must be given');

        // ensure that the parameters have the correct types
        if(typeof uri !== "string") throw new TypeError('typeof uri must be a string');
        if(typeof callback !== "function") throw new TypeError('typeof callback must be a function');
        // throw an error if the route uri already exists to avoid confilicting routes
        this.routes.forEach(route=>{
            if(route.uri === uri) throw new Error(`the uri ${route.uri} already exists`);
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

步骤 5-将路线添加到路线数组

验证方法的参数后get(),我们将创建一个名为的对象route并将该对象推送到我们现有的路由数组中。

class Router {

    constructor(){
       this.routes = [];
    }

    get(uri, callback){
        // ensure that the parameters are not empty
        if(!uri || !callback) throw new Error('uri or callback must be given');

        // ensure that the parameters have the correct types
        if(typeof uri !== "string") throw new TypeError('typeof uri must be a string');
        if(typeof callback !== "function") throw new TypeError('typeof callback must be a function');

        // throw an error if the route uri already exists to avoid confilicting routes
        this.routes.forEach(route=>{
            if(route.uri === uri) throw new Error(`the uri ${route.uri} already exists`);
        })

        // Step 5 - add route to the array of routes
        const route = {
            uri, // in javascript, this is the same as uri: uri, callback: callback, avoids repition
            callback
        }
        this.routes.push(route);
    }
}
Enter fullscreen mode Exit fullscreen mode

init()步骤 6 - 使用方法处理路由

快完成了!让我们使用 方法来处理路由init()。当调用此方法时,我们希望它循环遍历路由数组,并将 和 进行匹配route.uriwindow.request.pathname如果找到匹配项,我们将通过返回 函数来跳出循环route.callback。为了更轻松地跳出循环,我们将使用Array.some()方法来代替 ,Array.forEach()因为Array.some()会在循环返回真值时结束循环。

class Router {

    constructor(){
       this.routes = [];
    }

    get(uri, callback){
        // ensure that the parameters are not empty
        if(!uri || !callback) throw new Error('uri or callback must be given');

        // ensure that the parameters have the correct types
        if(typeof uri !== "string") throw new TypeError('typeof uri must be a string');
        if(typeof callback !== "function") throw new TypeError('typeof callback must be a function');

        // throw an error if the route uri already exists to avoid confilicting routes
        this.routes.forEach(route=>{
            if(route.uri === uri) throw new Error(`the uri ${route.uri} already exists`);
        })

        // Step 5 - add route to the array of routes
        const route = {
            uri, // in javascript, this is the same as uri: uri, callback: callback, avoids repition
            callback
        }
        this.routes.push(route);
    }

    init(){
        this.routes.some(route=>{

            let regEx = new RegExp(`^${route.uri}$`); // i'll explain this conversion to regular expression below
            let path = window.location.pathname;

            if(path.match(regEx)){
                // our route logic is true, return the corresponding callback

                let req = { path } // i'll also explain this code below
                return route.callback.call(this, req);
            }
        })
    }
}
Enter fullscreen mode Exit fullscreen mode

代码很少,但其中有些奇怪的地方,对吧?我先从转换成正则表达式开始。

我将我们的代码转换route.uri成正则表达式,因为我们想要匹配 route.uri 的精确值,否则window.location.pathnamerouter.get('/about', callback)匹配“/about-us”、“/about-me”,因此我引入了 regExp 关键字^$

你也注意到了let req = { path }, 也意味着let req = { path: path }。这只是传递一个可以通过回调参数访问的对象。实际上,这意味着:

const router = new Router();
router.get('/about-me', function(req){
      console.log(req.path); // outputs /about-me to the console
}
router.init();
Enter fullscreen mode Exit fullscreen mode

结论

这些步骤是构建一个基本的JavaScript 路由器时可以参考的。为了进一步提升,你应该关注以下功能:

  • 具有路由参数
  • 能够评估查询参数
  • 有命名路线
  • 分组路线

如果您不知道如何实现这些功能,可以查看我构建的路由器库的源代码,了解我是如何实现这些功能的。更好的是,您可以通过 npm 安装该库并在脚本中使用它。请查看npmnpm i @kodnificent/sparouter上的安装指南

注意:
这主要用于前端路由。如果你想构建后端路由器,可以遵循类似的流程,但获取请求 URI 的过程取决于服务器。

这是我在 dev.to 上的第一篇文章,所以点个爱心鼓励一下吧。非常欢迎评论、贡献和批评。欢迎查看我的dev.to 个人资料并关注我,让我们一起开发。

文章来源:https://dev.to/kodnificent/how-to-build-a-router-with-vanilla-javascript-2a18
PREV
《原子习惯》中的 3 条建议帮助我成为微软的全栈开发人员
NEXT
通过玩“Pod 游戏”来学习 Kubernetes