解锁NodeJS高级知识点【学习笔记】

185 阅读9分钟

Node高级

Fs模块

读取文件的三种方式

1.同步读取

const res1 = fs.readFileSync("./abc.txt");
console.log(res1);//<Buffer 31 31 31 31 31>
const res1 = fs.readFileSync("./abc.txt", { encoding: "utf8" });
console.log(res1); //11111

2.异步:回调函数

const res2 = fs.readFile("./abc.txt", { encoding: "utf-8" }, (err, data) => {
	if (err) return;
	console.log(data);
});

console.log("后续代码");

//后续代码
//11111

3.异步:Promise

fs.promises
	.readFile("./abc.txt", { encoding: "utf8" })
	.then((res) => {
		console.log(res);
	})
	.catch((err) => {
		console.log(err);
	});

//11111

fs获取文件描述符

fs.open("./abc.txt", (err, fd) => {
	if (err) {
		console.log(err);
		return;
	}
	//获取文件描述符
	console.log(fd);
	//获取文件信息:修改时间,创建时间,大小等...
	fs.fstat(fd, (err, stats) => {
		if (err) return;

		console.log(stats);
	});
	// fs.fstatSync();
	// fs.promises.fstatSync();
    fs.close(fd);//分配新的文件描述符
});

Stats {
  dev: 2021145378,
  mode: 33206,
  nlink: 1,
  uid: 0,
  gid: 0,
  rdev: 0,
  blksize: 4096,
  ino: 844424930605069,
  size: 5,
  blocks: 0,
  atimeMs: 1677071679788.5024,
  mtimeMs: 1677071087528.7202,
  ctimeMs: 1677071087528.7202,
  birthtimeMs: 1677071066021.2,
  atime: 2023-02-22T13:14:39.789Z,
  mtime: 2023-02-22T13:04:47.529Z,
  ctime: 2023-02-22T13:04:47.529Z,
  birthtime: 2023-02-22T13:04:26.021Z
}

读写

const content = "111";
fs.writeFile("./abc.txt", content, { encoding: "utf8", flag: "a+" }, (err) => {
	if (err) {
		return;
	} else {
		console.log("写入成功");
	}
});

flag:文件系统标志 | Node.js API 文档 (nodejs.cn)

  • 'a': 打开文件进行追加。 如果文件不存在,则创建该文件。

  • 'ax': 类似于 'a' 但如果路径存在则失败。

  • 'a+': 打开文件进行读取和追加。 如果文件不存在,则创建该文件。

  • 'ax+': 类似于 'a+' 但如果路径存在则失败。

  • 'as': 以同步模式打开文件进行追加。 如果文件不存在,则创建该文件。

  • 'as+': 以同步模式打开文件进行读取和追加。 如果文件不存在,则创建该文件。

  • 'r': 打开文件进行读取。 如果文件不存在,则会发生异常。

  • 'r+': 打开文件进行读写。 如果文件不存在,则会发生异常。

  • 'rs+': 以同步模式打开文件进行读写。 指示操作系统绕过本地文件系统缓存。

    这主要用于在 NFS 挂载上打开文件,因为它允许跳过可能过时的本地缓存。 它对 I/O 性能有非常实际的影响,因此除非需要,否则不建议使用此标志。

    这不会将 fs.open()fsPromises.open() 变成同步阻塞调用。 如果需要同步操作,应该使用类似 fs.openSync() 的东西。

  • 'w': 打开文件进行写入。 创建(如果它不存在)或截断(如果它存在)该文件。

  • 'wx': 类似于 'w' 但如果路径存在则失败。

  • 'w+': 打开文件进行读写。 创建(如果它不存在)或截断(如果它存在)该文件。

  • 'wx+': 类似于 'w+' 但如果路径存在则失败。

文件夹操作

创建文件夹

// 创建文件夹
fs.mkdir("./test", (err) => {});
// fs.mkdirSync();

读取文件夹

// 读取文件夹
fs.readdir("./test", (err, files) => {});

读取文件夹,并获取文件夹中文件的信息

//读取文件夹,并获取文件夹中文件的信息
fs.readdir("./test", { withFileTypes: true }, (err, files) => {
	files.forEach((file) => {
		if (file.isDirectory()) {
			console.log("这是一个文件夹", file.name);
		} else {
			console.log("是一个文件", file.name);
		}
	});
});

递归读取文件夹内所有文件

//递归读取文件夹内所有文件
function readDir(path) {
	fs.readdir(path, { withFileTypes: true }, (err, files) => {
		files.forEach((item) => {
			if (item.isDirectory()) {
				readDir(`${path}/${item.name}`);
			} else {
				console.log("获取到的文件", item.name);
			}
		});
	});
}

文件夹重命名(同样适用于文件重命名)

fs.rename("./test", "./newName", (err) => {});

Event模块

const EventEmitter = require("events");

const emitter = new EventEmitter();

function handleFn(args) {
	console.log("监听why的事件", args);
}
//监听事件
emitter.on("why", handleFn);

//发射事件
setTimeout(() => {
	emitter.emit("why", 111);
}, 2000);

//取消监听
emitter.off("why", handleFn);


emit可以传递参数: emitter.emit("why", 111);

常见方法

  • emitter.eventNames(); 返回当前EventEmitter对象注册的事件字符串数组

  • emitter.getMaxListeners(); 返回当前EventEmitter对象最大监听数量(默认10个),可通过**setMaxListeners()**修改

  • emitter.listenerCount("zxs"); 返回当前对象某个事件名称的监听器个数

  • emitter.listeners("zxs"); 返回当前对象某个事件监听器上所有监听器数组

  • emitter.once("zxs",()=>{}) 只监听一次

  • emitter.prependListener() 将事件添加到最前面

  • emitter.prependOnceListener(); 同上

  • emitter.removeAllListeners() 移除所有名称的监听器,带参数则移除特点名称的监听器

Buffer模块

在计算机中,很少情况我们会直接操作一位二进制,因为一位二进制存储的数据非常有限

通常 会将8位合在一起作为一个单元,称为一个字节(byte)

1byte=8bit 1kb=1024byte

rgb值为0-255,本质上用一个字节存储

使用

const buf2 = Buffer.from("hello");
console.log(buf2);//<Buffer 68 65 6c 6c 6f>
console.log(buf2.toString());//hello

其他操作

alloc默认开辟8kb空间

//1.开辟8个字节大小的空间
const buf3 = Buffer.alloc(8);

//2.
console.log(buf3[0]);
console.log(buf3[1]);

buf3[0] = 100;
buf3[1] = 0x66;
console.log(buf3);

从文件中读取buffer

文件读取,不传第二个参数 { encoding: "utf8" },则data默认为buffer

一般用于处理图片

fromStringFast

先判断剩余的空间是否足够填充这个字符串,

如果不够,则通过createPool()开辟新空间

够则直接使用,但之后要进行poolOffset的偏移变化

Stream

问题:可以直接通过readFile...等方式读写文件,为什么还需要流?

readFile方式无法控制 :从什么位置开始读,读到什么位置,一次性读取多少字节,读到某个位置后,暂停读取,某个时刻继续读取等

或当文件非常大时,如一个视频文件,一次性全部读取不合适

http模块的req和res也是基于流实现的

所有流都是EventEmitter的实例

四种基本流类型

1.Writable:可以向其写入数据 的流

const writeStream = fs.createWriteStream("./test.txt", {
	flags: "r+",
	start: 6,
});

writeStream.write("i love you", (err) => {
	console.log("写入完成", err);
});
writeStream.on("close", () => {
	console.log("文件被关闭");
});

//写入完成时,需要手动调用close方法
writeStream.close();

//将最后的内容写入文件,并关闭文件
writeStream.end();

2.Readable:可以从中读取数据 的流

const fs = require("fs");
// 1.可读流
// start:起始位置
//end:结束位置
//highWaterMark:每次读取几个字节,每读完一次,作一次回调,默认64kb
const readStream = fs.createReadStream("./test.txt", {
	start: 6,
	end: 20,
	highWaterMark: 3,
});

readStream.on("data", (data) => {
	console.log(data);
	console.log("执行了一次回调");
	readStream.pause(); //暂停读取
	//暂停1秒后继续读取
	setTimeout(() => {
		readStream.resume(); //继续读取
	}, 1000);
});

其他事件:

  • "open":监听打开文件事件,有参数fd
  • "end":读取到了end位置的事件,会自动关闭文件
  • "close":文件读取结束

3.Duplex:同时为Writable和Readable,如net.Socket

4.Transform:可以在写入和读取数据时 修改或转换数据 的流,如zlib.createDeflate()

Pipe()方法

pipe:直接将可读流的数据流入到可写流

拷贝文件示例:

const readStream = fs.createReadStream("./test.txt");
const writeStream = fs.createWriteStream("./test_copy.txt");

// 方式1.
readStream.on("data", (data) => {
	writeStream.write(data);
});
readStream.on("end", () => {
	writeStream.close();
});

// 方式2.
readStream.pipe(writeStream);

Http模块

const http = require('http')
const path = require('path')
const fs = require('fs');
const url=require("url")
const server = http.createServer();

//req是请求对象 ,包含了客户端相关的数据与属性
server.on('request', (req, res) => {
    console.log("request事件被触发了\n");
    const url = req.url;
    const method = req.method;
	const header=req.headers
    let {pathname, query} = url.parse(req.url)
    const fpath = path.join(__dirname, url)
    const str = `your url为: ${url} 请求方法为${method}`
    console.log(str);
    
    // 设置响应头
    res.setHeader('content-type', 'text/html;charset=utf-8');

    fs.readFile(fpath, 'utf8', function (err, data) {
        if (err) return res.end("404");
        res.end(data)
    })
    // 将内容发送给服务端
    res.end(content);
})

//启动服务器
server.listen(8080, () => {
    console.log("服务器运行中 http://127.0.0.1:8080");
})

createServer底层:new 一个Server对象

function createServer(options, reqListener) {
	return new Server(options, reqListener);
}

小问题:为什么浏览器访问服务器,服务器会响应两次?

原因:因为浏览器默认会请求一次localhost:8000/favicon.ico,还有localhost:8000

nodemon

npm i nodemon -g

req对象本质是一个readable可读流,res对象本质为writeable可写流

const server = http.createServer((req, res) => {
	//获取body参数
	// req对象本质是一个readable可读流
	req.setEncoding("utf-8");
	req.on("data", (data) => {
		const body = JSON.parse(data);
	});
	req.on("end", () => {
		res.end("");
	});
    
    res.write("hello world")
    res.writeHead(401)
    res.end()
});

server.listen(8000);
  • res.setHeader("Content-type:"application/json;charset=utf8"): 一次写入一个头部信息

  • res.writeHead(200,{"Content-type:"application/json;charset=utf8"}): 同时写入header和status

Http发送网络请求和axios

文件上传

上传的文件内容是需要通过 boundary 来标明分割线的,正确的Content-Type: mutlipart/form-data; boundary = -----xxxx这种形式。

原生nodejs 处理文件上传 - 掘金 (juejin.cn)

前端上传的时候只需要设置content-type为multipart/form-data。然后服务端拿到headers 里面的boundary 在根据这个去处理得到文件内容即可。以上是nodejs 原生处理文件上传流程,略显复杂。现在koa 结合koa-body 处理文件上传很方便

Express

当匹配到第一个符合要求的中间件时,则执行。是否执行下一个中间件,则取决当前中间件是否调用了next() 则执行下一个中间件。

const express = require('express');
const app = express();

//中间件本质是一个带next参数的函数(类似于vue路由守卫)
//定义多个全局中间件 会按照定义的先后顺序依次调用
const mw = function (req, res, next) {
    const time = Date.now();
    console.log("这是中间件函数");
    req.startTime = time;
    //把流转关系,转交给下一个中间件或路由
    next();
}
//定义局部中间件 next()必须在结尾  连续多个中间件,之间共享req,res对象
const mw1 = (req, res, next) => {
    console.log("局部中间件1");
    next();
}
const mw2 = (req, res, next) => {
    console.log("局部中间件2");
    next();
}


//中间件分类


//1.错误级别的中间件(必须注册在所有路由后)
// const mwerr=(err, req, res,next) => {
//     console.log("出现错误"+err.message);
//     res.send("中间件捕获到错误"+err.message);
//     // next();
// }
// 2.express内置中间件
// 2.1 express.json解析json格式请求
app.use(express.json())
app.get('/json', (req, res) => {
    console.log(req.body); //通过路由里的req.body访问解析后的json数据
    res.send('OK')
})

// 2.2 express.static
// 2.3 express.urlencoded 
app.use(express.urlencoded({
    extended: false//是否使用node内置的querystring模块,false为使用,true则使用qs模块
}));
app.get('/urlencode', (req, res) => {
    console.log(req.body);
    res.send('OK');
})
//3 第三方中间件
//body-parser
//4 自定义中间件
app.use((req, res, next) => {
    let str = '';
    req.on('data', (data) => {
        str += data;
    })
    req.on('end', () => {
        console.log(str);
        next();
    })
})
//注册全局中间件
app.use(mw)

//使用多个局部中间件(一定要在路由之前注册中间件)
app.get('/mid', [mw1, mw2], (req, res) => {
    res.send('home' + req.startTime);
    console.log("使用了局部中间件");
})

app.get('/', (req, res) => {
    res.send('home' + req.startTime);
})
app.get('/user', (req, res) => {
    res.send('user' + req.startTime)
})

// 1.错误级别的中间件(必须注册在所有路由后)
const mwerr = (err, req, res, next) => {
    console.log("出现错误" + err.message);
    res.send("中间件捕获到错误" + err.message);
    //无next()
}





app.listen(80, () => {
    console.log(" 打开 http://127.0.0.1");
})

express文件上传

multer库

npm i multer
const upload=multer({
    dest:'./upload'//文件储存到的位置
})
//单文件上传
app.post('/upload',upload.single('fileName'),(req,res,next)=>{
    req.file//文件内容
    res.end()
})

//多文件上传
const upload=multer({
   storage:multer.diskStorage({
       destination(req,file,callback){
           callback(null,'./upload')
       },
       filename(req,file,callback){
           callback(null,"自定义文件的名称")
       }
   })
})
app.post('/upload',upload.array('fileName'),(req,res,next)=>{
    req.files//多个文件的内容,数组
    res.end()
})

Koa

Koa 基础 (yuque.com)

ctx.req

Node 的 request 对象.

ctx.res

Node 的 response 对象.

ctx.request

koa 的 Request 对象.

ctx.response

koa 的 Response 对象.

response.body

将响应体设置为以下之一:

  • string 写入
  • Buffer 写入
  • Stream 管道
  • Object || Array JSON-字符串化
  • null 无内容响应

如果下一个中间件是一个异步函数,那next默认不会等到中间件的结果,就会直接执行下一步操作。

期望:等到中间件产生结果,再执行下一步操作。

解决办法:所有中间件加async,await next()

express与koa同步执行中间件顺序相同,异步不同

express中 中间件加async,await next()毫无意义,因为next返回值为void,而不是promise

(170条消息) Node 基础_皮蛋很白的博客-CSDN博客