什么是前端路由?
所谓的前端路由,拥有这样一种能力:客户端浏览器可以不依赖服务端,根据不同的URL渲染不同的视图页面。
为什么要有前端路由?
首先,它的出现无疑减轻了服务器端的压力。特别是对于一个比较复杂的应用来讲,或者更确切的说,对于拥有一个复杂路由系统的应用来说,服务器端需要为每一个不同的url执行一段处理逻辑在高并发的情况下实在有点不堪重负;其次,页面的切换可以不需要刷新整个页面了,没有网络延迟,没有闪烁刷新,提升了用户体验。
前端路由实现方式
首先我们知道了前端路由是不依赖服务器的,所以我们在前端需要做的就是:
- 在不改变页面的前提下实现url的变化
- 捕捉到url的变化以便执行页面替换
在不改变页面的前提下如果实现url的变化?这里有两种方式:
- 使用url中的hash字段
- 使用html5的history API
hash方式
了解http协议就会知道,url的组成部分有很多,譬如协议、主机名、资源路径、查询字段等等,其中包含一个称之为片段的部分,以“#”为标识。例如:www.gmail.com/text/#123
打开控制台,输入 location.hash,你可以得到当前url的hash部分(如果当前url不存在hash则返回空字符串)。
比如:
//https://www.nowcoder.com/discuss/33880?type=2&order=0&pos=57&page=1
console.log(location.hash); //''
接下来,输入 location.hash = '123',会发现浏览器地址栏的url变了,末尾增加了’#123’字段,并且,页面没有被重新刷新。很显然,这很符合我们的要求。
history API
html5引入了一个history对象,包含了一套访问浏览器历史的api,可以通过window.history访问到它。
这里我们要用的是它的两个api方法:pushState
和 replaceState
。
history.replaceState(dataObj, title, url);
history.pushState(dataObj, title, url);
若上所示,它们接收完全相同的参数,都是对浏览器的历史栈进行操作,将传递的url和相关数据压栈,并将浏览器地址栏的url替换成传入的url且不刷新页面。
这两个 API 都接收三个参数,分别是:
- 状态对象(state object) — 一个JavaScript对象,与用pushState()方法创建的新历史记录条目关联。无论何时用户导航到新创建的状态,popstate事件都会被触发,并且事件对象的state属性都包含历史记录条目的状态对象的拷贝。
- 标题(title) — FireFox浏览器目前会忽略该参数,虽然以后可能会用上。考虑到未来可能会对该方法进行修改,传一个空字符串会比较安全。或者,你也可以传入一个简短的标题,标明将要进入的状态。
- 地址(URL) — 新的历史记录条目的地址。浏览器不会在调用pushState()方法后加载该地址,但之后,可能会试图加载,例如用户重启浏览器。新的URL不一定是绝对路径;如果是相对路径,它将以当前URL为基准;传入的URL与当前URL应该是同源的,否则,pushState()会抛出异常。该参数是可选的;不指定的话则为文档当前URL。
相同之处是两个 API 都会操作浏览器的历史记录,而不会引起页面的刷新。
不同之处在于,pushState会增加一条新的历史记录,而replaceState则会替换当前的历史记录。
以https://www.baidu.com/
为例:
控制台输入
window.history.pushState(null, null, "https://www.baidu.com/?name=orange");
于是我们的url变成了
url并且页面并没有刷新,只是url栏发生了变化。
注意:这里的 url 不支持跨域,当我们把 www.baidu.com 换成 baidu.com 时就会报错。
到此我们在不改变页面的前提下实现了url的变化。
那么如何捕捉url的变化进行页面的替换呢?
对于hash方式的前端路由,通常可以监听 hashchange 事件,在事件回调中处理相应的页面视图展示等逻辑。
<script>
function change() {
alert("ok!");
}
</script>
</head>
<body onhashchange="change()">
</body>
在浏览器输入location.hash = "123"
,即弹出“ok!”
此外,html5提供的 popstate 事件也会在url的hash发生改变时触发。也就是说如果可以忽略低版本浏览器,我们使用hash方式路由时也可以采用监听这个事件进行回调处理。
那么,如果是采用history API的形式呢?根据MDN的描述:
调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件。popstate 事件只会在浏览器某些行为下触发, 比如点击后退按钮(或者在JavaScript中调用 history.back() 方法)。
这也就是说,我们在使用history API改变浏览器的url时,仍需要额外的步骤去触发 popstate 事件,例如调用 history.back() 会 history.forward() 等方法。
window.onpopstate = function(event) {
alert("location: " + document.location + ", state: " + JSON.stringify(event.state));
};
history.pushState({page: 1}, "title 1", "?page=1");
history.pushState({page: 2}, "title 2", "?page=2");
history.replaceState({page: 3}, "title 3", "?page=3");
history.back(); // alerts "location: http://example.com/example.html?page=1, state: {"page":1}"
history.back(); // alerts "location: http://example.com/example.html, state: null
history.go(2); // alerts "location: http://example.com/example.html?page=3, state: {"page":3}
不存在纯前端路由
我们此前所描述的前端路由,建立在已经打开了一个初始页面基础之上,然后在这个页面之内进行页面替换。然而,我们如何进入这个初始页面?仅靠前端路由肯定是力所不及。我们至少要向后端发送一次http请求,接收所需要加载的页面不是吗?
所以,我们并不能抛弃后端路由部分。这也意味着,我们需要和后端确认各自的分工,哪些url归前端解析,哪些归后台解析。
网友评论