前端猿一天不学习就没饭吃了,后端猿三天不学习仍旧有白米饭摆于桌前。IT行业的快速发展一直在推动着前端技术栈在不断地更新换代,前端的发展成了互联网时代的一个缩影。而单页面应用的发展给前端猿分了一杯羹。
最早单页面的应用无从知晓,在2004年,google的Gmail就使用了单页面。到了2010年,随着Backbone的问世之后,此概念才慢慢热了起来。随着后来React、Angular、Vue的兴起,单页面应用才成了前端圈里人人皆知的架构模式。接下来小生将通过对比传统页面应用和单页面应用来说明SPA具体是什么。
早期web应用的前后端交互模式是这样的,每个html作为一个功能元件,通过刷新、超链接、表单提交等方式,将页面组织起来后给用户提供交互。
后期很多流行的框架都是基于此模式进行设计的,比如 Ruby on Rails,Spring MVC,Express 等等
传统的web应用中,浏览器只是作为展示层,路由、服务调用、页面跳转都是服务端来处理的。也就是MVC的架构都是放在后端的,只有V这一层,将页面通过网络发送到浏览器端,渲染给用户。
传统的模式具有以下特点:
和传统应用相比较,单页面应用就是将MVC个架构搬到了前端来实现
如此看来单页面应用很像移动客户端,后端的精力就是提供高质量的、可复用的Rest API服务。
世间万物皆有裂痕,哪又怎样?裂痕,那是光照进来的地方。
单页面应用的出现依然存在着争议性,我们该如何看待他的两面性呢?接下来小生给大家总结一下他的优缺点。
单页面应用的优势:
单页面应用的劣势:
随着SPA的流行,目前主流的框架都实现了SPA模式,包括我们夏洛克产品里面用到的Angular和Vue。但是作为一家爱折腾公司里面爱折腾的前端团队里面爱折腾的人,我们总想跟自己较劲来试试自己去实现简单的模式,这次小生也简单地实现了一把,于是将其分享于诸位,目前只是简单的模型,不能用于生产(主流框架都有,干嘛用我的?学习一下思想即可),除非你愿意折腾。在此之前需要介绍几个核心点:
关于H5 History API在此需要介绍一下,他是HTML5引入的操作浏览器路由历史堆栈的内容,其中两个主要的方法为history.pushState(stateObj, title, URL) 和 history.replaceState(stateObj, title, URL) 方法,它们分别可以添加和修改历史记录条目。这些方法通常与window.onpopstate 配合使用。三个参数分别为:
小生结合window.onpopstate事件来监听浏览器前进和后退的动作来重新请求数据服务,更新视图。
每当处于激活状态的历史记录条目发生变化时, popstate事件就会在对应window对象上触发。 如果当前处于激活状态的历史记录条目是由history.pushState()方法创建, 或者由history.replaceState()方法修改过的, 则popstate事件对象的state属性包含了这个历史记录条目的state对象的一个拷贝。
调用history.pushState()或者history.replaceState()不会触发popstate事件. popstate事件只会在浏览器某些行为下触发, 比如点击后退、前进按钮(或者在JavaScript中调用history.back()、history.forward()、history.go()方法)。
-- data
-- auto.json
-- contact.json
-- home.json
-- platform.json
-- sharplook.json
-- ajax.js
-- index.js
-- index.html
-- index.css
data文件下是模拟的后端数据,数据的结构都与下面一样,比如home.json
{
"content": "上海擎创信息技术有限公司是专业服务于企业级客户的ITOA智能运营大数据分析解决方案提供商,专注于将人工智能技术赋予IT运维管理,创造具备分析和思考能力的IT管理软件,让每家企业都拥有自己的IT运维专家。"
}
ajax.js的代码如下:
function ajax() {
const ajaxData = {
type: arguments[0].type || 'GET',
url: arguments[0].url || '',
async: arguments[0].async || 'true',
data: arguments[0].data || null,
dataType: arguments[0].dataType || 'text',
contentType: arguments[0].contentType || 'application/x-www-form-urlencoded',
beforeSend: arguments[0].beforeSend || function () {},
success: arguments[0].success || function () {},
error: arguments[0].error || function () {}
}
ajaxData.beforeSend()
const xhr = _createxmlHttpRequest();
xhr.responseType = ajaxData.dataType;
xhr.open(ajaxData.type, ajaxData.url, ajaxData.async);
xhr.setRequestHeader('Content-Type', ajaxData.contentType);
xhr.send(_convertData(ajaxData.data));
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
ajaxData.success(xhr.response);
} else {
ajaxData.error();
}
}
}
}
function _createxmlHttpRequest() {
if (window.ActiveXObject) {
return new ActiveXObject('Microsoft.XMLHTTP');
} else if (window.XMLHttpRequest) {
return new XMLHttpRequest();
}
}
function _convertData(data) {
if (typeof data === 'object') {
let convertResult = '';
for (let c in data) {
convertResult += `${c}=${data[c]}&`;
}
convertResult = convertResult.substring(0, convertResult.length - 1);
return convertResult;
} else {
return data;
}
}
index.html的代码如下:
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<head>
<title>SPA</title>
<link href="./index.css" rel="stylesheet" type="text/css" />
<script src="./ajax.js"></script>
<script src="./index.js"></script>
</head>
<body>
<main class="container">
<header class="bar">
<span class="title">SHARPLOOK 大数据运维监控平台</span>
</header>
<section class="body">
<ul id="nav">
<li><a href="/home">首页</a></li>
<li><a href="/sharplook">夏洛克</a></li>
<li><a href="/platform">自管理平台</a></li>
<li><a href="/auto">自动化安装</a></li>
<li><a href="/contact">联系我们</a></li>
</ul>
<div id="content-main">
<div id="content">
<p id="p"></p>
</div>
</div>
</section>
</main>
</body>
<script type="text/javascript">
const spa = new SPA();
spa.init();
</script>
</html>
index.js的代码如下:
class SPA {
constructor () {
this.elment = void 0;
this.menu = Array.from(document.getElementsByTagName('a'));
}
getCurrentHash() {
return window.history.state ? window.history.state.hash : '/home';
}
isSupportH5History() {
return !!(window.history && window.history.pushState);
}
setElement(hash) {
if (!hash) { // 默认为根路由 ‘/’
this.elment = this.menu[0];
} else {
this.menu.forEach(item => {
if(item.getAttribute('href') === hash) {
this.elment = item;
}
});
}
}
renderData() {
const contentElement = document.getElementById('p');
this.loadData(contentElement, this.elment.getAttribute('href').split('/')[1]);
}
addHistory(hash, isReplace) {
const stateObj = { hash };
if(isReplace) {
window.history.replaceState(stateObj, null, hash);
} else {
window.history.pushState(stateObj, null, hash);
}
}
loadData(contentElement, type) {
ajax({
type: 'get',
url: `/data/${type}.json`,
dataType: 'json',
success: function(msg) {
console.log(msg);
contentElement.innerText = msg.content;
},
error: function() {
console.log('error')
}
})
};
popStateHandler(linkHash, isPopState = false) {
if(!linkHash) {// 刷新界面时候,默认获取刷新之前的路由信息
this.addHistory(this.getCurrentHash(), true);
} else {
if(!isPopState) this.addHistory(linkHash, false);
}
this.setElement(this.getCurrentHash());
this.renderData();
this.addActiveClass();
}
bindLiClick() {
const list = document.getElementsByTagName('li');
Array.from(list).forEach(item => {
item.onclick = (event) => {
const linkHash = item.childNodes[0].getAttribute('href');
this.popStateHandler(linkHash);
}
});
}
addActiveClass() {
this.menu.forEach(item => {
item.parentNode.classList.remove('active');
})
this.elment.parentNode.classList.add('active');
}
init() {
if(!this.isSupportH5History()) throw new Error('对不起!不支持 H5 History API!');
this.bindLiClick();
window.onpopstate = (event) => {
this.popStateHandler(event.state.hash, true);
}
// 首次默认首次进入页面
this.popStateHandler();
}
}
index.css的代码如下:
* {
margin: 0;
padding: 0;
}
html, body {
height: 100%;
}
.container {
height: 100%;
display: flex;
flex-direction: column;
}
.bar {
background-color: #213442;
color: white;
height: 60px;
display: flex;
align-items: center;
font-size: 20px;
}
.body {
display: flex;
height: calc(100% - 60px);
border-top: 1px solid #ccc;
}
#content {
border: 1px solid #ccc;
border-radius: 3px;
padding: 10px;
width: 600px;
box-shadow: 5px 5px 15px 0 #bbb;
}
#nav {
background-color: #213442;
width: 120px;
text-align: center;
}
a {
color: white;
text-decoration: none;
pointer-events: none;
}
#content-main {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
.title {
padding-left: 20px;
}
li {
margin: 10px 0;
line-height: 30px;
cursor: pointer;
}
li:hover {
background-color: #00A5D5;
}
.active {
background-color: #00A5D5;
}
代码的具体逻辑不做过多介绍,特别需要注意的是请将代码部署到web服务器上查看效果,因为history api需要在同域里面才能使用,否则报错,爱学习的小伙伴请自行学习。 完整代码
小生给大家介绍了目前web开发的SPA模式,希望诸君在使用主流框架时能进一步了解其原理,你我共勉。小生基于H5的History实现了一个简单的SPA模式,仅供学习之用,最后小生想说,身为后端转为前端的前端猿,感觉前端的技术栈应是最有活力的,因为一旦你不想动了,就如温水里的青蛙,距离另一个世界也就近了,祝君能像前端的发展势头一样,活力四射,不断进步。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章