前言
本篇是该书系列最后一篇,本书所有涉及的功能和其中所涉及的知识点,基本已经学习完成。
大体上有以下几个部分
简单的页面布局
第一个部分首先是进行简单的页面,和其布局的设计,并且采用的是模块化思想,组件组装器概念,因此每个模块都有自己的页面代码,在加载模块的时候将其渲染出来。
功能添加和功能容器,或者说模块容器及其子模块
这部分主要涉及,模块容器
shell
和页面子模块的完成,这部分主要值得学习的是该单页应用的实现思想,即组件组装器概念:也就是将shell
定义成全局的容器作用,在该容器中负责着子组件的加载和初始化行为,而子组件的功能实现被限定在自身模块当中,从而不会与其他模块产生混淆,降低了模块与模块之间的依赖性;数据模型模块
这部分主要负责数据模型的创建和管理,比如(用户管理对象
people
和chat
聊天消息对象等等),而数据模块负责对该应用涉及到的数据的增删改查等功能的实现。服务器
如果说前面几个部分都属于客户端一块的东西,那这以及这以后的部分则是涉及到服务器端的业务和功能的内容了,该应用的服务器部分自然由
Node.js
来承担,而其中涉及的路由、通信、数据库则是由express
,socket.io
,和mongodb
等来完成,并且在后台服务器的处理过程中,还涉及了一些认证,数据验证,CRUD
等概念。
经过该书的系统性学习加实践,对于单页应用的大体开发流程,和其中涉及的部分关键技术有了更深的认识,其中也对部分关键技术做了更深入的研究,比如(Node.js
,express
,socket.io
,mongodb
),并且在该书系列完成之后便会依次完整系统性的去学习这些知识点。
知识点回顾
模块化思想
模块化实现,不仅让功能进行分离,减少模块间的依赖,还可使代码更容易维护,甚至能使模块得到复用,好处自然是值得称赞。
比如当前主流的模块化编程有(CommonJS
,AMD
,CMD
,ES6 标准
),这里简单看下它们的简单使用方式,稍详细点的介绍,之前有篇文章是关于这几个的,地址:WEB前端模块化基础知识 go ✈
CommonJS
相对而言,
CommonJS
模块化更偏向于服务器端,也正由于它的发展就是由服务器端模块化的需要,随着技术不断更新和发展,前端模块化的概念推出,CommonJS
在Node.js
的推动下大力支持了前端模块化。// welcome.js var hello = 'hello world!'; function sayHello () { alert( hello ); } function bye() { alert( 'bye bye !' ); } module.exports = { hello : sayHello, bye : bye }
使用:
// test.js var welcome = require( './welcome' ); welcome.hello(); // 'hello world!' welcome.bye(); // 'bye bye !'
AMD
AMD
:Asynchronous Module Definition,异步模块定义,属于异步加载,这个其实是一套基于浏览器模块化开发的规范,要使用该规范进行模块化开发,需要用到其中的函数库:requireJS
相比较
CMD
而言,这个有个不好的地方就是,模块需要提前一次性进行加载。来看下器定义和使用:
定义:
// module.js define( ['dependModule1', 'dependModule2', ...], // 这个数组里面包含了该模块所依赖的所有模块 function () { // 这里面就是新模块的内容 return { // 返回模块对象 }; } );
会发现上面模块的定义时,
define
中并没有声明模块的名称,这是因为对于AMD
而言,一个文件既是一个模块,因此该模块的文件名也就是该模块的名称:module
使用:
// test.js // 加载模块 require(['module'], function ( myModule ) { // myModule 就是模块加载完成之后,该模块的对象名,在这个回调里面 // 可以通过 myModule 来引用该模块 });
CMD
CMD
:Common Module Definition,表示通用模块定义,这个相对AMD
有个好处就是,能按需加载模块,也就是说在哪里需要才去加载模块,而不是一次性把模块全部加载好,这点会一定程度上减少程序启动时间,但在运行过程中则需要实时去加载模块。定义:
// module.js define(function (require, exports, module) { var dependModule = require( 'dependModuleName.js' ); // balabala module content });
使用模块,需要用到
seajs
seajs.use(['module.js'], function (myModule) { // 模块加载完成 });
ES6
ES6
标准下的模块化编程,主要分两种方式命名式导出
这种方式,支持一个文件同时可以导出多个函数或变量
比如:
// module.js export function fn1() {} export var1 = 10;
然后通过
import
使用:// test.js import { fn1, var1 } from 'module' // 或者别名 import { fn1, var1 } as myModule from 'module' // 或者 import { fn1 as show, var1 as name } from 'module'
定义式导出
定义式导出,一个文件只能出现一个定义式的导出变量或函数
比如:
// module.js // 这个时候就可以省略调函数名 export function () {}
使用的时候,可以通过文件名,或者
default
关键字来使用module.js
模块中定义式声明的函数,因为一个文件只需要一个,因此不用担心会引用不对的情况发生// test.js import myModule from 'module'; // 或者使用 default import default from 'module'; // 也可别名 import { default as myModule } from 'module';
混合使用
也就是说两种定义方式可以混合使用。
// module.js export function () { // 定义式导出方式的内容 在这里完成 } // 下面是命名式方式 export function fn1() {} export function fn2() {} export var name = 'lizc';
使用:
// test.js // 这里的 module 可以用 default 代替,得到的结果就是 // module.js 中采取定义式方式定义的那个函数 import { module, fn1, fn2, name } from 'module'
服务器端编程
该书中的单页应用中的服务器端是基于 Node.js
来实现的,其中使用到的内容主要包含三大块(Express
,Socket.IO
,MongoDB
)下面依次来简单的回顾下
http
服务器先来看下使用
http
模块创建简单的服务器// server.js require( 'http' ).createServer(function ( req, res ) { // do something }).listen( 3000 );
Express
使用
express
创建服务器程序// server.js var express = require( 'express' ) app = express(); app.listen( 3000, function () { console.log( 'express server listening on port 3000.' ); } );
http
和express
混合使用的情况// server.js var http = require( 'http' ), express = require( 'express' ), app = express(), server; server = http.createServer( app ); server.listen( 3000, function () { console.log( 'http server with express listening on port 3000.' ); } );
express
环境变量(’development’, ‘testing’, ‘staging’, ‘production’)获取:
app.get( 'env' );
设置:
app.set( 'env', 'testing' );
中间件使用:比如日志中间件(logger)
在 3.0+ 版本中 logger 可直接通过
express.logger
使用,4.0+ 版本中大部分中间件都被独立出来了,都需要单独安装使用安装 logger:
npm install morgan --save
, 4.0+ 中间件 logger 的包名是:morgan
使用: `app.use( logger( ‘dev’ ) );
可以根据开发环境不同设置不同的中间件
比如:
var env = app.get( 'env' ); if ( env === 'development' ) { app.use( ... ); } else if ( env === 'testing' ) { app.use( ... ); } ......
自定义中间件
app.use( function ( req, res, next ) { // 中间件处理程序 next(); } );
路由设置
get 请求路由处理:
app.get( '/', function ( req, res ) { ...... } );
post 请求路由处理:
app.post( '/', function ( req, res ) { ...... } );
通用路由处理(必须要有
next
且执行next();
将路由控制流往下传递)app.all( '/', function ( req, res, next ) { ...... } );
Socket.IO
socket.io
主要用来客户端和服务器进行通信用,通过消息加事件触发机制来完成。要使用
socket.io
,首先要建立个服务器,如上面http
或express
方式,然后获取socket
实例监听该服务器,得到通信管道。// server.js var http = require( 'http' ), express = require( 'express' ), app = express(), socket = require( 'socket.io' ), server, io; // 新建服务 server = http.createServer( app ); // socket 监听服务 io = socket.listen( server ); // 服务监听端口 3000 server.listen( 3000, function () { console.log( 'http server with express listening on port 3000.' ); } );
服务程序创建完成,并且使用
socket
进行了监听,那么接下来就可以通过socket
管道向客户端发送消息:io.sockets.emit( 'update', data );
客户端通过连接,然后监听消息
前提:客户端需要通过
<script>
引入socket.io
库文件<script src="/socket.io/socket.io.js"></script>
接下来就可以 :
var connect = io.connect( 'http://192.168.179.103:3000' ); // 开始监听消息 connect.on( 'update', function ( data ) { // 这里就是客户端对服务器发送来的 update 消息的处理程序 // 参数 data 就是服务器携带的数据 } );
也可以链式写法:
io.connect( 'http://192.168.179.103:3000' ).on( 'update', function ( data ) { // 这里就是客户端对服务器发送来的 update 消息的处理程序 // 参数 data 就是服务器携带的数据 } );
上面就是个简单的
socket.io
客户端和服务器连接,以及消息的发送和监听处理。MongoDB
数据库,基于文件存储,文件内容,采用的是
JSON
格式,也就是说该数据库的数据存储包括各种操作都是在JSON
之上进行的。对于更多的介绍和优缺点,百度/google/官网介绍的很详细,没事可以去观摩观摩。这里就直接回顾下它的简单使用
首先:安装
sudo apt-get install -y mongodb-org
安装之后,需要启动下服务,不然直接使用
mongo
进入数据库时候会报错。sudo service start mongod
启动之后就可以进入
mongo
数据库的命令行进行数据库操作了,这里就不回顾了,重点是Node.js
环境下mongodb
的使用。在新版 2.0+ 中,连接方式已经发生了改变,使用如下:
// 引入数据库模块,并获取数据库实例 var MongoClient = require( 'mongodb' ).MongoClient, collection; // 连接到默认的 test 数据库 MongoClient.connect( 'mongodb://localhost:27017/test', function ( error, db ) { // 这个函数有两个参数,第一个表示连接过程中发生的错误对象,如果 // 该对象存在,表示连接不成功,可以通过该对象获取到具体错误信息 // 只有在 error 为空的情况下,表示连接成功,那么第二个参数 db 就是连接后的数据库实例 // 通过这个实例我们就可以连接到 test 数据库中具体集合对象,比如:user collection = db.collection( 'user' ); // 随后就可以对集合进行一系列的 CRUD 操作 } );
CRUD
相关函数(需要注意的是这些函数都是基于数据中集合上进行操作的)插入:
collection.insertOne({}, function ( error, result ) {} );
collection.insertMany([{},{}], function ( error, result ) {} );
error
: 插入失败的错误信息;
result
:插入之后的结果,一般包含插入的条目,简要内容等信息;删除:
collection.deleteOne( {}, function ( error, result ) {} );
collection.deleteMany( [{},{}], function ( error, result ) {} );
更新:
collection.updateOne( find_map, set_map, function ( error, result ) {} );
collection.updateMany( find_map, { $set: set_map }, function ( error, result ) {} );
find_map
:查找的对象,需要被更新的;
set_map
: 更新的对象,用来替换旧数据的;查找:查找功能很强大,并且包含很多功能,具体可参考
find
链式函数collection.find( {}, function ( error, result ) {} );
通过路由和数据库结合,返回查询结果
// 路由处理 app.get( '/user/list', function ( req, res ) { // 查询所有用户 collection.find( {} ).toArray( function ( error, result ) { // 错误退出 if ( error ) { return false; } // 成功返回查询到的的结果 // 回调放在 toArray 中,表示将结果数组化 res.send( result ); } ); } );
发布准备
最后来看下该书对于发布的准备工作都讲了些啥
前面部分讲述了爬虫,错误处理机制,CDN,重点看下缓存的内容。
缓存
缓存方式有下面四种:
Web 存储:把字符串保存至客户端,应用可以访问这些数据,并用它来保存处理好的 HTML,这些HTML是根据服务器上的数据生成的;
包含两种方式:本地存储(localStorage)和会话存储(sessionStorage)
两种方式除了存储时期不同之外,使用方式几乎相同
window.storage = localStorage || sessionStorage;
对象的使用方式也有两种,通过
*item
系的函数,或者直接使用obj[ key ] = value
形式,推荐使用函数方式;添加:
storage.setItem(key, value);
或storage[ key ] = value;
读取:
storage.getItem( key );
或storage[ key ];
修改:
storage.setItem(key, newValue);
删除:
storage.removeItem( key );
HTTP 缓存:客户端缓存,在客户端保存着服务器的响应;
这种方式是通过浏览器进行缓存,当服务器给浏览器发送数据时,这个时候浏览器就会把数据缓存起来,下次如果发生同样的数据请求时直接从浏览器缓存中读取。
对于 HTTP 缓存有两种模式存储:是否检查数据新鲜度(或者说是否过期)
如果不检查,相当于直接取浏览器缓存;
如果检查,则需要从服务器重新获取数据;因此 HTTP 缓存方式比较适合那种在一定时间内不会有啥改动的数据,比如图片,脚本文件,样式文件等等。
max-age
服务器在响应数据的时候,可以通过设置
max-age
来告诉客户端该数据需要被缓存多久,设置方式:
单位(秒),如果设为 0 ,表示每次请求都需要检查数据是否是最新的。res.header( 'Cache-Control', 'max-age=28800' );
阻止中间服务器使用它的缓存,可以通过下面三个属性来达到目的:
no-cache
: 不进行缓存,使用起来会引起不必要的慢,建议使用no-store
;no-store
:通知客户端和中间服务器,不缓存本次请求结果;last-modified
:在不设置Cache-Control
情况下,客户端会根据这个属性来决定缓存期限,通常是这个值的 1/3;
服务器缓存:Memcached 和 Redis 服务器缓存,将客户端请求的响应数据保存至服务器,以至于其他请求同样的数据的时候可以使用,而不用重新搜索数据库;
- 数据库缓存:数据库缓存查询结果,供下次使用。
把书中的对比图绘制了一份,看下图更直观
结篇语
《单页应用开发》这本书完整的阅读下来,包括一步步跟着把书中代码敲下来,至今刚好一个月了,这本书中从前到后的介绍了单页应用的开发过程,从中学习到了很多内容,不管是编程思想上,还是技术知识上都是不小的积累。
还是随便侃几句吧,文笔太菜了,只怪书读的太少,囧!
学习的步伐越来越紧促,越是感觉知识的缺乏,技术的薄弱,不论以往经历了什么,如今能继续从事自己热爱的技术工作,是件值得开心的事情,前路漫漫,奋斗在路上 !!!
最后感谢作者,也感谢翻译此书的牛牛们!