NodeJS
Node 是一个构建于 Chrome V8引擎之上的一个 Javascript 运行环境, 作用是让 js 拥有开发服务端的功能
安装
node 版本管理: 安装 n
1
2
| npm install -g n
# n --help
|
监控 js 变化并重启服务: node-dev
1
2
| npm i -g node-dev
node-dev app.js
|
客户端 js 和服务端 js
客户端 JavaScript 由三部分组成
- ECMAScript:确定 js 的语法规范
- DOM:js 操作网页内容
- BOM:js 操作浏览器窗口
node 中的 JavaScript 组成
基本的语法和写法和之前的 js 没有本质的区别
- 在 nodejs 中使用 dom 与 bom 的 api 程序会报错
- 服务器端没有界面
- 不需要操作浏览器和页面元素
运行 node. js 程序
node [js文件路径]
生产环境: pm2
模块化
CommonJS 规范
模块必须通过 module.exports
导出对外的变量或接口,通过require()
来导入其他模块的输出到当前模块作用域中。
CommonJS模块的特点:
- 所有代码运行在当前模块作用域中,不会污染全局作用域
- 模块同步加载,根据代码中出现的顺序依次加载
- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
==模块使用前要先导入==
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
| /**
* 模块化规范
* CommonJS: 2015年前社区开发, nodejs官方默认
* - 自定义模块
* - 一个js文件就是一个模块
* - 使用 exports 或者 module.exports 暴露: exports.xxx = xxx
* module.exports = {xxx:xxx}
* 不能使用 exports = {xxx:xxx} 这是在给变量赋值, 上面的是修改对象的属性
* - 使用 require("模块的路径") 引入, 用变量来接收: const {xxx} = require()
* - 后缀自动补全: 先找js, 再找json
* - 核心模块
* - require("模块名")
* - require("node:模块名")
* nodejs将以下内容视为CommonJS
* 1. 使用.cjs扩展名
* 2. package.json的type属性为CommonJS,且扩展名为js
* 3. package.json不包含type属性,且扩展名为js
* 4. 扩展名是mjs, cjs, json, node, js以外的值, package.json的type属性不是module
*/
(function(exports, require, module, __filename, __dirname){
// 所有的CommonJS模块都会被包装到一个函数里
// exports, require是作为参数传进来的
console.log(arguments) // 证明
})
|
ES 模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/* ES模块: 原生, 2015年es6标准发布
* - mjs 扩展名
* - package.json的type属性设置为module
*
* - 导出: export let a = xxx
* export const b = xxx
* - 导入: import {} from '路径.mjs'
* 改别名 import {a as b} from './test.mjs'
* 开发时要尽量避免 import * as c from './test.mjs' , 按需引用
* - 设置默认导出: export default function sum(){}
* default 后面跟值, export default let a = 0 这种不行, 后面是语句
* 一个模块只有一个默认导出
* - 默认导入: import sum {a} from './test.mjs'
* import {default as sum, a} from './test.mjs'
* 默认导入可以随便起名
* 默认导入可以和按需导入一起用
* - 通过es模块化导入的, 都是常量
* - es模块都是运行在严格模式下
*/
|
node. js 核心模块
Node 应用是由模块组成的,Node 遵循了 CommonJS
的模块规范,来隔离每个模块的作用域,使每个模块在它自身的命名空间中执行。
fs文件模块(读写文件)
先导入文件模块
1
| const fs = require('fs')
|
readFile异步读取
1
2
3
4
5
6
7
8
| fs.readFile(path[, options], callback(err,data))
/**
* 第一个参数:文件路径
* 第二个参数:编码格式 (可选参数,默认为buffer二进制,buffer:数据缓冲区)
* 第三个参数:读取回调操作(异步操作)
* err:如果读取成功, err为null,否则读取失败(一般文件路径错误或者找不到文件)
* data:读取到的数据(字符串|二进制)
*/
|
示例:
1
2
3
4
5
6
7
8
9
10
| fs.readFile('./data/aaa.txt','utf-8',(err,data)=>{
//按utf-8编码读取, 解决中文乱码
if(err){
console.log(err);
//抛出异常,throw的作用就是让node程序终止运行,方便调试
throw err;
}else{
console.log(data);
};
});
|
同步读取(几乎不用,会阻塞,一般在异步的api后面加上Sync就是同步):
1
| let data = fs.readFileSync('./data/aaa.txt','utf-8')
|
writeFile异步写入
1
2
3
4
5
6
7
8
| fs.writeFile(file, data[, options], callback(err))
/**
* 第一个参数:文件路径
* 第二个参数:要写入的数据
* 第三个参数:文件编码 默认utf-8
* 第四个参数: 异步回调函数
* err: 如果成功,err为null.否则读取失败
*/
|
- 默认写入会覆盖
- 如果文件名不存在,新创建再写入
- 如果文件夹不存在,报错
示例:
1
2
3
4
5
6
7
| fs.writeFile('./data/bbb.txt','测试','utf-8',(err)=>{
if(err){
throw err;
}else{
console.log('写入成功');
};
});
|
异步追加
异步地追加数据到文件,如果文件尚不存在则创建文件
1
| fs.appendFile(path, data[, options], callback(err))
|
Promise版本的fs方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| const fs = require("node:fs/promises")
fs.readFile(path.resolve(__dirname, './hello.js'))
.then(buffer=>{
console.log(buffer.toString()) // 也可以直接用toSting()方法转成字符串
})
.catch(err=>{
console.log(err)
})
// 或者
;(async()=>{
try{
const buffer = await fs.readFile(path.resolve(__dirname, './hello.js')
console.log(buffer.toString())
}catch((err)=>{
console.log(err)
})
})()
|
1
2
3
4
5
6
| // 常见方法
fs.mkdir() // 创建目录
fs.rmkdir() // 删除目录
fs.rm() // 删除文件
fs.rename() // 重命名
fs.copyFile() // 复制
|
path路径模块
==在服务端开发中,一般不要使用相对路径,而使用绝对路径==
1
| const path = require('path')
|
nodejs中的绝对路径和相对路径
node中的相对路径: ./
不是相对于当前文件所在路径,而是相对于执行node命令的文件夹路径(当前被执行的文件所在的文件夹路径).
解决方案:在nodejs中,每一个js文件都有两个全局属性,它可以帮助我们获取到文件的绝对路径
- __filename:当前js文件绝对路径
- ==__dirmame:当前js文件所在目录的绝对路径==
windown中路径 用双反斜杠 \\
而不是 \
示例:
1
2
3
4
5
6
7
8
9
10
11
| const fs = require('fs')
let path = __dirname + '/aaa.txt'
console.log(path)
fs.readFile(path,'utf-8',(err,data)=>{
if(err){
console.log(err);
throw err;
}else{
console.log(data);
};
});
|
join()方法 路径拼接
1
2
3
4
5
| path.join([...paths])
/*使用path模块拼接文件路径与使用'+'连接符拼接的好处
1.会自动帮我们正确添加路径分隔符 '/',我们无需手动添加
2.当我们路径格式拼接错误的时候,能自动帮我们转换正确的格式
*/
|
示例:
1
| let filePath = path.join(__dirname, './page/login.html')
|
resolve()方法 路径处理
1
2
3
4
| // 把一个路径或路径片段的序列解析为一个绝对路径
// 传入路径从右至左解析,遇到第一个绝对路径解析停止
// 如果没有传入参数,将只返回当前根目录
path.resolve([...paths])
|
示例:
1
2
3
4
5
| // "/b" 就是遇到的第一个绝对路径
path.resolve('/a', '/b', 'c') // /b/c
path.resolve('/a', './b', 'c') // /a/b/c
//因为没有遇到第一个绝对路径,所以会一直向上解析(根目录路径/a/b/c)
path.resolve('a', 'b', 'c') // /Users/siyuan/Desktop/example/node测试/a/b/c
|
process
获取进程信息, 或者对进程进行操作
使用: 全局变量, 直接使用
属性和方法
- process.exit(code): 结束当前进程, code默认0
- process.nextTick(()=>{}): 将函数插入tick队列, 调用栈=>tick=>微任务队列=>宏任务队列
服务器基础
基本的访问流程
- 输入主机地址
- 指定端口(如果没有指定, 默认是80)
- 指定需要访问的资源路径
- 发起请求
- 获取服务器返回的结果并处理
http协议
超文本传输协议(HyperText Transfer Protocol), 是基于TCP/IP协议之上的应用层协议
HTTP是一个客户端终端(用户)和服务器端(网站)请求和应答的标准
客户端和服务器的通信必须遵守某种协议,http协议就是最常见的一种
端口
端口是通过端口号来标记的,端口号只有整数,范围是从0 到65535(2^16-1)
常见的端口号:
- 80:web服务器端口
- 3306:mysql数据服务器端口
查询端口状态netstat
以数字格式显示地址和端口信息netstat -n
常见的状态码
- 200请求已成功,请求所希望的响应头或数据体将随此响应返回。出现此状态码是表示正常状态
- 404:请求失败,请求所希望得到的资源未被在服务器上发现
- 500:服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器端的源代码出现错误时出现
返回数据的格式
- text/html格式:html代码,浏览器会以html语法解析
- text/css:样式,浏览器会以css语法解析
- application/javascript:js代码,浏览器会以js语法解析
- application/json:json格式字符串,描述从服务器返回的数据
hellow app.js
流程:
- 导入模块
const * = require(*)
- 创建服务器
const server = http.creatServer()
- 监听端口
server.listen(端口, ()=>{})
- 响应请求, 进行事件处理
server.on('request', (req,res)=>{})
注意点:
req.url
可以获取当前用户请求的url- 中文乱码
1
2
3
| res.setHeader('Content-type','text/html;charset=UTF-8')
// html页面不需要, 头部已有
|
1
2
3
4
5
6
7
8
9
10
11
12
| //1.导入http模块
//2.创建服务器
//3.监听端口
/*
第一个参数:端口号
第二个参数:ip地址 默认不写,就是本机ip(127.0.0.1)
第三个参数:一个回调函数,启动时会调用
*/
//4.处理请求
|
1
2
3
4
5
6
7
8
9
10
11
12
| const http = require('http')
const server = http.createServer()
server.listen(3000,'127.0.0.1',(err)=>{
console.log('服务器开启成功: http://127.0.0.1:3000');
})
server.on('request',(req,res)=>{
// 所有请求都响应 'hello word'
res.end('hello word')
})
|
响应页面
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
| const http = require('http')
const fs = require('fs')
const path = require('path')
const server = http.createServer()
server.listen(3000, () => {
console.log('已开始监听 http://127.0.0.1:3000')
})
server.on('request', (req, res) => {
// 每个请求,都执行这里的代码
const url = req.url
console.log(url)
switch (url) {
case '/':
case '/index':
res.end('hello word')
break
case '/login':
// 页面head已经有编码格式
fs.readFile(path.join(__dirname, './page/login.html'), (err, data) => {
if (err) {
res.end('404 not found')
} else {
res.end(data)
}
})
break
default:
res.end('404 not found')
break
}
})
|
允许跨域
1
| res.setHeader('Access-Control-Allow-Origin', '*')
|
响应不同的请求
req.method
获取请求的类型
get请求
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
| const http = require('http')
const fs = require('fs')
const path = require('path')
const server = http.createServer()
server.listen(3000, () => {
console.log('127.0.0.1:3000')
})
server.on('request', (req, res) => {
const url = req.url
console.log('url:', url)
const method = req.method
console.log('method', method)
if (url === '/getUserList' && method === 'GET') {
fs.readFile(path.join(__dirname, './4-user.json'), 'utf-8', (err, data) => {
if (err) {
console.log(err)
res.end('404')
} else {
res.setHeader('Content-type', 'text/html;charset=UTF-8')
res.end(data)
}
})
return
}
res.end('hello word')
})
|
post请求
1
2
3
4
5
6
7
8
9
10
11
12
13
| /**
* node支持大容量的参数传递, 它会分批接收参数, 接收参数会触发两个事件
* 1.给req注册一个data事件
* req.on('data', (chunk)=>{})
* 每接收一次参数就触发一次, 接收到的chunk是字符串格式
* 如果参数较多,它支持分批进行参数的接收,当客户端每发送一次数据流,都会触发里面的回调函数,我们需要主动将这些数据拼接起来
*
* 2.给req注册一个end事件
* req.on('end', ()=>{})
* 当客户端post数据全部发送完毕之后,就会触发这个事件
*
* 3.使用querystring模块解析接收完成的post参数数据
*/
|
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
| // 服务器
const http = require('http')
const fs = require('fs')
const path = require('path')
// 解析参数的querystring模块
const querystring = require('querystring')
const server = http.createServer()
server.listen(3000, () => {
console.log('127.0.0.1:3000')
})
server.on('request', (req, res) => {
// 允许跨域
res.setHeader('Access-Control-Allow-Origin', '*')
const url = req.url
console.log('url:', url)
const method = req.method
console.log('method', method)
if (url === '/login' && method === 'POST') {
let postData = ''
// 1.注册一个data事件
req.on('data', chunk => {
//具体多少次,取决于客户端带宽
postData += chunk
})
req.on('end', () => {
// 2.给req注册一个end事件
// 3.使用querystring模块解析接收完成的post参数数据
let postObj = querystring.parse(postData)
console.log('postObj',postObj)
if (postObj.username == 'admin' && postObj.password == '123456') {
res.end('yes')
} else {
res.end('no')
}
})
} else {
res.end('hello word')
}
})
|
登录页
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form method="post" action='http://127.0.0.1:3000/login'>
用户名:<input type="text" name='username' placeholder="请输入用户名"><br>
密码: <input type="password" name='passwrod' placeholder="请输入密码"> <br>
<input type="submit" value="提交">
</form>
</body>
</html>
|
模块化
可以先把路由和响应拆分出来
以一个登录页面为例:
程序入口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // app.js
// 路由被拆分到路由模块
const http = require('http')
const router = require('./js/router')
const server = http.createServer()
server.listen('3000', function () {
console.log('http://127.0.0.1:3000')
})
server.on('request', function (req, res) {
router(req, res)
})
|
路由模块
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
| // js/router.js
// 响应被拆分到响应模块
const handler = require('./handler')
module.exports = function (req, res) {
//获取请求方式
let method = req.method.toLowerCase()
//获取请求url
let url = req.url
//判断请求方式和url
//读取注册页面并返回
if (method == 'get' && url == '/register') {
handler.getRegisterPage(req, res, url)
}
//静态资源处理
else if (method == 'get' && url.indexOf('/css/') != -1 || url.indexOf('/js/') != -1 || url.indexOf('/images/') != -1) {
handler.getStaticSource(req, res, url)
}
//实现用户注册
else if (method == 'post' && url == '/register') {
handler.userRegister(req, res, url)
}
}
|
响应处理
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
| const fs = require('fs')
const path = require('path')
const mime = require('mime')
module.exports = {
//响应页面注册
getRegisterPage: function (req, res) {
fs.readFile(path.join(__dirname, '../views/register.html'), function (err, data) {
if (err) {
res.end('404')
throw err
} else {
res.end(data)
}
})
},
//响应静态资源
getStaticSource: function (req, res, url) {
fs.readFile(path.join(__dirname, '../' + url), function (err, data) {
if (err) {
res.end('404')
throw err
} else {
//根据文件类型不同设置响应头
res.setHeader('Content-Type', mime.getType(url))
res.end(data)
}
})
},
//实现用户注册
userRegister: function (req, res) {
//分批接收数据
let str = ''
req.on('data', (chunk) => {
str += chunk
})
req.on('end', () => {
console.log('str',str)
//调用自定义模块把接收的数据转成对象
let obj = JSON.parse(str)
console.log(obj)
//读取旧的数据
fs.readFile(path.join(__dirname, '../data/users.json'), 'utf-8', function (err, data) {
//设置响应头
res.setHeader('Content-type', 'text/html;charset=UTF-8')
if (err) {
let ret = {
code: 404,
msg: '注册失败'
}
res.end(JSON.stringify(ret))
throw err
} else {
let arr = JSON.parse(data)
console.log('arr',arr)
console.log('obj',obj)
//把数据加到旧数据
arr.push(obj)
fs.writeFile(path.join(__dirname, '../data/users.json'), JSON.stringify(arr, null, ' '), function (err, data) {
if (err) {
let ret = {
code: 404,
msg: '注册失败'
}
res.end(JSON.stringify(ret))
} else {
let ret = {
code: 200,
msg: '注册成功'
}
res.end(JSON.stringify(ret))
}
})
}
})
})
}
}
|
页面
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
| <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>用户注册</title>
<link rel="stylesheet" href="../css/index.css" />
</head>
<body>
<div class="register">
<form id="myForm">
<ul>
<li>
<label for="">用户名</label>
<input type="text" name="username" class="name" />
</li>
<li>
<label for="">密码</label>
<input type="password" name="password" class="pass" />
</li>
<li>
<label for="">手机号</label>
<input type="text" name="phone" class="mobile" />
</li>
<li>
<label for=""></label>
<button type="submit" class="submit">立即注册</button>
</li>
</ul>
</form>
</div>
<script>
const form = document.querySelector('#myForm')
form.addEventListener('submit', e=>{
event.preventDefault()
const formData = new FormData(event.target)
const data = Object.fromEntries(formData.entries())
fetch('http://127.0.0.1:3000/register', {
method: 'post',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
}).then(res=>{
console.log('res', res)
})
})
</script>
</body>
</html>
|
查看应用内存占用
Node.js提供了内置的v8
模块,可以用来查看程序的内存占用情况。具体步骤如下:
- 在程序中引入
v8
模块
const v8 = require('v8');
- 手动触发垃圾回收
v8.setFlagsFromString('--expose-gc'); global.gc();
- 获取内存占用信息
const heap = v8.getHeapStatistics(); console.log(heap);
getHeapStatistics()
方法返回一个包含有关V8堆内存使用情况的对象,包括总内存使用量,已分配内存量,垃圾回收次数等信息。通过这些信息,可以分析出程序的内存占用情况并进行优化。
注意:使用v8
模块需要在启动Node.js时使用--expose-gc
选项启用垃圾回收器的暴露功能。node --expose-gc app.js