一、什么是SPA应用

SPA的主要思想是利用js动态地将内容加载到当前页面,而不需要从服务器加载整个页面。会给你一种桌面应用的感觉。

所有的 HTMLJavaScriptCSS 都可以在第一次加载应用程序时从服务器中下载,而不是像传统应用那样每次加载都要重新下载所有的资源文件。

另一个要点是,主页或内容永远不会重新加载,但是 hashHTML5 history API,您仍然拥有不同的url和浏览器历史记录。

二、它能带来什么好处

让我们来说一说SPA应用的优势:

  • 性能

SPA只有第一次需要加载所有的资源到本地,后面的请求都只会加载必要的资源,这大大节省了资源下载和页面渲染的时间。

  • 更好的用户体验

页面加载资源减少了,页面渲染的速度更快了,用户自然会感觉到更好的交互体验。

当然,凡事有利就有弊,现在我们来说一下SPA应用的缺点:

  • 首屏渲染会很慢

  • 客户端脚本必须启用

  • 安全性

三、如何实现一个SPA

通过 hash 来实现一个SPA应用:

目录结构

SPA-folder

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Single Page Application</title>
</head>
<body>
<ul>
<li><a href="#home">Home</a></li>
<li><a href="#about">About</a></li>
</ul>
<div id="app">

</div>
<script src="js/route.js"></script>
<script src="js/router.js"></script>
<script src="js/app.js"></script>
</body>
</html>

Route JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
'use stict';

function Route(name, htmlName, defaultRoute) {
try {
if(!name || !htmlName) {
throw 'error: name and htmlName params are mandatories';
}
this.constructor(name, htmlName, defaultRoute);
} catch (e) {
console.error(e);
}
}

Route.prototype = {
name: undefined,
htmlName: undefined,
default: undefined,
constructor: function (name, htmlName, defaultRoute) {
this.name = name;
this.htmlName = htmlName;
this.default = defaultRoute;
},
isActiveRoute: function (hashedPath) {
return hashedPath.replace('#', '') === this.name;
}
}

Router JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
'use strict';

function Router(routes) {
try {
if (!routes) {
throw 'error: routes param is mandatory';
}
this.constructor(routes);
this.init();
} catch (e) {
console.error(e);
}
}

Router.prototype = {
routes: undefined,
rootElem: undefined,
constructor: function (routes) {
this.routes = routes;
this.rootElem = document.getElementById('app');
},
init: function () {
var r = this.routes;
(function(scope, r) {
window.addEventListener('hashchange', function (e) {
scope.hasChanged(scope, r);
});
})(this, r);
this.hasChanged(this, r);
},
hasChanged: function(scope, r){
if (window.location.hash.length > 0) {
for (var i = 0, length = r.length; i < length; i++) {
var route = r[i];
if(route.isActiveRoute(window.location.hash.substr(1))) {
scope.goToRoute(route.htmlName);
}
}
} else {
for (var i = 0, length = r.length; i < length; i++) {
var route = r[i];
if(route.default) {
scope.goToRoute(route.htmlName);
}
}
}
},
goToRoute: function (htmlName) {
(function(scope) {
var url = 'views/' + htmlName,
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
if (this.readyState === 4 && this.status === 200) {
scope.rootElem.innerHTML = this.responseText;
}
};
xhttp.open('GET', url, true);
xhttp.send();
})(this);
}
};

初始化路由

app.js 里面初始化路由:

1
2
3
4
5
6
7
8
9
10
11
'use strict';

(function () {
function init() {
var router = new Router([
new Route('home', 'home.html', true),
new Route('about', 'about.html')
]);
}
init();
}());

通过调用自执行的 init 函数,定义了两条路由,并且将home设置为默认路由。

四、总结

这篇文章展示了实现SPA应用的实践,也希望能帮助你理解现代前端框架在处理SPA单页应用的思想。