因为 AJAX 的出现诞生了单页应用这种网页类型,单页应用简单的说就是前端通过 AJAX 加载和动态改变 DOM 内容来切换页面,网页本身并不会刷新和跳转。像 YouTubeGoogle Play网易云音乐 等 都属于单页应用。虽然页面不会跳转但观察浏览器地址栏就会发现地址是会改变的,手动刷新网页也不会转到首页,这就是前端实现的路由。

目前常见的实现路由的方式有两种,一种是通过 hash 来实现,另一种是通过 HTML5 中加入的 history API 来实现。

hash

这里主要是实现改变 URL,AJAX 的部分就不写了,下面是一个简单的 HTML:

HTML

<div>无内容</div>
<ul>
    <li><a href="#red">红色</a></li>
    <li><a href="#green">绿色</a></li>
    <li><a href="#blue">蓝色</a></li>
</ul>

只需要在链接 URL 前面加一个 # 链接就不会跳转。这里要实现的功能就是点击链接后 URL 改变,div 中的内容也改变,刷新网页 div 中的内容也不会消失,下面是 JS 代码:

JavaScript

//  封装一个函数,根据传入的 hash 改变 div 的内容
let setPage = hash => {
    //  如果 hash 没有内容
    if (hash == '' || hash == null) {
        hash = 'white';
    }else {
        hash = hash.replace('#', '');  //  去除 #
    }
    //  div 的内容
    let content = {
        red: "红色",
        green: "绿色",
        blue: "蓝色",
        white: "无内容"
    };

    //  根据传入的 hash 改变 div 的内容
    document.querySelector('div').innerHTML = content[hash];
    //  根据传入的 hash 改变 div 的背景颜色
    document.querySelector('div').style.background = hash;
    //  根据传入的 hash 设置页面标题
    document.title = content[hash];
};

//  给 window 添加一个 hashchange 事件,当 hash 改变就会触发 hashchange 事件
window.addEventListener('hashchange', (event) => {
    setPage(location.hash);  //  根据获取的 hash 改变 div 的内容
});

//  执行 setPage 函数,刷新页面 div 的效果也不会消失
setPage(location.hash);

因为注释比较多 看上去可能比较长,实际很简单,下面是详细说明:

location.hash 属性

如果 URL 中包含 # 通过 location.hash 可获取 # 和后面的内容。location.hash 是一个可读可写的属性,直接修改 location.hash 的内容 URL 也会改变。

hashchange 事件

当窗口 URL # 后面的内容改变时就会触发 hashchange 事件,hashchange 还包含两个属性:

event.newURL

当前页面的完整 URL。

event.oldURL

前一个 URL,也就是更改之前的 URL。

setPage 函数封装了 改变 div 的内容和背景颜色、改变页面标题,当 hashchange 监听到 URL 变化时就会调用 setPage 函数,刷新或打开页面也会调用 setPage 函数。使用 hash 路由最合兴的就是 location.hash 属性 和 hashchange 方法,hashchange 方法需要绑定到 window

下面是实现效果:

hash路由效果演示

在历史记录中也能看到:

hash路由历史记录

注意!如果链接 URL # 后面的内容和页面元素的 id 相同 在点击链接时会跳转到相同 id 元素的区域。

history

history 是 HTML5 中加入的 API,主要功能就是操作浏览器的会话历史记录,下面是 HTML:

HTML

<div>无内容</div>
<ul>
    <li><a href="red">红色</a></li>
    <li><a href="green">绿色</a></li>
    <li><a href="blue">蓝色</a></li>
</ul>

这里要实现的功能还是点击链接改变 URL 和 div 的效果,同时添加历史记录,下面是 JS:

JavaScript

let link = document.querySelectorAll("li a");  //  获取链接

//  封装一个函数,根据传入的 data 改变 div 的内容
let setPage = data => {
    //  div 的内容
    let content = {
        red: "红色",
        green: "绿色",
        blue: "蓝色",
        white: "无内容"
    };
    
    //  根据传入的 data 改变 div 的内容
    document.querySelector('div').innerHTML = content[data];
    //  根据传入的 data 改变 div 的背景颜色
    document.querySelector('div').style.background = data;
    //  根据传入的 data 设置页面标题
    document.title = content[data];
};

for (let i = 0; i < link.length; i++) {
    //  链接点击
    link[i].addEventListener("click", event => {
        event.preventDefault();  //  阻止浏览器跳转
        let el = event.target;  //  获取当前点击的链接
        //  修改 URL 和历史记录
        history.pushState(el.getAttribute('href'), '', el.href);
        //  根据点击链接的 href 来设置 div 的效果
        setPage(el.getAttribute('href'));
    });
}

//  给 window 添加 popstate 事件,点击浏览器的前进和后退就会触发 popstate
window.addEventListener('popstate', event => {
    let data = event.state;  //  history.pushState 传入的数据
    //  如果没有数据
    if (data == undefined) {
        data = 'white';
    }
    //  根据 history.pushState 传入的数据来设置 div 的效果
    setPage(data);
});

这里也是因为注释比较多,实际很简单,下面是详细说明:

history.pushState(data, title, url) 方法

在不跳转的情况下改变 URL,同时添加到历史记录,下面是参数说明:

参数类型说明
datastate一个状态对象,可以通过 popstate 事件读取
titlestring标题,对大多数浏览器都无效。
urlstring要添加到历史记录的 URL

history.replaceState(data, title, url) 方法

在不跳转的情况下改变 URL,同时替换历史记录,和 history.pushState 不一样的地方就是 history.replaceState 只会替换历史记录,不会添加历史记录,也就是说无论执行多少次都只会有一条记录,参数可以参考 history.pushState 的参数。此方法在这里暂时没有用到。

popstate 事件

当浏览器前进或者后退时就会触发 popstate 事件,注意!只有在同一个文档前进或者后退才会触发 popstate 事件,不同文档之间跳转并不会触发 popstatepopstate 也包含一些属性:

event.state

获取 history.replaceStatehistory.pushState 设置的状态对象。

history.back() 方法

移动到上一条历史记录,类似于浏览器的后退按钮。这里暂时没有用到此方法。

history.forward() 方法

移动到下一条历史记录,类似于浏览器的前进。这里暂时没有用到此方法。

history.go() 方法

接收一个数值,以当前 URL 为基准移动到指定的 URL,例如 history.go(-1) 就是后退一次,history.go(1) 就是前进一次,history.go(0) 就是直接刷新当前页面。此处暂时没有用到此方法。

history.back()history.forward()history.go() 都能触发 popstate 事件。

setPage 函数封装了更改 div 的标题、背景颜色,更改页面标题。因为 history.pushState 不会触发 popstate 事件,所以在点击按钮时还需要调用 setPage 函数来设置 div 的效果。

注意!直接使用 .hrefgetAttribute('href') 获取的属性是不一样的,.href 获取的是完整的 URL,getAttribute('href') 只能获取 href 中的内容。

history 路由最合兴的就是 history.pushState 方法 和 popstate 事件,popstate 事件需要绑定到 window

下面是 history 路由的效果:

history路由效果

注意!如果使用 history 路由 服务器需要设置 404 重定向,把 404 重定向到首页,否则刷新网页或访问历史记录会出现 404 。