1. 从前的请求 —— 服务端渲染(SSR:Server Side Render)
在最开始,在浏览器中向服务端发送一个请求,服务端会把 html 字符串生成好,再发送给客户端,客户端直接渲染即可。
并不是说一个 html 就只请求了一次服务端。一个 html 里可能会引用一些 js、css 文件,这些静态资源都是放在服务端的。当浏览器渲染服务端返回的 html 字符串,读到诸如
<script src="js/jquery.js"></script>
时会再次向服务端发送一条请求去获取 jquery.js 资源的,获取回来之后在浏览器中加载一遍。
缺点:在后管系统中,当从“用户管理”菜单跳转到“角色管理”菜单时,往往变化的只是表格的字段以及数据,但在服务端渲染中每次都需要全部请求一遍。请求头部横条,请求左侧菜单栏,重新加载 jquery.js 等等。我们希望不需要改变的东东就别动了,修改变局部。
2. Ajax 出现实现局部刷新
ajax 的出现解决了上面每次请求页面都全部刷新的问题,只是请求数据,然后通过 Dom 操作修改 html 中表格相关的东东。
缺点:使用 ajax 异步请求之后,点击浏览器的回退上一个页面会发现没效果,因为这次请求仅仅是数据的请求,并没有重新刷新页面,也就没有加入到浏览器历史栈中。
3. 单页应用(SPA:Single Pagination Application)
单页应用的出现解决了服务端渲染和 ajax 存在的问题。
解决服务端渲染的问题:
人们开始想,从“用户管理”菜单跳转到“角色管理”菜单时,需要请求服务端获取 html 之后在浏览器中渲染。这个渲染的过程包括创建一个个 dom 元素,如 div dom 元素,p dom 元素。他们会认为这一过程很消耗性能,事实也是如此,可以试着在浏览器调试台中输入:document.createElement('div') 会发现仅仅创建一个 div 元素附带创建了很多属性和方法,那些东东你可能压根就用不上。
单页应用的解决方式是,在页面第一次加载的时候就把“用户管理”的表格 dom 元素和“角色管理”的表格 dom 元素都加载进来,当再次从“用户管理”切换到“角色管理”时,只需要渲染
“角色管理” 的表格 dom,而不是像服务端渲染那样将整个页面都渲染一遍,渲染的少了,性能上无疑得到了提升。这一点和 ajax 有点相似。
单页应用与 ajax 的区别:
ajax 是先请求数据,然后在浏览器通过 js 创建 dom 进行替换。单页应用在第一次加载应用的时候将 dom 都加载到浏览器中,只不过先用了“用户管理”表格 dom,“角色管理”表格 dom 也加载进来了,放在那暂时不用。这种通过点击不同菜单,自动替换不同的 dom 的方式就叫做路由。
还有一点区别就是单页应用解决了 ajax 的问题:单页应用通过 hashHistory 和 browerHistory 可以将每次的路由都加入到浏览器的历史栈,这样点击回退按钮就可以回到上一个页面。
说起来 ajax 也可以通过 hashHistory 和 browerHistory 将路由添加到历史栈,只不过历史大环境下决定它还是功能专一点。
当然,单页应用也有个大问题,那就是首次加载时间很长,造成页面出现白屏。单页应用第一次加载时就会取请求服务端获取所有 html 和 js,然后接下来的操作都在浏览器端进行了,
浏览器会去渲染这些文件,但由于东东太多了,在浏览器端会出现短暂白屏的现象。
为什么服务端渲染不会有白屏现象:因为服务端只会返回 html字符串,浏览器只要渲染一下即可,渲染的东西少了,自然就快了。
4. 路由的概念
去银行办理业务,银行小姐姐会询问你办理什么业务,然后教你不同的操作。有一张表:
业务 | 操作 |
---|---|
办理银行卡 | 先做xx,再做xx |
办理网上银行业务 | 先做yy,再做yy |
去上厕所,如果你是女生,就进入女厕所,你是男生,就进入男厕所。有一张表:
性别 | 厕所 |
---|---|
男生 | 男厕所 |
女生 | 女厕所 |
你去你女朋友宿舍楼下找她,你告诉宿管大妈你女朋友是xx专业xx班的学生,宿管大妈告诉你去几楼几零几。有一张表:
姓名 | 宿舍 |
---|---|
张晓丽 | 503 宿舍 |
李美丽 | 301 宿舍 |
回到浏览器路由,在浏览器中输入“localost:8000/”会显示首页,输入“localost:8000/user”会显示用户页面,输入“localost:8000/role”会显示角色页面。有一张表:
路由 | 视图 |
---|---|
/ | 首页 |
/user | 用户页面 |
/role | 角色页面 |
在浏览器中路由的作用就是根据你的输入的地址,显示不同的视图。那就意味着首先要定义这样一张路由表。
5. 路由/单页应用的实现原理
了解了路由表,好奇宝宝肯定想知道具体如何实现呢?
单页应用路由的实现分为两个步骤:改变浏览器地址栏地址,视图切换。
1)HashHistory 的应用
- 改变浏览器地址栏地址
window.location.hash = 'bbbb' 浏览器地址会变成 localhost:8080/#bbbb
window.location 对象专门处理浏览器地址栏各种操作。
使用 window.location.hash 操作时,已经将路由 '#bbbb' 添加到了浏览器历史栈中,点击回退按钮可以回退到上一个页面。 - 添加浏览器事件监听函数
window.addEventListener('hashchange', function () {})
此时如果 hash 变化了,就会触发该事件,在这个事件里就可以实现替换局部 dom 的实现了。
2)Browser History (h5 history)的出现
使用 HashHistory 产生的路由 localhost:8080/#bbbb 会有个井号 #,很多人认为这很不美观,但一直也无计可施,直到 h5 的出现,带来了新的 API —— window.history 的更新。
- 改变浏览器的地址栏地址
var stateObj = { foo: "bar" };
history.pushState(stateObj, "page 2", "user");
可以看到路由变成了 localhost:8000/user,没有了那不美观的 #。关于history api 具体用法,参考:http://blog.csdn.net/tianyitianyi1/article/details/user - 添加浏览器事件监听函数
通过 history api 改变浏览器地址栏地址同样会触发一个事件
window.onpopstate = function () { }
3)手写 js 实现一个简单的路由功能:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Hash</title>
</head>
<body>
<ul class="cs">
<li class="li">
<a href="#/" class="button button-primary">首页</a>
</li>
<li class="li">
<a href="#/nav" class="button button-highlight">菜单</a>
</li>
<li class="li">
<a href="#/subpage" class="button button-royal">子页</a>
</li>
</ul>
<div id="app" style="border:2px solid #f00;height:30px;">
Hello
</div>
<script>
function Router(){
//有个名字,用做一件事情
this.routes = [];
this.currentUrl = "";
this.init();//先让他初始化监听事件,然后才调用路由
}
Router.prototype = {
constructor:Router,
router:function(path,callback){
this.routes[path] = callback || function(){};
},
refresh:function(){
this.currentUrl = location.hash.slice(1) || '/';
this.routes[this.currentUrl]();//通过路由名字去调用相对应的方法
},
init:function(){//监听页面:加载的时候、监听改变的时候
window.addEventListener("load",this.refresh.bind(this),false);
window.addEventListener("hashchange",this.refresh.bind(this),false);
}
}
var appObj = document.getElementById("app");
function changeText(text){
appObj.innerHTML = text;
}
var router = new Router();
router.router("/",function(){
changeText("首页");
});
router.router("/nav",function(){
changeText("菜单");
});
router.router("/subpage",function(){
changeText("子页");
});
</script>
</body>
</html>
6. 最终
目前由于单页应用操作起来更快速,除了首屏加载尴尬的问题,人们提出的方案是使用服务端渲染首屏,之后其它操作都进行单页应用的操作来规避首屏加载的问题。
最终,当硬件与软件发展到一定程度,网速加速到一定程度,单页应用首屏加载虽然需要请求很多东东,但由于网速的变快一瞬间就加载下来,服务端渲染可能就会慢慢退出历史舞台,这也是一种技术的发展趋势。