你好,谈谈你对前端路由的理解

一篇文章,不可能作的面面俱到,所有受众。但愿你们带着发散思惟去看文章,将文章涉及的知识点,吸取为己所用。这样看完一篇文章,才能有所收获。javascript

前言

好了不装了,今天我就化身性感面试官在线问你们一个问题,“谈谈你对前端路由的理解”。看到这个问题,那回答可多了去了。可是换位思考一下,你问候选人这个问题的时候,你想要获得什么答案?以我我的拙见,我但愿候选人能从全局解读这个问题,大体如下三点。html

一、为何会出现前端路由。前端

二、前端路由解决了什么问题。java

三、前端路由实现的原理是什么。web

咱们带着这三个问题,继续往下看,阅读的过程当中若是同窗们有本身的看法,能够评论区发表本身的见解。若是以为讲的内容让你有了新的看法,请献上你宝贵的一赞👍,这将是我继续写做的动力。面试

传统页面

这里不纠结叫法,凡是整个项目都是 DOM 直出的页面,咱们都称它为“传统页面”(SSR 属于首屏直出,这里我不认为是传统页面的范畴)。那么什么是 DOM 直出呢?简单说就是在浏览器输入网址后发起请求,返回来的 HTML 页面是最终呈现的效果,那就是 DOM 直出。而且每次点击页面跳转,都会从新请求 HTML 资源。耳听为虚,眼见为实。咱们以这个地址为例,验证如下上述说法。数组

www.cnblogs.com/han-1034683…浏览器

腚眼一看,就能明白上图在描述什么。没错,博客园就是一个传统页面搭建而成的网站,每次加载页面,都会返回 HTML 资源以及里面的 CSS 等静态资源,组合成一个新的页面。前端框架

“瞎了”的同窗,我再教一个方法,就是在浏览器页面右键,点击“显示网页源代码”,打开后以下所示:markdown

网页上能看到什么图片或文字,你能在上述图片中找到相应的 HTML 结构,那也属于传统页面,也就是 DOM 直出。

单页面

时代在进步,科技在发展,面对日益增加的网页需求,网页开始走向模块化、组件化的道路。随之而来的是代码的难以维护、不可控、迭代艰难等现象。面临这种状况,催生出很多优秀的现代前端框架,首当其冲的即是 React 、 Vue 、 Angular 等著名单页面应用框架。而这些框架有一个共同的特色,即是“经过 JS 渲染页面”。

举个例子,之前咱们直出 DOM ,而如今运用这些单页面框架以后, HTML 页面基本上只有一个 DOM 入口,大体以下所示:

全部的页面组件,都是经过运行上图底部的 app.js 脚本,挂载到 <div id="root"></div> 这个节点下面。用一个极其简单的 JS 展现挂载这一个步骤:

<body>
  <div id="root"></div>
  <script> const root = document.getElementById('root') // 获取根节点 const divNode = document.createElement('div') // 建立 div 节点 divNode.innerText = '你妈贵姓?' // 插入内容 root.appendChild(divNode) // 插入根节点 </script>
</body>
复制代码

image.png

脱去全部的凡尘世俗,最本真的单页项目运行形式即是如此。 注意,我要点题了啊!!! 

image.png

既然单页面是这样渲染的,那若是我有十几个页面要互相跳转切换,咋整!!??这时候 前端路由 应运而生,它的出现就是为了解决单页面网站,经过切换浏览器地址路径,来匹配相对应的页面组件。咱们经过一张丑陋的图片来理解这个过程:

前端路由 会根据浏览器地址栏 pathname 的变化,去匹配相应的页面组件。而后将其经过建立 DOM 节点的形式,塞入根节点 <div id="root"></div> 。这就达到了无刷新页面切换的效果,从侧面也能说明正由于无刷新,因此 React 、 Vue 、 Angular 等现代框架在建立页面组件的时候,每一个组件都有本身的 生命周期 。

原理

前端路由 插件比较火的俩框架对应的就是 Vue-Router 和 React-Router ,可是它们的逻辑,归根结底仍是同样的,用异曲同工四个字,再合适不过。

经过分析哈希模式和历史模式的实现原理,让你们对前端路由的原理有一个更深入的理解。

哈希模式

a 标签锚点你们应该不陌生,而浏览器地址上 # 后面的变化,是能够被监听的,浏览器为咱们提供了原生监听事件 hashchange ,它能够监听到以下的变化:

  • 点击 a 标签,改变了浏览器地址
  • 浏览器的前进后退行为
  • 经过 window.location 方法,改变浏览器地址

接下来咱们利用这些特色,去实现一个 hash 模式的简易路由: 在线运行

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hash 模式</title>
</head>
  <body>
    <div>
      <ul>
        <li><a href="#/page1">page1</a></li>
        <li><a href="#/page2">page2</a></li>
      </ul>
      <!--渲染对应组件的地方-->
      <div id="route-view"></div>
    </div>
  <script type="text/javascript"> // 第一次加载的时候,不会执行 hashchange 监听事件,默认执行一次 // DOMContentLoaded 为浏览器 DOM 加载完成时触发 window.addEventListener('DOMContentLoaded', Load) window.addEventListener('hashchange', HashChange) // 展现页面组件的节点 var routeView = null function Load() { routeView = document.getElementById('route-view') HashChange() } function HashChange() { // 每次触发 hashchange 事件,经过 location.hash 拿到当前浏览器地址的 hash 值 // 根据不一样的路径展现不一样的内容 switch(location.hash) { case '#/page1': routeView.innerHTML = 'page1' return case '#/page2': routeView.innerHTML = 'page2' return default: routeView.innerHTML = 'page1' return } } </script>
  </body>
</html>
复制代码

固然,这是很简单的实现,真正的 hash 模式,还要考虑到不少复杂的状况,你们有兴趣就去看看源码。

浏览器展现效果以下:

历史模式

history 模式会比 hash 模式稍麻烦一些,由于 history 模式依赖的是原生事件 popstate ,下面是来自 MDN 的解释:

image.png

小知识:pushState 和 replaceState 都是 HTML5 的新 API,他们的做用很强大,能够作到改变浏览器地址却不刷新页面。这是实现改变地址栏却不刷新页面的重要方法。

包括 a 标签的点击事件也是不会被 popstate 监听。咱们须要想个办法解决这个问题,才能实现 history 模式。

image.png

**解决思路:**咱们能够经过遍历页面上的全部 a 标签,阻止 a 标签的默认事件的同时,加上点击事件的回调函数,在回调函数内获取 a 标签的 href 属性值,再经过 pushState 去改变浏览器的 location.pathname 属性值。而后手动执行 popstate 事件的回调函数,去匹配相应的路由。逻辑上可能有些饶,咱们用代码来解释一下: 在线地址 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>History 模式</title>
</head>
<body>
  <div>
    <ul>
      <li><a href="/page1">page1</a></li>
      <li><a href="/page2">page2</a></li>
    </ul>
    <div id="route-view"></div>
  </div>
  <script type="text/javascript"> window.addEventListener('DOMContentLoaded', Load) window.addEventListener('popstate', PopChange) var routeView = null function Load() { routeView = document.getElementById('route-view') // 默认执行一次 popstate 的回调函数,匹配一次页面组件 PopChange() // 获取全部带 href 属性的 a 标签节点 var aList = document.querySelectorAll('a[href]') // 遍历 a 标签节点数组,阻止默认事件,添加点击事件回调函数 aList.forEach(aNode => aNode.addEventListener('click', function(e) { e.preventDefault() //阻止a标签的默认事件 var href = aNode.getAttribute('href') // 手动修改浏览器的地址栏 history.pushState(null, '', href) // 经过 history.pushState 手动修改地址栏, // popstate 是监听不到地址栏的变化,因此此处须要手动执行回调函数 PopChange PopChange() })) } function PopChange() { console.log('location', location) switch(location.pathname) { case '/page1': routeView.innerHTML = 'page1' return case '/page2': routeView.innerHTML = 'page2' return default: routeView.innerHTML = 'page1' return } } </script>
</body>
</html>
复制代码

这里注意,不能在浏览器直接打开静态文件,须要经过 web 服务,启动端口去浏览网址。

总结

这篇文章主要知识点集中在前端路由这块,能彻底看完,而且把实现原理捋一遍,我想你应该对现代前端框架会有一个新的理解。没有新的理解的同窗,来杭州打我,我不还手。

相关文章
相关标签/搜索