1. 模块化编程
模块化是一种设计思想,利用模块化可以把一个非常复杂的系统结构细化到具体的功能点,每个功能点看作一个模块,然后通过某种规则把这些小的模块组合到一起,构成模块化系统。
1.1 模块化开发的优点
1)开发效率高:方便代码重用,对于别人开发好的模块功能可以直接拿过来使用,不需要重复开发类似的功能;
2)维护成本低:软件开发周期中,由于需求经常发生变化,最长的阶段并不是开发阶段,而是维护阶段,使用模块化开发的方式更容易维护。
1.2 非模块化开发遇到的问题
1)命名冲突
2)文件依赖
将模块化思想带入编程中,可以解决命名冲突和文件依赖的问题。
1.3 模块化编程的演变
1)全局函数
全局函数这种编程方式很常见,但是不可取,因为所有的变量和函数都暴露在全局,无法保证全局变量不与其他模块的变量发生冲突,另外,全部函数形成的模块成员之间看不出直接关系。
2)对象命名空间
只能从理论意义上减少命名冲突的问题,但是命名冲突仍然存在,而且这种方式使内部成员的状态可以随意被外部改写,不安全。
3)函数的作用域(闭包)
JavaScript中通过封装函数的私有空间可以让一些属性和方法私有化,也就是所谓的闭包。可以利用JavaScript函数作用有的特点,通过匿名自执行函数,进行私有变量隔离。大部分第三方库使用这种形式,例如jQuery。
4)维护和扩展
当要对某个模块进行扩展和维护的时候,如果这个模块又存有第三方模块的依赖,可以通过参数的形式将原来的模块和第三方库传递进去。
2. Node.js概述
所谓全栈即包括用户界面、业务逻辑、数据建模、服务器、网络及环境等。
有了Node.js的出现,用JavaScript语言既可以进行客户端的开发,又可以进行服务器端的开发,还可以与数据库交互。
2.1 客户端和服务器端
客户端与服务器端在Web开发中的位置:
在传统Web开发中,客户端将用户请求发送给服务器端,服务器端根据用户的请求进行逻辑处理、数据处理并将结果响应给客户端。
现在用Node.js来代替传统的服务器端语言,开发服务器端的Web框架:
2.2 Node.js的概述
Node.js是一个在服务器端可以解析和执行JavaScript代码的运行环境,也可以说是一个运行时平台,仍然使用JavaScript作为开发语言,提供了一些功能性的API。
Node.js采用单线程,利用事件驱动的异步编程模式,实现了非阻塞I/O。
2.3 回调函数
回调函数是指函数可以被传递到另一个函数中,然后被调用的形式。
1)同步代码中使用try...catch处理异常;
2)异步代码无法使用try...catch处理异常;
3)使用回调函数接收异步代码的执行结果。
在回调函数的设计中有3个约定:
1)函数名通常为callback,在封装异步执行代码时,优先把callback作为函数的最后一个参数出现;
2)把代码中出现的错误作为callback回调函数的第一个参数进行传递;
3)把真正的返回的结果数据,传递给callback的第二个参数。
2.4 异步编程的“事件驱动”
在异步编程中,当异步函数执行时,不确定何时执行完毕,回调函数会被压入到一个事件循环(Event Loop)的队列,然后往下执行其他代码,直到异步函数执行完成后,才开始处理事件循环,调用相应的回调函数。这个事件循环队列是一个先进先出的队列,回调是按照它们被加入队列的顺序执行的。
3. Node.js的文件操作
3.1 基本文件操作
Node.js的文件操作API由fs(File System)模块提供,该模块提供的函数具有异步和同步两个版本,下面只看异步对应的API。
3.1.1 文件写入
fs.writeFile(file, data[,options], callback);
参数说明:
示例:
const fs = require('fs')
fs.writeFile('D:/nodeProject/text/text.txt', '爱学习的程序媛!', err => {
if(err) return console.log('文件写入失败了');
console.log('文件写入完成');
});
3.1.2 向文件中追加内容
appendFile(file, data[,options], callback);
示例:
const fs = require('fs');
fs.appendFile('D:/nodeProject/text/text.txt', '好好学习,天天向上!', err => {
if(err) return console.log('文件追加失败了!');
console.log('文件追加成功了!');
});
3.1.3 文件读取
fs.readFile(file[, options], callback);
示例:
const fs = require('fs');
fs.readFile('D:/nodeProject/text/text.txt', (err, data) => {
if(err) return console.log('文件读取失败');
console.log(data.toString());
});
3.1.4 文件复制
在操作文件的过程中,有时需要将一个文件中的内容读取出来,写入到另一个文件中,这个过程就是文件复制的过程。
示例:
const fs = require('fs');
fs.readFile('D:/nodeProject/text/text.txt', (err, data) => {
if(err) return console.log('读取文件失败');
fs.writeFile('D:/nodeProject/text/copyText.txt', data.toString(), err => {
if(err) return console.log('写入文件失败');
});
console.log('文件复制成功了');
});
3.1.5 获取文件信息
fs.stat(path[, options], callback);
callback回调函数带有两个参数(err, stats),stats是fs.stats对象(Stats类函数的实例),可以通过Stats类中提供的函数判断文件的相关属性。
Stats类函数说明:
示例:
const fs = require('fs');
fs.stat('D:/nodeProject/text/text.txt', (err, stats) => {
if(err) return console.log('获取文件信息失败');
console.log('是否是文件:' + stats.isFile());
console.log(stats);
});
3.2 文件相关操作
3.2.1 路径字符串操作(Path模块)
示例:
const path = require('path');
let str = 'D:/nodeProject/text/text.txt';
console.log(path.basename(str));//text.txt
console.log(path.dirname(str));//D:/nodeProject/text
console.log(path.extname(str));//.txt
console.log(path.isAbsolute(str));//true
console.log(path.join('D:/', 'nodeProject/text/', 'text.txt'));//D:\nodeProject\text\text.txt
console.log(path.normalize(str));//D:\nodeProject\text\text.txt
console.log(path.sep);//\
3.2.2 目录操作
1)创建目录
fs.mkdir(path[, mode], callback);
示例:
const fs = require('fs');
fs.mkdir('D:/nodeProject/createDir/', err => {
if(err) return console.log('目录创建失败了');
console.log('目录创建成功');
});
2)读取目录
fs.readdir(path[, options], callback);
示例:
const fs = require('fs');
fs.readdir('D:/nodeProject/text/', (err, files) => {
if(err) return console.log('读取失败');
files.forEach(file => {
console.log(file);//copyText.txt text.txt
})
});
3)删除目录和删除文件
//删除目录
fs.rmdir(path[, options], callback);
//删除文件
fs.unlink(path, callback);
删除目录时有一个要求,就是该目录必须为空,所以删除目录的操作还需要读取目录和删除文件操作进行支持。
示例:
const fs = require('fs');
let dirPath = 'D:/nodeProject/text/';
fs.readdir(dirPath, (err, files) => {
if(err) return console.log('读取失败');
files.forEach(file => {
fs.unlink(dirPath + file, err => {
if(err) return console.log('文件删除失败');
console.log(file + '文件删除成功');
})
});
setTimeout(() => {
fs.rmdir(dirPath, err => {
if(err) return console.log('目录删除失败');
console.log('目录删除成功');
});
});
});
4. Node.js中处理数据I/O
4.1 Buffer缓冲区
Buffer类用来在内存中创建一个专门存放二进制数据的缓冲区,也就是说,在内存中预留一定的存储空间,用来暂时保存输入或输出的数据,这样Node.js就能够处理二进制数据。
4.1.1 写入缓冲区
buf.write(string[, offset[, length]][, encoding]);
参数说明:
示例:
const buf = Buffer.from('hello world', 'utf8');
console.log(buf);//<Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
console.log(buf.toString());//hello world
console.log(buf.length);//11
4.1.2 从缓冲区读取数据
buf.toString([encoding[, start[, end]]]);
示例:
const buf = Buffer.alloc(26);
for(let i = 0; i < 26; i++){
buf[i] = i + 97;
}
console.log(buf.toString());//abcdefghijklmnopqrstuvwxyz
console.log(buf.toString('ascii', 10, 15));//klmno
console.log(buf.toString('utf8', 11, 20));//lmnopqrst
4.2 Stream文件流
在Node.js中,文件流的操作由Stream模块提供,Stream是一个抽象接口,Node.js中很多对象实现了这个接口。
Node.js中,Stream有4种流类型:
1)Readable:可读取数据的流(例如:fs.createReadStream());
2)Writable:可写入数据的流(例如:fs.createWriteStream());
3)Duplex:可读又可写的流(例如:net.Socket);
4)Transform:在读写过程中可以修改或转换数据的Duplex流(例如:zlib.createDeflate())。
4.2.1 可读流
fs.createReadStream(path[, options]);
示例:
const fs = require('fs');
let total = '';
let readableStream = fs.createReadStream('D:/nodeProject/text/text.txt');
readableStream.on('data', chunk => {
total += chunk;
});
readableStream.on('end', () => {
console.log(total);//好好学习,天天向上!
});
readableStream.on('error', err => {
console.log(err.stack);
});
4.2.2 可写流
fs.createWriteStream(path[, options]);
示例:
const fs = require('fs');
let readableStream = fs.createReadStream('D:/nodeProject/text/text.txt');
let writableStream = fs.createWriteStream('D:/nodeProject/text/copyText.txt');
readableStream.on('data', chunk => {
writableStream.write(chunk);
});
readableStream.on('error', err => {
console.log(err.stack);
});
readableStream.on('end', () => {
writableStream.end();
});
writableStream.on('error', err => {
console.log(err.stack);
});
4.2.3 使用pipe()处理大文件
示例:使用pipe()进行文件复制
const fs = require('fs');
let srcPath = 'D:/nodeProject/text/text.txt';
let distPath = 'D:/nodeProject/text/copyText.txt';
let readableStream = fs.createReadStream(srcPath);
let writableStream = fs.createWriteStream(distPath);
if(readableStream.pipe(writableStream)){
console.log('文件复制成功了');
}else {
console.log('文件复制失败了');
}
5. Node.js网络编程
网络编程就是在两个或两个以上的设备之间进行传输数据,也叫作网络通信。
5.1 IP地址和端口号
IP地址是用来定位一台计算机的,这台计算机可以是服务器,也可以是客户端,需要注意的是IP地址对于计算机是唯一的,一个端口号也只能被一个应用程序所占用。
5.2 套接字Socket简单模型
简单理解,Socket就是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口。
Socket中包含了进行网络通信必需的5种信息:连接使用的协议、客户端设备IP地址、客户端的端口号、服务器端的IP地址、服务器端的端口号。
Socket需要使用套接字地址来开展工作,套接字地址就是IP地址和端口号的组合,套接字服务与其他网络服务不同,不需要处理网络中的GET或POST请求,而是采用点对点传输数据方式,是一个轻量级的网络通信解决方案。
5.3 Node.js中实现套接字服务
Node.js中的套接字服务由net模块提供,其中包含了创建服务器/客户端的方法。
Net模块API:
5.3.1 Net.Server对象
在Node.js中,使用net模块可以创建一个TCP或本地服务器:
let server = net.createServer([options][, connectionListener]);
Net.Server对象的函数:
Server对象的事件:
示例:
const net = require('net');
let server = net.createServer();
server.on('connection', () => {
console.log('有客户端连接上了');
});
server.on('listening', () => {
console.log('服务器开启监听成功了,正在等待客户端连接');
});
server.listen(3000, '127.0.0.1');
5.3.2 Net.Socket对象
在Node.js中提供了一个Net.Socket对象,用于方便调用底层Socket接口,实现数据传输的功能。
Net.Socket实现了Duplex(双工、双向)流接口,提供了Writable和Readable所有功能,所以可以说它既是可读流又是可写流。
Net.Socket对象可被触发的事件:
Net.Socket对象属性:
Net.Socket对象函数:
1)服务器向客户端发送消息
const net = require('net');
let server = net.createServer();
server.on('connection', socket => {
console.log('有客户端连接上了');
console.log('客户端IP地址:' + socket.remoteAddress + '连接到了当前服务器');
socket.write('hello world');
});
server.on('listening', () => {
console.log('服务器开启监听成功了,正在等待客户端连接');
});
server.listen(3000, '127.0.0.1');
2)统计在线人数
const net = require('net');
let server = net.createServer();
let count = 0;
server.on('connection', socket => {
count++;
console.log('welcome,当前在线人数:' + count);
socket.write('remoteAddress' + socket.remoteAddress + '\n');
socket.write('remotePort' + socket.remotePort);
});
server.listen(3000, '127.0.0.1', () => {
console.log('server listening at port 3000');
});
3)客户端与服务器双向通信
//server.js 双向通信-服务器
const net = require('net');
let server = net.createServer();
server.on('connection', socket => {
socket.on('data', data => {
console.log(data.toString());
socket.write('服务器来信!');
})
});
server.listen(3000, '127.0.0.1', () => {
console.log('server listening at port 3000');
});
//client.js 双向通信-客户端
const net = require('net');
let client = net.createConnection({
port: 3000
});
client.on('connect', () => {
console.log('客户端与服务器端连接成功了');
client.write('客户端来信!');
});
client.on('data', data => {
console.log(data.toString());
});
5.4 Node.js进程管理
进程可以看作是一个正在运行的应用程序,在Node.js中提供了Process模块用来处理与进程相关的内容。
5.4.1 Process模块获取终端输入
process.stdin.on('data', data => {
console.log(data.toString().trim());
});
5.4.2 多人广播消息
所谓多人广播消息就是在一个客户端输入消息时,除了自己以外的其他客户端都能看到,这时就需要在服务器端获取该客户端输入的数据,然后将其发送到其他客户端。
示例:
//server.js 多人广播聊天服务器端
const net = require('net');
let server = net.createServer();
let users = [];
server.on('connection', socket => {
users.push(socket);
socket.on('data', data => {
data = data.toString().trim();
users.forEach(client => {
if(client !== socket){
client.write(socket.remotePort + ':' + data);
}
})
});
socket.on('error', () => {
console.log('有客户端异常退出了!');
})
});
server.listen(3000, '127.0.0.1', () => {
console.log('server listening at port 3000');
});
//client.js 多人广播聊天客户端
const net = require('net');
let client = net.createConnection({
port: 3000,
host: '127.0.0.1'
});
client.on('connect', () => {
process.stdin.on('data', data => {
client.write(data.toString().trim());
});
});
client.on('data', data => {
console.log(data.toString());
});
6. Node.js中实现HTTP服务
6.1 HTTP协议
HTTP(Hyper Text Transfer Protocol)全称为超文本传输协议,用于从WWW服务器传输超文本到本地浏览器的传送协议,基于TCP的连接方式,它可以使浏览器更加高效,使网络传输减少。
简单地说,HTTP协议就是用于规范客户端浏览器和服务器端以什么样的格式进行通信数据交互,作为应用层的面向对象的协议,HTTP由请求和响应构成,是一个标准的客户端服务器模型,也是一个无状态的协议。
HTTP请求响应报文流程:
6.2 Node.js的HTTP服务
Node.js提供了HTTP模块,HTTP模块主要用于搭建HTTP服务器端和客户端。
HTTP服务器本质上也是一个Socket服务器,可以理解为在Socket服务器的基础上进行了一些封装,简化了一些操作。
6.2.1 HTTP模块常用API
1)http.Server对象
在Node.js中,HTTP服务器是指http.Server对象,用Node.js做的所有基于HTTP协议的系统,如网站、社交应用甚至代理服务器,都是基于http.Server实现的。
2)http.IncomingMessage对象
在HTTP服务器和客户端都会创建http.IncomingMessage对象,它一般由http.Server的request事件发送,作为第一个参数传递,通常简称为request或req。
3)http.ServerResponse对象
http.ServerResponse对象是返回给客户端的信息,决定了用户最终能看到的结果,它也是由http.Server的request事件发送的,作为第二个参数传递,一般简称为response或res。
6.2.2 使用HTTP模块构建Web服务器
示例:
const http = require('http');
let server = http.createServer();
server.on('request', (req, res) => {
res.write('hello world');
res.write('hello node');
res.end();
});
server.listen(3000, '127.0.0.1', () => {
console.log('server listening at port 3000');
});
6.3 HTTP服务请求处理
6.3.1 根据不同的URL发送不同响应消息
示例:
const http = require('http');
let server = http.createServer();
server.on('request', (req, res) => {
switch (req.url){
case '/':
res.end('hello home');
break;
case '/login':
res.end('hello login');
break;
case '/register':
res.end('hello register');
break;
default:
res.end('404 Not Found');
break;
}
});
server.listen(3000, '127.0.0.1', () => {
console.log('server listening at port 3000');
});
6.3.2 HTTP处理静态资源服务
示例:
const fs = require('fs');
const path = require('path');
const http = require('http');
let server = http.createServer();
server.on('request', (req, res) => {
switch (req.url){
case '/':
setResData('index.html', res);
break;
case '/login':
setResData('login.html', res);
break;
case '/register':
setResData('register.html', res);
break;
case '/css/main.css':
setResData('css/main.css', res);
break;
case '/images/1.png':
setResData('images/1.png', res);
break;
default:
setResData('404.html', res);
break;
}
});
server.listen(3000, '127.0.0.1', () => {
console.log('server listening at port 3000');
});
function setResData(textName, res){
fs.readFile(path.join(__dirname, 'static/' + textName), (err, data) => {
if(err) throw err;
res.end(data);
});
}
6.3.3 动态处理静态资源请求
示例:
const fs = require('fs');
const path = require('path');
const http = require('http');
let server = http.createServer();
server.on('request', (req, res) => {
let url = req.url;
let fullPath = path.join(__dirname, 'static', url);
if(url === '/'){
fullPath = path.join(__dirname, '/static/index.html');
}
fs.readFile(fullPath, (err, data) => {
if(err) return res.end(err.message);
res.end(data);
})
});
server.listen(3000, '127.0.0.1', () => {
console.log('server listening at port 3000');
});