[{"data":1,"prerenderedAt":494},["ShallowReactive",2],{"content:\u002F2021\u002Ffrontend-routing":3,"surround:\u002F2021\u002Ffrontend-routing":483},{"id":4,"title":5,"body":6,"categories":450,"date":452,"description":91,"draft":453,"extension":454,"image":455,"meta":456,"navigation":458,"path":459,"permalink":455,"published":455,"readingTime":460,"recommend":455,"references":465,"seo":474,"sitemap":475,"stem":478,"tags":479,"type":481,"updated":455,"__hash__":482},"content\u002Fposts\u002F2021\u002Ffrontend-routing.md","浅析前端路由",{"type":7,"value":8,"toc":439},"minimark",[9,13,31,39,42,62,67,84,94,99,149,157,163,167,184,188,225,253,259,272,277,281,290,293],[10,11,12],"h2",{"id":12},"什么是前端路由",[14,15,16,17,21,22,26,27,30],"p",{},"用最简单的话来说就是，",[18,19,20],"strong",{},"页面间的跳转仅由前端来控制，而不需要向后端发起请求","。前端路由反映的是",[23,24,25],"code",{"code":25},"URL","和",[23,28,29],{"code":29},"组件","之间的映射关系。",[14,32,33],{},[34,35],"img",{"alt":36,"src":37,"title":38},"route_01","\u002Fimages\u002Ffrontend-routing\u002Froute_01.png","前端路由反映的是 URL 和组件之间的映射关系",[10,40,41],{"id":41},"如何实现前端路由",[14,43,44,45,48,49,48,52,55,56,58,59,61],{},"vue-router 中有 3 种路由模式：",[23,46,47],{"code":47},"hash","、",[23,50,51],{"code":51},"history",[23,53,54],{"code":54},"abstract","（不常用），这里仅介绍下 ",[23,57,47],{"code":47}," 模式和 ",[23,60,51],{"code":51}," 模式的简单实现",[63,64,66],"h3",{"id":65},"hash-模式","hash 模式",[14,68,69,70,73,74,76,77,79,80,83],{},"早期的前端路由的实现就是基于 ",[23,71,72],{"code":72},"location.hash"," 来实现的。实现原理很简单，",[23,75,72],{"code":72}," 的值就是 URL 中 # 后面的内容。比如下面这个网站，它的 ",[23,78,72],{"code":72}," 的值为 ",[23,81,82],{"code":82},"'#first'","。",[85,86,92],"pre",{"className":87,"code":89,"language":90,"meta":91},[88],"language-raw","http:\u002F\u002Fwww.example.com#first\n","raw","",[23,93,89],{"__ignoreMap":91},[14,95,96,98],{},[23,97,47],{"code":47}," 路由模式的实现主要基于下面几个特性：",[100,101,102,113,121,138],"ul",{},[103,104,105,106,108,109,112],"li",{},"URL 中 ",[23,107,47],{"code":47}," 值只是客户端的一种状态，也就是说当向服务器端发出请求时，",[18,110,111],{},"hash 部分并不会被发送","；",[103,114,115,117,118,112],{},[23,116,47],{"code":47}," 值的任意改变，都会在浏览器的访问历史中增加一个记录。因此我们能",[18,119,120],{},"通过浏览器的回退、前进按钮来控制 hash 的切换",[103,122,123,124,127,128,130,131,134,135,137],{},"通过设置 a 标签的 ",[23,125,126],{"code":126},"href"," 属性，当用户点击这个标签后，URL 的 ",[23,129,47],{"code":47}," 值会发生改变；或者直接对 ",[23,132,133],{"code":133},"loaction.hash"," 进行赋值，改变 URL 的 ",[23,136,47],{"code":47}," 值；",[103,139,140,141,144,145,148],{},"通过浏览器原生的 ",[23,142,143],{"code":143},"hashchange"," 事件来",[18,146,147],{},"监听 hash 值的变化","，从而对页面进行跳转（渲染）。",[85,150,155],{"className":151,"code":153,"language":154,"meta":91},[152],"language-html","\u003Cul>\n  \u003Cli>\u003Ca href=\"#\u002Fpage1\">page1\u003C\u002Fa>\u003C\u002Fli>\n  \u003Cli>\u003Ca href=\"#\u002Fpage2\">page2\u003C\u002Fa>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C!--渲染对应组件的地方-->\n\u003Cdiv id=\"route-view\">\u003C\u002Fdiv>\n\n\u003Cscript type=\"text\u002Fjavascript\">\n  \u002F\u002F 第一次加载的时候，不会执行 hashchange 监听事件，默认执行一次\n  \u002F\u002F DOMContentLoaded 为浏览器 DOM 加载完成时触发\n  window.addEventListener('DOMContentLoaded', Load);\n  window.addEventListener('hashchange', HashChange);\n  \u002F\u002F 展示页面组件的节点\n  var routeView = null;\n  function Load() {\n    routeView = document.getElementById('route-view');\n    HashChange();\n  }\n  function HashChange() {\n    \u002F\u002F 每次触发 hashchange 事件，通过 location.hash 拿到当前浏览器地址的 hash 值\n    \u002F\u002F 根据不同的路径展示不同的内容\n    switch (location.hash) {\n      case '#\u002Fpage1':\n        routeView.innerHTML = 'page1';\n        return;\n      case '#\u002Fpage2':\n        routeView.innerHTML = 'page2';\n        return;\n      default:\n        routeView.innerHTML = 'page1';\n        return;\n    }\n  }\n\u003C\u002Fscript>\n","html",[23,156,153],{"__ignoreMap":91},[158,159,160],"blockquote",{},[14,161,162],{},"这仅是最简易的实现，真实的 hash 模式，还要考虑到很多复杂的情况，具体情况可以去看 Vue Router 的源码。",[63,164,166],{"id":165},"history-模式","history 模式",[14,168,169,171,172,175,176,179,180,183],{},[23,170,51],{"code":51}," 模式的实现基于 HTML5 提供的 History API，其中最主要的 API 为：",[23,173,174],{"code":174},"history.pushState()"," 和 ",[23,177,178],{"code":178},"history.repalceState()","。这两个 API 均可在",[18,181,182],{},"不刷新页面","的情况下，对浏览器的历史记录进行操作。唯一不同的是，前者是新增一个历史记录，后者是直接替换当前的历史记录。",[14,185,186,98],{},[23,187,51],{"code":51},[100,189,190,202,208],{},[103,191,192,193,175,195,197,198,201],{},"通过 ",[23,194,174],{"code":174},[23,196,178],{"code":178}," 这两个 API 来操作实现 URL 的变化（实际上是改变了浏览器的 ",[23,199,200],{"code":200},"location.pathname"," 属性值）；",[103,203,140,204,207],{},[23,205,206],{"code":206},"popstate"," 事件来监听浏览器动作的变化，从而对页面进行跳转（渲染）；",[103,209,210,211,213,214,217,218,220,221,224],{},"但 ",[23,212,174],{"code":174}," 或 ",[23,215,216],{"code":216},"history.replaceState()"," 只能改变 URL，并不会触发 ",[23,219,206],{"code":206}," 事件，需要",[18,222,223],{},"手动触发","页面跳转（渲染）。",[158,226,227],{},[14,228,229,230,213,232,234,235,237,238,241,242,245,246,252],{},"需要注意的是调用 ",[23,231,174],{"code":174},[23,233,216],{"code":216}," 不会触发 ",[23,236,206],{"code":206}," 事件。只有在做出浏览器动作时，才会触发该事件，如用户点击浏览器的回退按钮（或者在 Javascript 代码中调用 ",[23,239,240],{"code":240},"history.back()"," 或者 ",[23,243,244],{"code":244},"history.forward()"," 方法）（摘自 MDN 对 ",[247,248,206],"a",{"href":249,"rel":250},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FAPI\u002FWindow\u002Fpopstate_event",[251],"nofollow"," 的解释）",[85,254,257],{"className":255,"code":256,"language":154,"meta":91},[152],"\u003Cul>\n  \u003Cli>\u003Ca href=\"\u002Fpage1\">page1\u003C\u002Fa>\u003C\u002Fli>\n  \u003Cli>\u003Ca href=\"\u002Fpage2\">page2\u003C\u002Fa>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cdiv id=\"route-view\">\u003C\u002Fdiv>\n\n\u003Cscript type=\"text\u002Fjavascript\">\n  window.addEventListener('DOMContentLoaded', Load);\n  window.addEventListener('popstate', PopChange);\n  var routeView = null;\n  function Load() {\n    routeView = document.getElementById('route-view');\n    \u002F\u002F 默认执行一次 popstate 的回调函数，匹配一次页面组件\n    PopChange();\n    \u002F\u002F 获取所有带 href 属性的 a 标签节点\n    var aList = document.querySelectorAll('a[href]');\n    \u002F\u002F 遍历 a 标签节点数组，阻止默认事件，添加点击事件回调函数\n    aList.forEach((aNode) =>\n      aNode.addEventListener('click', function (e) {\n        e.preventDefault(); \u002F\u002F阻止a标签的默认事件\n        var href = aNode.getAttribute('href');\n        \u002F\u002F  手动修改浏览器的地址栏\n        history.pushState(null, '', href);\n        \u002F\u002F 通过 history.pushState 手动修改地址栏，\n        \u002F\u002F popstate 是监听不到地址栏的变化，所以此处需要手动执行回调函数 PopChange\n        PopChange();\n      })\n    );\n  }\n  function PopChange() {\n    console.log('location', location);\n    switch (location.pathname) {\n      case '\u002Fpage1':\n        routeView.innerHTML = 'page1';\n        return;\n      case '\u002Fpage2':\n        routeView.innerHTML = 'page2';\n        return;\n      default:\n        routeView.innerHTML = 'page1';\n        return;\n    }\n  }\n\u003C\u002Fscript>\n",[23,258,256],{"__ignoreMap":91},[14,260,261,262,265,266,268,269,271],{},"思路：通过遍历页面上的所有 a 标签，阻止 a 标签的默认事件的同时，加上点击事件的回调函数，在回调函数内获取 a 标签的 href 属性值，再通过 ",[23,263,264],{"code":264},"pushState"," 去改变浏览器的 ",[23,267,200],{"code":200}," 属性值。然后手动执行 ",[23,270,206],{"code":206}," 事件的回调函数，去匹配相应的路由。",[158,273,274],{},[14,275,276],{},"注意，以上代码不能在浏览器直接打开静态文件（会报错），需要通过 web 服务，启动端口去浏览网址。",[10,278,280],{"id":279},"单页面应用-vs-多页面应用","单页面应用 v.s. 多页面应用",[14,282,283,284,289],{},"对前端来说，路由概念的出现是伴随着",[247,285,288],{"href":286,"rel":287},"https:\u002F\u002Fzh.wikipedia.org\u002Fwiki\u002F%E5%8D%95%E9%A1%B5%E5%BA%94%E7%94%A8",[251],"SPA","出现的；在SPA出现之前，页面的跳转(导航)都是通过服务端来控制的，并且页面跳转存在一个明显白屏跳转过程；SPA出现后，用户体验好、快，内容的改变不需要重新加载整个页面，避免了不必要的跳转和重复渲染，就不再让服务端控制页面跳转了，于是前端路由出现了，前端可以自由控制组件的渲染，来模拟页面跳转。",[14,291,292],{},"单页面应用和多页面应用的对比如下：",[294,295,296,311],"table",{},[297,298,299],"thead",{},[300,301,302,305,308],"tr",{},[303,304],"th",{},[303,306,307],{},"单页面应用（SinglePage Web Application，SPA）",[303,309,310],{},"多页面应用（MultiPage Application，MPA）",[312,313,314,326,337,348,374,385,396,407,417,428],"tbody",{},[300,315,316,320,323],{},[317,318,319],"td",{},"组成",[317,321,322],{},"一个外壳页面和多个页面片段组成",[317,324,325],{},"多个完整页面构成",[300,327,328,331,334],{},[317,329,330],{},"资源共用（css，js）",[317,332,333],{},"共用，只需在外壳部分加载",[317,335,336],{},"不共用，每个页面都需要加载",[300,338,339,342,345],{},[317,340,341],{},"刷新方式",[317,343,344],{},"页面局部刷新或更改",[317,346,347],{},"整页刷新",[300,349,350,353,364],{},[317,351,352],{},"URL 模式",[317,354,355,358,361],{},[23,356,357],{"code":357},"xxx.com\u002F#\u002Ffirst",[359,360],"br",{},[23,362,363],{"code":363},"xxx.com\u002F#\u002Fsecond",[317,365,366,369,371],{},[23,367,368],{"code":368},"xxx.com\u002F#\u002Ffirst.html",[359,370],{},[23,372,373],{"code":373},"xxx.com\u002F#\u002Fsecond.html",[300,375,376,379,382],{},[317,377,378],{},"用户体验",[317,380,381],{},"页面片段间切换快，用户体验好",[317,383,384],{},"页面切换加载缓慢，流畅度不够，用户体验较差",[300,386,387,390,393],{},[317,388,389],{},"转场动画",[317,391,392],{},"容易实现",[317,394,395],{},"无法实现",[300,397,398,401,404],{},[317,399,400],{},"数据传递",[317,402,403],{},"容易",[317,405,406],{},"依赖 URL 传参，或者 cookie、localStorage 等",[300,408,409,412,415],{},[317,410,411],{},"搜索引擎优化（SEO）",[317,413,414],{},"需要单独方案、实现较为困难、不利于SEO检索、可利用服务端渲染（SSR）优化",[317,416,392],{},[300,418,419,422,425],{},[317,420,421],{},"使用范围",[317,423,424],{},"高要求的体验度、追求界面流畅的应用（一般是后台管理系统）",[317,426,427],{},"适用于追求高度支持搜索引擎的应用",[300,429,430,433,436],{},[317,431,432],{},"维护成本",[317,434,435],{},"相对容易",[317,437,438],{},"相对复杂",{"title":91,"searchDepth":440,"depth":440,"links":441},4,[442,444,449],{"id":12,"depth":443,"text":12},2,{"id":41,"depth":443,"text":41,"children":445},[446,448],{"id":65,"depth":447,"text":66},3,{"id":165,"depth":447,"text":166},{"id":279,"depth":443,"text":280},[451],"学习笔记","2021-02-22 17:12:30",false,"md",null,{"comments":453,"slots":457},{},true,"\u002F2021\u002Ffrontend-routing",{"text":461,"minutes":462,"time":463,"words":464},"8 min read",7.55,453000,1510,[466,468,471],{"title":467,"link":467},"https:\u002F\u002Fjuejin.cn\u002Fpost\u002F6844903918753808398#heading-19",{"title":469,"link":470},"你好，谈谈你对前端路由的理解","https:\u002F\u002Fjuejin.cn\u002Fpost\u002F6917523941435113486",{"title":472,"link":473},"前端：你要懂的单页面应用和多页面应用","https:\u002F\u002Fjuejin.cn\u002Fpost\u002F6844903512107663368",{"title":5,"description":91},{"loc":459,"images":476},[477],{"loc":37},"posts\u002F2021\u002Ffrontend-routing",[480],"前端路由","tech","3wL0tLJdGdSI9M7XAcpNI-dlLVtsiVO4_h8PZENHefU",[484,489],{"title":485,"path":486,"stem":487,"date":488,"type":481,"children":-1},"Vue 两个版本的区别（完整版 v.s. 运行时版）","\u002F2020\u002Fvue-difference-version","posts\u002F2020\u002Fvue-difference-version","2020-06-30 20:56:48",{"title":490,"path":491,"stem":492,"date":493,"type":481,"children":-1},"Google 搜索技巧","\u002F2021\u002Fgoogle-search-skills","posts\u002F2021\u002Fgoogle-search-skills","2021-03-01 11:28:30",1793699840086]