Nodejs笔记

NodeJS

Node 是一个构建于 Chrome V8引擎之上的一个 Javascript 运行环境, 作用是让 js 拥有开发服务端的功能

安装

1
2
# 查看版本
node -v

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 组成

  • ECMAScript
  • 核心模块
  • 第三方模块

基本的语法和写法和之前的 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. 如果文件夹不存在,报错

示例:

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=>微任务队列=>宏任务队列

服务器基础

基本的访问流程

  1. 输入主机地址
  2. 指定端口(如果没有指定, 默认是80)
  3. 指定需要访问的资源路径
  4. 发起请求
  5. 获取服务器返回的结果并处理

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

流程:

  1. 导入模块 const * = require(*)
  2. 创建服务器const server = http.creatServer()
  3. 监听端口server.listen(端口, ()=>{})
  4. 响应请求, 进行事件处理server.on('request', (req,res)=>{})

注意点:

  • req.url可以获取当前用户请求的url
  • 中文乱码
1
2
3
res.setHeader('Content-type','text/html;charset=UTF-8')

// html页面不需要, 头部已有
  • 客户端没有指定url,默认为/
 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模块,可以用来查看程序的内存占用情况。具体步骤如下:

  1. 在程序中引入v8模块

const v8 = require('v8');

  1. 手动触发垃圾回收

v8.setFlagsFromString('--expose-gc'); global.gc();

  1. 获取内存占用信息

const heap = v8.getHeapStatistics(); console.log(heap);

getHeapStatistics()方法返回一个包含有关V8堆内存使用情况的对象,包括总内存使用量,已分配内存量,垃圾回收次数等信息。通过这些信息,可以分析出程序的内存占用情况并进行优化。

注意:使用v8模块需要在启动Node.js时使用--expose-gc选项启用垃圾回收器的暴露功能。node --expose-gc app.js

build with Hugo, theme Stack, visits 0