JavaScript-Node.js

Node.js

1.关于 Node.js

Node.js是一个让 JavaScript 运行在服务器端的开发平台.

1.1. 介绍

1). Node.js 不是一种独立的语言,与PHP、JSP、Python、Perl、Ruby的”既是语言,也是平台”不同,Node.js的使用JavaScript进行编程,运行在JavaScript引擎上(V8)。

2). 与PHP、JSP等相比,Node.js跳过了Apache、Naginx、IIS等HTTP服务器,它自己不用建设在任何服务器软件之上。Node.js的许多设计理念与经典架构(LAMP)有着很大的不同,可以提供强大的伸缩能力。

官网: https://nodejs.org/en/
中文网: http://nodejs.cn/

1.2. 特点

1
单线程 | 非阻塞I/O | 事件驱动

1). 单线程

1
2
3
4
所有客户端请求的连接 都使用一个线程来处理.

Node.js 不是给每个连接去创建新的 线程,而是仅仅使用一个线程来处理.
单线程带来的好处,减少内存的损耗,提高并发量. 操作系统完全不再有线程创建和销毁的开销.

2). 非阻塞I/O

1
2
3
4
5
6
I/O操作不会阻塞程序的运行
I: Input 输入
O: Output 输出

在阻塞模式下,一个线程只能处理一项任务,想要提高吞吐率,必须通过多线程
而非阻塞模式下,一个线程 永远在执行某种运算操作.这个线程的CPU 核心利用率永远是满载的

3). 事件驱动

1
2
客户端 请求建立连接,提交数据等行为,就会触发 相应的事件.
在Node中,在一个时刻,只能执行 一个事件回调函数,但是在执行 一个事件回调函数的中途,(比如,又有新用户连接了)可以转而处理其他事件,然后返回继续执行原事件的回调函数,这种处理机制,称为“事件环”机制.

Node.js底层是C++(V8引擎也是C++写的).底层代码中,近半数都用于事件队列、回调函数队列的构建.用事件驱动 来完成服务器的 任务调度,是Node.js中 真正底层核心逻辑.

4). 三特点说明

单线程
是为了减少内存的开销,操作系统的内存换页(创建/销毁)
但是,如果某一个请求 有I/O操作,单线程就会被阻塞了!
非阻塞I/O
程序不会傻等I/O语句的执行结束,才继续后续代码的运行,而会直接运行后续代码.
但是,非阻塞就能完美的解决问题吗, 比如 小A的业务执行I/O过程中,有小C要新的请求,此时则么办?
事件驱动(事件环)
不管是新用户的请求,还是老用户的I/O操作,都将以事件的方式加入到事件环之中,等待调度.

Node.js 中所有的I/O都是异步的.回调函数 套 回调函数
泡茶 : 洗茶壶 4 / 洗茶杯 3 / 用茶壶烧开水 8 / 准备茶叶 2 / 泡茶水 5 / 倒茶水 1
4 + 8 + 5 + 1 = 18
3 + 2

1.3 Node.js 的优缺点,适合开发什么?

1). 优点

​ 善于I/O,不善于 大量计算
​ 处理高并发
​ 服务器推送

2). 缺点

​ 单线程的缺点: 单一线程 一旦奔溃则整个服务全奔溃
​ 服务不是绝对可靠的.

3). 适用场景

1
2
3
4
5
6
7
8
9
不能完全替代 传统的后端语言,但在某些方面优于传统.
当应用程序需要处理大量并发的I/O操作,而在发出响应之前,应用程序内部 并不需要进行非常复杂的计算处理的时候,Node.js非常适合。
Node.js也非常适合与web socket配合,开发长连接的实时交互应用程序。

- 聊天室
- 图文直播
- 考试系统
- 收集用户数据的表单
- 提供JSON的API

2.安装使用node

2.1 windows环境安装

node -v 查看版本

node 文件名 运行文件

2.2 搭建Node.js服务器

​ – 首个nodejs页面页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 搭建nodejs WEB服务器

// 引入HTTP模版
var http = require('http');

var hostname = '127.0.0.1';
var port = 3000;

// 创建服务器
// req 表示请求(request) / res 表示响应(response)
var server = http.createServer(function(req, res){
// 设置HTTP头信息. 状态码 200; 文件类型是html; 字符集 utf-8
res.writeHead(200, {'Content-type':'text/html;charset=UTF-8'});
// 服务器响应输出完成
res.end('Hi~ o(* ̄▽ ̄*)ブ, 首个nodejs页面,您的积分为: ' + (50 + 10) + '分');
});

// 运行服务器
server.listen(port, hostname, function(){
console.log(`请访问: http://${hostname}:${port}`);
});

如果修改了程序代码,必须中断当前的node服务,重新在node一次

node.js是服务器的程序,写好的JS代码,都将运行在服务器上.返回给客户端的, 都是已经处理好的结果

node就是一个JS的运行环境

js文件是不能直接拖入浏览器取运行的,必须靠node去执行

2.3 根目录 与 web容器

1
1). Node.js没有根目录的概念,因为它根本没有任何的web容器!

1
2
3
4
2). 静态页面的呈现

URL和真实的物理文件之间 是没有关系的。
URL是通过 Node 的路由设计之后,才呈现出 某一个静态文件的
1
3). HTTP运行原理

由此,我们可以看出,要是使用nodejs 搭建服务器,
是需要写 各种回调函数,定义 各种路由规则,来实现 页面的显示.
nodejs中就是 回调函数 套回调函数,每个回调函数 都是一个事件

由此就用到了 nodejs的三个特性:
单线程 | 非阻塞I/O | 事件驱动(事件环)
URL路由规则,与实际的物理文件,不一定有直接的联系.

3.HTTP模块

Node.js将各种功能,都划分为了一个个mudule(模块)
需要用什么模块,就可以使用require(‘’)来引入使用

4.URL模块

​ 解析URL

1
2
3
4
5
6
// GET 参数的处理
var getParams = url.parse(req.url, true).query;
var name = getParams.name;
var age = getParams.age;
var sex = getParams.sex;
res.end('服务器接收到了以下信息: '+ name + ' | ' + age + ' | ' + sex);

​ 路由设计

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

var http = require('http');
var url = require('url');

var hostname = '127.0.0.1';
var port = 3000;

// 创建服务器
var server = http.createServer(function(req, res){
// 跳过了 chrome 的收藏夹图标的请求
if (req.url == '/favicon.ico') return;
res.writeHead(200, {'content-type': 'text/html;charset=utf-8'});

// /stu/20190212007 查询学员
// /tch/00128 查询老师

// 获取URL中的path部分
var user = url.parse(req.url).pathname;

// substr
if (user.substr(0, 5) == '/stu/') {
var stuid = user.substr(5);
// console.log(stuid);
if (/^\d{11}$/.test(stuid)) {
res.end('查询到的学号是: ' + stuid);
} else {
res.end('学员的学号有误!!!');
}
} else if (user.substr(0, 5) == '/tch/') {
var tchid = user.substr(5);
// console.log(tchid);
if (/^\d{5}$/.test(tchid)) {
res.end('查询到的工号是: ' + tchid);
} else {
res.end('工号有误!!!');
}
} else {
res.end('URL有误,请检查重试!!!');
}
});

// 运行服务器
server.listen(port, hostname);

5.文件系统

​ 5.1. 新建文件夹 / 删除文件夹 / 文件状态信息 / 读取文件夹

​ 5.2. 读取出 文件夹/文件

6.静态目录 文件加载 (web容器)

7.模块的概念

在Node.js中,不可能用一个js文件去写全部的业务,肯定要有MVC.
它以模块为单位 划分所有功能,并且提供了一个 完整的模块加载机制,我们可以将应用程序 划分为各个不同的部分.

每一个JavaScript文件都是一个模块;
而多个JavaScript文件可以使用require引入,使他们共同实现了一个功能模块.

7.1 输出变量/函数

​ Node.js中,JS文件中定义的变量、函数,都只在这个文件内部有效.
​ 其他文件中需要引用变量、函数时,必须使用exports对象进行暴露(输出).
​ 使用者要用require()命令,引用执行这个JS文件.

7.2 输出一个类(构造函数)

​ 可以用module.exports = 构造函数名;的方式 向外输出一个类

7.3 模块关联关系

  • 某一个js文件中,有函数或变量: exports.变量 = 变量;
  • 某一个js文件中,有一个类: module.exports = 构造函数名;

7.4 模块封装

8.npm

(node package management)

这是一个工具名字.npm的主要职责是 安装开发包和管理依赖项.
安装开发包:安装 npm install命令;更新 npm update命令.
管理依赖项:借助 package.json 文件;最简单生成 package.json 的方法就是 npm init

npm不需要单独安装,只要安装了 Node.js 环境,npm 就已经包含在里面了. 查看 npm 版本: npm -v

为什么要使用npm?
开发时,会使用到各种功能的组件,所有组件都由我们自己来写代码的话,开发效率就会很低.我们不要重复的去造轮子,要学会使用已有的工具,来完善我们的项目,站在巨人的肩膀上去工作.
npm是js世界里的一个伟大的社区,能够让开发者更加轻松的共享代码和共用代码片段或模块组件.

https://www.npmjs.com/ # npm官网
https://npm.taobao.org/ # 淘宝npm镜像

不要修改 [node_modules] + package-lock.json 这两个文件,因为它是使用npm去管理的

9.POST请求

相比较GET请求,POST请求比较复杂。
因为Node.js认为,使用POST请求时,数据量会比较多。
为了追求极致的效率,它将数据拆分成为了众多小的数据块(chunk),然后通过特定的事件,将这些小数据块有序传递给回调函数。

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

var http = require('http');
var fs = require('fs');
var querystring = require('querystring');

var hostname = '127.0.0.1';
var port = 3000;

var server = http.createServer(function(req,res){
if (req.url == '/form') {
// 读取表单加载页面
fs.readFile('./form.html', function(err, data){
res.writeHead(200, {'content-type': 'text/html;charset=UTF-8'});
res.end(data);
});
} else if (req.url == '/dopost' && req.method.toLocaleLowerCase() == 'post') {
// 如果访问/dopost,且请求类型是post
// 进行POST数据处理
var postData = '';
// node为了实现极致的效率,所以把post分成多个小份去传递
req.addListener('data', function (chunk){
postData += chunk;
});

// 全部接收完毕
req.addListener('end', function(){
console.log(postData);

// 将post字串转换为一个对象
var dataObj = querystring.parse(postData);
console.log(dataObj);
console.log(dataObj.name);
res.end('POST DATA Success!');
});


} else {
res.end('404');
}

});

server.listen(port, hostname);

10.文件上传处理

原生写POST处理,比较复杂,要写两个监听.
文件上传业务比较麻烦.所以,用第三方模块: `formidable`
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

var http = require('http');
var fs = require('fs');
var querystring = require('querystring');
var formidable = require('formidable'); // 处理表单数据
var timestamp = require('time-stamp'); // 时间
var path = require('path'); // 时间

var hostname = '127.0.0.1';
var port = 3000;

var server = http.createServer(function(req,res){
if (req.url == '/form') {
// 读取表单加载页面
fs.readFile('./form_file.html', function(err, data){
res.writeHead(200, {'content-type': 'text/html;charset=UTF-8'});
res.end(data);
});
} else if (req.url == '/dopost' && req.method.toLocaleLowerCase() == 'post') {
// 如果访问/dopost,且请求类型是post
// formidable 处理 parse a file upload
var form = new formidable.IncomingForm();

// 设置上传目录
form.uploadDir = "./uploads";
// 该模块已经将 POST数据 和 文件数据 分离处理
// 当代码执行到parse()方法的回调函数时,表单中的数据 就都已经处理好了
form.parse(req, function(err, fields, files) {
// console.log(fields);
// console.log(files);
// 处理上传文件的存储
// 新文件名: 时间 + 随机数 + 后缀
var t = timestamp('YYYYMMDDHHmmss');
var ran = parseInt(Math.random()* 100000);
var extname = path.extname(files.myfile.name);

// 旧文件名
var oldPath = './' + files.myfile.path;
// 新文件名
var newPath = './uploads/' + t + ran + extname;
console.log(oldPath);
console.log(newPath);

// 实现改名
fs.rename(oldPath, newPath, function (err){
res.end('文件上传成功!!');
});
});
} else {
res.end('404');
}

});

server.listen(port, hostname);

11.ejs模版

https://ejs.co/        #官网
https://www.npmjs.com/package/ejs #npm上的ejs包
ejs是Embedded JavaScript templates的简称,意思是嵌入式JavaScript模板.node中的后台模版.

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

var http = require('http');
var ejs = require('ejs');
var fs = require('fs');
var hostname = '127.0.0.1';
var port = 3000;

var server = http.createServer(function (req, res) {
if (req.url == '/') {
fs.readFile('./views/index.ejs', function (err, data) {
// 模版
// console.log(data);
var template = data.toString();
// console.log(template);
// 数据
var dict = {
title: 'EJS 模版的使用',
content: '我是段落内容....',
pic: './imgs/1.jpg',
songci : {
title: '酒调歌头',
list : [
'12345',
'67890',
'09876',
'54321'
]
}
}
// 绑定数据
var html = ejs.render(template, dict);

// 显示输出页面
res.writeHead(200, {'content-type':'text/html'});
res.end(html);
});
} else if (req.url == '/imgs/1.jpg') {
fs.readFile('./imgs/1.jpg', function (err, data) {
res.writeHead(200, {'content-type':'image/jpg'});
res.end(data);
});
}
});

server.listen(port, hostname);
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
<!--index.ejs-->
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style type="text/css"></style>
</head>
<body>
<h1><%= title %>></h1>
<hr>

<p><%= content %>></p>
<p><%= content %>></p>
<p><%= content %>></p>
<p><%= content %>></p>

<hr>
<br>
<img src="<%= pic %>" width="300">

<hr>
<h2><%= songci.title %></h2>
<ul>
<% for (var i = 0; i < songci.list.length; i++) { %>
<li><%= songci.list[i] %></li>
<% } %>
</ul>

</body>
</html>

PS:V8引擎说明

​ V8 JavaScript引擎是 Google用于其Chrome浏览器的底层JavaScript引擎。
​ Google使用V8创建了一个用C++编写的超快解释器,该解释器拥有另一个独特特征:
​ 您可以下载该引擎并将其嵌入任何应用程序。
​ V8 JavaScript引擎并不仅限于在一个浏览器中运行。
​ 因此,Node.js实际上会使用Google编写的V8 JavaScript引擎,并将其重建为可在服务器上使用。