双语面试:实现一个简单的前端路由函数

11 阅读6分钟

面试题 Interview Question

实现一个简单的前端路由器,使得当 URL 哈希改变时,根据路由表执行对应的渲染函数。
Implement a simple client-side router that listens to URL hash changes and executes the corresponding rendering function based on a routing table.

要求 Requirements

  1. 路由初始化:创建一个 Router 函数,用于初始化路由表(一个 key-value 对象),键名为路由路径(不含 #),值为对应的回调函数(渲染视图或加载内容)。
    Router Initialization: Create a Router function to initialize a routing table—an object where each key is a route path (without the #) and each value is a callback function (which renders the view or loads content).

  2. 哈希监听:使用 window.onhashchange 事件监听 URL 哈希(location.hash)的变化,提取新的哈希片段并根据路由表执行对应的处理器函数。
    Hash Listening: Use the window.onhashchange event to listen for URL hash (location.hash) changes, extract the new hash fragment, and invoke the corresponding handler function from the routing table.

  3. 初始路由处理:在路由初始化时,也需调用一次路由处理函数以处理页面首次加载(如用户直接访问 index.html#about 的情况)。
    Initial Route Handling: Upon router initialization, immediately invoke the route handler once to handle the initial load (e.g., when a user directly visits index.html#about).

  4. 404 处理:当哈希不匹配任何路由表中的路径时,执行一个默认的 404 渲染函数。
    404 Handling: If the hash does not match any key in the routing table, execute a default 404-rendering function.

  5. 纯函数式或类封装:不使用第三方库(如 React Router 等),可以使用纯函数式风格,不要依赖windowlocation 以外的全局状态。
    Pure FP or Class Encapsulation: Do not use any third-party libraries (e.g., React Router). You may implement in a pure functional style, but avoid relying on global state rather than window and location.

  6. 动态路由支持(可选加分) :路由表的键可以是正则表达式或支持参数(如 product/:id),需要能提取动态参数并传递给处理器函数。(此情形比较复杂,限于篇幅另起文章探讨)
    Dynamic Route Support (Bonus) : Allow routing table keys to be regex patterns or parameterized routes (e.g., product/:id), and extract dynamic parameters to pass to the handler function.

参考答案 Reference Solution

// 路由表(键为无 # 前缀的路径,值为对应渲染函数)
const routes = {
  '': () => {
    document.getElementById('app').innerHTML = '<h1>Home Page</h1>';
  },
  'about': () => {
    document.getElementById('app').innerHTML = '<h1>About Us</h1>';
  },
  'contact': () => {
    document.getElementById('app').innerHTML = '<h1>Contact Us</h1>';
  },
  // ...可拓展更多静态路由
};

// 默认 404 渲染函数
const notFound = () => {
  document.getElementById('app').innerHTML = '<h1>404 - Page Not Found</h1>';
};

// 路由处理函数:提取当前 hash,并执行匹配的处理器
function handleRoute() {
  // 获取 hash 部分并去除前导 #
  const hash = location.hash.slice(1) || '';
  // 如果路由存在则执行,否则 404
  (routes[hash] || notFound)();
}

// 初始化路由:绑定 hashchange 事件并处理初始路由
function initRouter() {
  window.addEventListener('hashchange', handleRoute);  // :contentReference[oaicite:10]{index=10}
  handleRoute();  // 处理首次加载时的路由 
}

// 调用初始化,启动路由器
initRouter();
  • 解释 Explanation
  1. 定义一个 routes 对象作为路由表, 为无 # 的路径(如 ''about)、 为渲染函数(更改 DOM 内容)
    Define a routes object as the routing table, where the keys are paths without the # (such as '' and about), and the values are rendering functions (which modify the DOM content).

  2. handleRoute 函数通过 location.hash.slice(1) 获取当前哈希,如果为空则使用 '',再根据是否在 routes 表中找到对应的函数来执行或调用 notFound
    The handleRoute function gets the current hash using location.hash.slice(1). If it's empty, it defaults to ''. Then, it checks whether a corresponding function exists in the routes table to execute it; otherwise, it calls notFound.

  3. initRouter 绑定 hashchange 事件:一旦 URL 发生哈希变化,浏览器会触发此事件,执行 handleRoute,从而动态加载内容
    The initRouter function binds the hashchange event: whenever the URL hash changes, the browser triggers this event, executes handleRoute, and dynamically loads the content.

  4. 在绑定完成后立即调用 handleRoute() 以处理页面首次加载时的路由(用户直接访问包含哈希的 URL)
    The initRouter function binds the hashchange event: whenever the URL hash changes, the browser triggers this event, causing handleRoute to run and dynamically load the corresponding content.

  5. 不使用任何框架,依赖原生 windowlocationaddEventListener 等 API,满足纯函数式 / 轻量级路由需求
    Without using any frameworks, it relies on native APIs like window, location, and addEventListener to fulfill a purely functional and lightweight routing solution.

示例 Example

以下示例展示一个具有导航链接、内容区域和默认 404 处理的最小化 HTML + JS 结构。
The following example demonstrates a minimal HTML + JS structure with navigation links, a content area, and default 404 handling.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Simple SPA Router</title>
</head>
<body>
  <!-- 导航栏 / Navigation Bar -->
  <nav>
    <a href="#/">Home</a>
    <a href="#/about">About</a>
    <a href="#/contact">Contact</a>
    <a href="#/nonexistent">Invalid</a> <!-- 测试 404 / Test 404 -->
  </nav>

  <!-- 内容区域 / Content Area -->
  <div id="app"></div>

  <!-- 路由脚本 / Router Script -->
  <script>
    const routes = {
      '': () => {
        document.getElementById('app').innerHTML = '<h1>Home Page</h1>';
      },
      'about': () => {
        document.getElementById('app').innerHTML = '<h1>About Us</h1>';
      },
      'contact': () => {
        document.getElementById('app').innerHTML = '<h1>Contact Us</h1>';
      }
    };

    const notFound = () => {
      document.getElementById('app').innerHTML = '<h1>404 - Page Not Found</h1>';
    };

    function handleRoute() {
      const hash = location.hash.replace(/^#/?/, '') || '';  // 移除 "#/" 前缀
      (routes[hash] || notFound)();
    }

    function initRouter() {
      window.addEventListener('hashchange', handleRoute);  // 
      handleRoute();  // 初始加载 / Initial load 
    }

    initRouter();
  </script>
</body>
</html>
  • 说明 Explanation
  1. <a href="#/">Home</a> 等导航链接将 URL 哈希更改为 #/#/about#/contact 等,由浏览器触发 hashchange 事件
    Navigation links like <a href="#/">Home</a> change the URL hash to #/, #/about, #/contact, etc., which triggers the hashchange event in the browser.

  2. handleRoute 中通过正则 replace(/^#/?/, '') 去除 ##/ 前缀,得到纯路径字符串用作路由表的键
    In handleRoute, the regular expression replace(/^#/?/, '') is used to remove the # or #/ prefix, resulting in a clean path string to be used as the key for the routing table.

  3. 对于无匹配的哈希(如 #/nonexistent),调用 notFound() 渲染 404 页面
    For unmatched hashes (such as #/nonexistent), the notFound() function is called to render the 404 page.

  4. initRouter 在页面加载时立即执行一次 handleRoute(),确保首次访问 URL(带哈希或不带)时正确渲染
    initRouter immediately calls handleRoute() once when the page loads to ensure the correct rendering on the initial visit, whether the URL contains a hash or not.

面试考察点 Interview Focus

  1. 前端路由基础:理解 SPA 路由原理,包括如何使用 location.hashhashchange 事件来管理浏览器历史记录和视图切换
    Client-Side Routing Fundamentals: Understand the principles of SPA routing, including how to use location.hash and the hashchange event to manage browser history and view transitions.

  2. 事件监听:熟练使用 window.addEventListener('hashchange', handler)window.onhashchange = handler,并理解两者区别及兼容性
    Event Listener Usage: Be proficient in using window.addEventListener('hashchange', handler) or window.onhashchange = handler, and understand the differences and compatibility between the two approaches.

  3. 路由表映射:能够设计一个灵活的路由表,将路径映射到渲染函数或组件,处理静态路由和(可选)动态路由(正则或参数化)
    Routing Table Mapping: Be able to design a flexible routing table that maps paths to rendering functions or components, handling both static routes and (optionally) dynamic routes (using regular expressions or parameterization).

  4. 404 处理:在没有匹配路径时提供默认的 404 渲染方案,保持用户体验完整性
    404 Handling: Provide a default 404 rendering solution when no matching path is found, ensuring a consistent and complete user experience.

  5. 首次加载:在 hashchange 绑定后,还必须手动调用一次路由处理函数,以支持用户直接通过 URL 访问含哈希的页面
    Initial Load Handling: After binding the hashchange event, you must also manually call the routing handler once to support users accessing a hash-based URL directly.

  6. 代码结构与可扩展性:使用纯函数式设计以提高可读性、可维护性,同时支持动态添加路由、动态视图加载或懒加载等
    Code Structure & Extensibility: Use a purely functional design to improve readability and maintainability, while also supporting dynamic route addition, dynamic view rendering, or lazy loading.

  7. 性能与用户体验:理解为什么在高频切换时要避免重复重绘 DOM,以及如何在触发路由更改时只重新渲染必要部分,以提高 SPA 性能
    Performance & UX: Understand why it's important to avoid redundant DOM re-rendering during frequent route changes, and how to re-render only the necessary parts when a route changes to improve SPA performance.

  8. 浏览器历史管理:了解 hash 机制与 HTML5 History API(pushState/popState)的区别与适用场景,为后续实现基于 History 的伪路径路由打基础
    Browser History Management: Understand the differences between the hash mechanism and the HTML5 History API (pushState/popState), along with their appropriate use cases, to lay the groundwork for implementing History-based pseudo-path routing later on.