SPA路由机制详解

69 阅读3分钟

前端路由原理:让单页面应用跳转如丝般顺滑

前言

今天来聊聊单页面应用(SPA)的核心技术——前端路由。你有没有想过,为什么现在很多网站跳转页面时不刷新了?为什么URL变了但内容切换得这么流畅?这一切都要归功于前端路由!今天就带你彻底搞懂它的实现原理,保证让你收获满满!

利用URL的Hash(#)

最古老但最简单的方法就是利用URL中的hash(就是#号后面的部分)。浏览器有个特点:改变hash不会重新加载页面,但会留下历史记录。

// 改变hash
window.location.hash = '/home';

// 监听hash变化
window.addEventListener('hashchange', function() {
    console.log('Hash变了!现在是:', window.location.hash);
});

这种方法兼容性好,连IE8都支持!但缺点就是URL里总是带着个#号,看起来不太美观。

import 方式

早年还有人用import的方式实现"模块化"加载,但现在基本被淘汰了:

// 古老的方式,了解一下就行
const importPage = (url) => {
    document.write('<script type="text/javascript" src="' + url + '"></script>');
};

这种方法现在基本没人用了,知道有这么回事就行。

AJAX 方式

Ajax出现后,我们可以无刷新获取内容了:

function loadPage(url) {
    fetch(url)
        .then(response => response.text())
        .then(html => {
            document.getElementById('content').innerHTML = html;
        });
}

但这样有个问题:URL没变,用户没法直接分享或收藏特定页面。所以还需要配合其他技术来实现真正的路由。

利用 H5 新增方法 History interface

HTML5给我们带来了更优雅的解决方案——History API。这才是现代前端路由的标配!

往返缓存

浏览器为了提升性能,会把之前访问的页面缓存起来(叫做bfcache)。当你点击后退按钮时,页面会从缓存中快速加载,而不是重新请求。

往历史记录栈中添加记录:pushState(state, title, url)

这个方法可以在不刷新页面的情况下改变URL,并添加历史记录:

// 添加新历史记录
history.pushState({page: 1}, "首页", "/home");

参数说明:

  • state:一个状态对象,与新历史记录相关联
  • title:目前大多数浏览器忽略这个参数(但必须传)
  • url:新的URL(必须是同源的)

改变当前的历史记录:replaceState(state, title, url)

和pushState类似,但不是添加新记录,而是替换当前记录:

// 替换当前历史记录
history.replaceState({page: 2}, "关于", "/about");

history.state

这个属性可以获取当前历史记录条目的状态对象:

console.log(history.state); // 输出当前状态对象

popstate

当用户点击浏览器前进/后退按钮时,会触发popstate事件:

window.addEventListener('popstate', function(event) {
    // event.state 包含pushState或replaceState时传入的state对象
    console.log('位置变了:', event.state);
});

实现

现在我们来手写一个简单的前端路由:

class SimpleRouter {
    constructor() {
        this.routes = {};
        this.bindEvents();
    }
    
    // 添加路由
    addRoute(path, callback) {
        this.routes[path] = callback;
    }
    
    // 跳转到指定路径
    navigate(path) {
        history.pushState({path}, null, path);
        this.executeRoute(path);
    }
    
    // 执行路由对应的回调
    executeRoute(path) {
        if (this.routes[path]) {
            this.routes[path]();
        } else {
            console.error('路由不存在:', path);
        }
    }
    
    // 绑定事件监听
    bindEvents() {
        // 监听popstate事件(前进/后退)
        window.addEventListener('popstate', (event) => {
            if (event.state && event.state.path) {
                this.executeRoute(event.state.path);
            }
        });
        
        // 监听链接点击(阻止默认行为,使用路由导航)
        document.addEventListener('click', (e) => {
            if (e.target.tagName === 'A' && e.target.getAttribute('href')) {
                e.preventDefault();
                this.navigate(e.target.getAttribute('href'));
            }
        });
    }
}

// 使用示例
const router = new SimpleRouter();
router.addRoute('/home', () => {
    document.getElementById('content').innerHTML = '<h1>首页内容</h1>';
});
router.addRoute('/about', () => {
    document.getElementById('content').innerHTML = '<h1>关于我们</h1>';
});

// 初始化
router.navigate('/home');

总结

前端路由让单页面应用成为了可能,提供了更流畅的用户体验。现在你知道了吧:

  1. Hash方式简单兼容性好,但URL不美观
  2. History API是现代SPA的首选,URL干净美观
  3. pushState/replaceState改变URL但不刷新页面
  4. popstate监听前进后退操作
  5. 实际实现需要结合路由表和事件监听

现在的主流框架(Vue Router、React Router)都是基于这些原理实现的,只是加了更多功能和优化。理解了这些底层原理,你再使用这些框架时就会得心应手了!