nodejs学习

代码仓库更新在github:https://github.com/donotknowsjtu/nodejs_study

本学习项目来自 B站尚硅谷nodejs课程:https://www.bilibili.com/video/BV1gM411W7ex?p=58&vd_source=c2437214a3443b24fc91272a4284968e

参考资料:https://github.com/qianguyihao/Web

fs模块

attention:js的相对路径都是相对当前运行环境的工作目录,而不是相对该js文件的目录

引入

const fs = require('fs');

写入

异步写入:定义:写入操作执行时同时进行后面语句的执行,效率高

同步写入:定义:写入操作执行时等待写入操作执行完再执行后面语句,效率低

异步写入example:

fs.writeFile('./hello.txt', 'Hello from Node.js!',err => {
// err write wrong;
if(err){
console.log('error with writing file');
return;
}
console.log('file written successfully');
});

同步写入:将writeFile改为writeFileSync

追加

  1. 方式一:appendFile
  2. 方式二:在writeFile的形式参数中加上{fag:’a’}

for example:

const fs = require('fs');
fs.writeFile('./hello.txt', 'Hello from Node.js!',{flag:'a'},err => {
// err write wrong;
if(err){
console.log('error with writing file');
return;
}
console.log('file written successfully');
});
fs.appendFile('./hello.txt', '\r\nhello', err => {
if(err){
console.log('error with writing file');
return;
}
console.log('file written successfully');
})

流式写入

创建对象

const ws = fs.createWritestream()

写入

ws.write()

结束

ws.end()或ws.close()

可以不加结束语句

特点

流失写入方式适合大文件或频繁操作文件,通过减少打开文件的次数来提高速度

读取

readfile()

  1. 路径
  2. 参数(可选)
  3. 回调函数(error 和 data)

回调的data是buffer,需要通过tostring方法将其转换为string格式便于阅读

readflieSync()是同步读取方法

流式读取

createReadStream,作用:提高读取效率

const fs = require('fs');
const rs = fs.createReadStream('hello.txt');
rs.on('data',chunk => {
console.log('Data: ',chunk.toString());
})

每次读取65536bit,64kb

end可选事件

rs.on('end',() => {
console.log('Read stream finished');
})

个人理解流式写入和读取都是在fs模块的基础上新建对象,然后读取需要绑定事件,写入直接调用方法;

然而直接写入写出可以直接用fs模块新建对象,然后调用方法进行写入写出

但是直接写入写出是将文件全部内容先读出再全部写入,相比之下,流式读取写入则是一边读取一边写入,节约了内存占用

重命名和移动

rename方法,参数包括原路径,末路径,回调函数

所谓的重命名和移动就是更改源文件的路径,本质相同

同步的方法就是加上Sync

for example

// rename
fs.rename('hello.txt', 'rename.txt', err=>{
if(err){
console.log('rename failed');
}
else{
console.log('rename successful');
}
})
// move
fs.rename('rename.txt','rename/rename.txt',error =>{
if(error){
console.log('move failed');
}
else{
console.log('move successful');
}
})

删除

unlink和rm两种方法,参数为文件路径和回调函数

同步就是加上Sync

for example

fs.unlink('hellw2.txt', err => {
if(err){
console.log('delete failed');
}
else{
console.log('delete sussessful');
}
})
fs.rm('hellw2.txt', err => {
if(err){
console.log('delete failed');
}
else{
console.log('delete sussessful');
}
})

创建文件(夹)

mkdir方法,传参:目标directory路径,回调函数

fs.mkdir('test', err => {
if(err){
console.log('create failed');
}
else{
console.log('create successful');
}
}
)

查看文件夹file

readdir方法,传参:目标directory路径,回调函数

fs.readdir('test', (err, files) => {
if(err){
console.log('read failed');
}
else{
console.log('read successful');
console.log(files);
}
})

删除文件夹

rm方法,传参:目标文件路径、回调函数

注:rmdir方法将要被移除

查看文件状态

stat方法,传参:目标文件路径、回调函数

for example

fs.stat('read.js', (err, stats) => {
if(err){
console.log('status failed');
}
else{
console.log('status successful');
console.log(stats);
}
})

stat输出如下:

Stats {
dev: 1084496056,
mode: 33206,
nlink: 1,
uid: 0,
gid: 0,
rdev: 0,
blksize: 4096,
ino: 562949953449871,
size: 212,
blocks: 0,
atimeMs: 1726888786803.9666,
mtimeMs: 1726888783851.079,
ctimeMs: 1726888783851.079,
birthtimeMs: 1726843900323.625,
atime: 2024-09-21T03:19:46.804Z,
mtime: 2024-09-21T03:19:43.851Z,
ctime: 2024-09-21T03:19:43.851Z,
birthtime: 2024-09-20T14:51:40.324Z
}

birthtime:文件创建时间

mtime:最后一次修改时间

ctime:最后一次更新状态时间

atime:最后一次访问时间

按照时间先后排序:b早于m早于c早于a

路径

导入

const path = require('path');

拼接

path.resolve方法;传参:路径;作用:将路径拼接并标准化

for example

const path = require('path');

console.log(path.resolve(__dirname, 'resolve.js'));

console log:

PS D:\study\nodejs\path> node resolve.js
D:\study\nodejs\path\resolve.js

解析路径

parse方法;传参:路径;返回值:目标文件(夹)对象

for example:

const str = 'D:\\study\\nodejs\\path\\resolve.js';
console.log(path.parse(str));

console log:

{
root: 'D:\\',
dir: 'D:\\study\\nodejs\\path',
base: 'resolve.js',
ext: '.js',
name: 'resolve'
}

其余方法

sep 获取操作系统的路径分隔符

basename 获取路径的基础名称,即:文件名

dirname 获取路径的目录名

extname 获取路径的扩展名

http模块

全名:Hypertext Transfer Protocol (超文本传输协议)

导入模块

const http = require('http');

创建服务

createServer方法,参数:回调函数(回调函数参数:require, request)

for example

const server = http.createServer((request, response) => {
// 获取请求
console.log(request.method);
console.log(request.url); // url只包含路径和查询字符串,不包含协议和域名
console.log(request.headers); // 获取请求头
console.log(request.httpVersion); // 获取http版本
console.log(request.socket.remoteAddress); // 获取客户端ip
console.log(request.socket.remotePort); // 获取客户端端口

// 设置响应
response.setHeader('content-type', 'text/html;charset=utf-8'); // 设置响应头
response.end('hello Http Server'); // 设置响应体
});

监听端口,启用服务

listen方法,传参:端口,回调函数

server.listen(9000,()=>{
console.log('server is running ');
})

输出案例

server is running 
GET
/
{
host: 'localhost:9000',
connection: 'keep-alive',
'sec-ch-ua': '"Microsoft Edge";v="129", "Not=A?Brand";v="8", "Chromium";v="129"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0',
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'sec-fetch-site': 'none',
'sec-fetch-mode': 'navigate',
'sec-fetch-user': '?1',
'sec-fetch-dest': 'document',
'accept-encoding': 'gzip, deflate, br, zstd',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6'
}
1.1
::1
20012
GET
/favicon.ico
{
host: 'localhost:9000',
connection: 'keep-alive',
'sec-ch-ua-platform': '"Windows"',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0',
'sec-ch-ua': '"Microsoft Edge";v="129", "Not=A?Brand";v="8", "Chromium";v="129"',
'sec-ch-ua-mobile': '?0',
accept: 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
'sec-fetch-site': 'same-origin',
'sec-fetch-mode': 'no-cors',
'sec-fetch-dest': 'image',
referer: 'http://localhost:9000/',
'accept-encoding': 'gzip, deflate, br, zstd',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6'
}
1.1
::1
20012

获取路径和查询字符串

方法一:通过url模块解析url和查询字符串

const url = require('url');
const server = http.createServer((request, response) => {
let res = url.parse(request.url, true);
let pathname = res.pathname;
let keyword = res.query.keyword;
console.log(pathname);
console.log(keyword);
response.end('hello');
})
const listen = server.listen(9000, () => {
console.log('server is running');
}
)

方法二:通过url类获取url和查询字符串

const server = http.createServer((request, response) => {
let url = new URL(request.url, 'http://sss.sss.sss.com:111');
console.log(url.pathname);
// 获取查询字符串
console.log(url.searchParams.get('keyword'));
response.end('hello2');
}
)
const listen = server.listen(9000, () => {
console.log('server is running');
}
)

URL类的传参必须为完整的url地址包括协议和域名(和端口)

注意:其中 'http://sss.sss.sss.com:111'

只是起到补全url的作用,返回的url不受影响,即:该路径可以随便填写

输出示例如下:

server is running
/ssssss&k
dddjjjj
/favicon.ico
null

其中第二行为我的请求path,第三行为我的请求查询的字符串

具体请求url如下:http://localhost:9000/ssssss&k?keyword=dddjjjj

查询响应报文

// 设置响应码
res.statusCode = 200;
// 设置响应状态的描述
res.statusMessage = 'OK';
// 设置响应头
res.setHeader('contenet-type', 'text/plain');
// 设置多个同名响应头
res.setHeader('Set-Cookie', ['type=ninja', 'language=javascript', 'type=ninja']);
// 设置响应体
res.write('Hello World1\n');
res.write('Hello World2\n');
// write 和 end二选一,且end最多存在一个
res.end('Hello World3\n');
}

响应体的引用

具体请参考代码仓库中的train3

但是随着项目的发展,请求和响应也会大量增加,如果仍然像train3中那样一个个地检测请求并返回响应是非常麻烦的,这里就需要用到动态资源

网页的链接

网页的链接主要又三种方式:

  1. 绝对路径:通常给外链使用
  2. 相对路径(协议拼接)://domainName.com/pathName,大型网站常用
  3. 相对路径(主机拼接):/pathName,与当前页面的协议、主机名、端口计算形成完整URL再发送请求,中小型网站常用

3的好处是域名改变不影响网页使用但是比较复杂,需要抽象分析;1和2虽然直观清晰但是需要及时维护。

设置资源类型(MIME)

MIME(Multipurpose Internet Mail Extensions)是一种标准,用来表示文档、文件或字节流的性质和格式

GET 和 POST请求场景

get请求:

  1. 浏览器输入url访问
  2. a标签中的链接
  3. link引入css
  4. script引入js
  5. aideo和vudio引入音视频
  6. img 引入图库
  7. form表单中的method为get(不区分大小写)
  8. ajax中的get请求

post请求:

  1. form表单中的method为post(不区分大小写)
  2. AJAX中的post请求

get主要用来获取数据,post主要用来提交数据

get请求大小一般为2k,post请求没有大小限制

post比get更安全

导出导入

js文件导出:module.exports = 常量/变量/函数

导入:requires(’相对路径即可’) 注:对于js和json和node拓展文件可以不加后缀名,同名以js优先

导入文件夹

通过require(’./文件夹相对路径’)来导入文件夹

注意:这里的路径必须规范,如果是在当前目录下的目录,需要加上./ 其余类似,否则会报错无法找到文件等

多啰嗦一嘴:换句话说,如果我不加具体路径,直接写文件夹的名称,nodejs会默认这个是一个库或者文件(省去了后缀名),并且尝试从内置模块和node_modules和本地目录中进行查找,但是由于这是一个文件夹而且不是组件库,并没有以node_module命名的文件,所以会返回报错

然后在文件夹中编写package.json指定导出方式,对于main的文件优先导出

package.json’s example

{
"main": "index.js"
}

如果package.json文件不存在,则会尝试导入文件夹下的index.js和index.json文件

否则报错

导入组件库

example:const unip = require('uniq');

从node_modules中导入uniq包,如果node_modules中没有该包,则会逐级递增查找,直至盘符

npm

npm init:初始化packages库

作者的一些废话:当我学到这里(npm)的时候,我总算对nodejs的角色有了一定的理解,之前我一直搞不懂nodejs在前后端中扮演了一个什么角色。现在来看,它既是一个前端的框架又是一个后端的框架,或许我的理解还是有问题,不过无所谓。nodejs既能为前端的网页(包括其它客户端)开发开发框架,又能为前端提供大量的组件库;同时它也是一个良好的服务器运行环境,许多内置(包括外部下载)的模块提供了js的本地运行环境。//此段写于2024.9.24 23:21

对于node的package库与其它语言的包管理方式不太一样,node的组件有两种安装方式1.是按库管理的,换句话说,每一个node库单独安装和运行自己的node_modules库,这里的node_modules库在package.json中的呈现是依赖:dependencies;

2.全局安装, npm install -g package

以python为例,pip是在当前的python环境中管理库,同一环境下的任何项目均可直接调用并共享库;但是node则是每个项目(库)单独管理

生产依赖和开发依赖

npm install -S package_name: S是生产依赖(dependencies);

npm install -D package_name:D是开发依赖(devDependencies).

如何确认使用哪个?

1.看package文档

2.自己感觉

安装包依赖

npm i (npm install),根据package.json和package-lock.json安装项目依赖

删除包依赖

局部删除

npm r (remove) package

全局删除

npm r (remove) -g package

cnpm

cnpm 是淘宝镜像,与npm几乎完全等价

或者直接npm set registry也可以

yarn

yarn是npm的替代品,也是npm的一个组件库

yarn官方宣称优点:

  1. 速度快,yarn通过缓存和并发下载提高安装速度
  2. 安全,yarn会验证包的完整性
  3. 跨平台,yarn通过锁文件格式和明确的安装算法使得能在不同系统上工作

安装yarn

npm i -g yarn

其余命令

初始化 yarn init (-y)

生产依赖 yarn add package

开发依赖 yarn add --dev package

yarn global add package

yarn remove package

yarn global remove package

安装项目依赖 yarn

不需要run直接运行命令 yarn command

yarn下的lock文件由package-lock.json变成了yarn.lock并且node_modules中的.package-lock.json变成了.yarn-integrity

yarn全局安装添加环境变量

通过yarn global bin查看yarn的安装路径,然后添加到环境变量即可直接运行全局安装的脚本

nrm

nrm是一个组件库,来选择npm仓库

命令:

nrm list 查看仓库

nrm use registry 使用该仓库

express 框架

express 是nodejs的一个组件库,提供了服务器服务(不太准确,目前理解是这样)

express路由

router:路由确定了应用程序如何响应客户端对特定端口的请求

获取url路由参数

get('/:abc.html')

req.params.abc_name

响应方法

for example

app.get('/', (req, res) => {
// 重定向响应
res.redirect('http://donotknowsjtu.top');
// 下载响应
res.download(__dirname + '/package.json');
// json响应
res.json({
name: 'zhangsan',
age: 18
});
// 响应文件内容
res.sendFile(__dirname + '/package.json');

})

中间件

中间件说到底就是与路由配合执行的(回调)函数

中间件分为两种,第一种是全局中间件,第二种是路由中间件

路由中间件

for example

function checkCodeMiddleWare(req, res, next){
if(req.query.code === '521'){
next();
}
else{
res.send('code error');
}
}
app.get('/home',checkCodeMiddleWare, (req, res) => {
res.send('home page');
})
app.get('/about',checkCodeMiddleWare, (req, res) => {
res.send('about page');
})

其中checkCodeMiddleWare 函数就是一个路由中间件函数。

其中next()函数用来进行将控制权后移,使得程序继续运行下去。

全局中间件

for example

// 声明访问日志记录中间件
function recordMiddleWare(req, res, next){
let {url, ip} = req;
fs.appendFileSync(path.resolve(__dirname , './access.log'),`url:${url} \t ip:${ip} \r\n`);
// 调用next函数将控制权下移
next();
}
// 调用中间件
app.use(recordMiddleWare);
// 创建路由

通过app.use(function)来调用中间件

静态资源中间件设置

静态资源中间件是一个管理静态资源的全局中间件。

使用: app.use(express.static(path));

功能:类似nginx中的设置静态资源的根目录,所有的get请求都会从path中查找并返回同时自动设置好字符集。

注意:

  1. 默认根路径为根目录下的index.html
  2. 冲突:静态资源和路由规则冲突,谁在上谁响应
  3. 由于2,路由通常响应动态资源,静态资源交由静态资源中间件来管理

防盗链

通过req中的referer来获取访问页面的hostname,来判断是否是外部网站,如果是外部网站返回404从而避免本网站资源被外部网站引用使用

模板引擎ejs

模板引擎是一种分离用户界面和业务数据的技术,可以在express框架中结合使用

for example

const ejs = require('ejs');
const fs = require('fs');
const html = fs.readFileSync('./try.html', 'utf-8');
let china = '中国';
let result = ejs.render('我的国家是<%= china %>', {china: html});
console.log(result); // 我的国家是中国

模板文件

for example

// 设置模板引擎
app.set('view engine', 'ejs');
// 设置模板文件目录,模板文件是具有模板语法内容的文件
app.set('views', path.resolve(__dirname, './views' ));
// 创建路由
app.get('/home', (req, res) => {
// 渲染模板文件
let title = 'this is home page';
res.render('home', {title});

})

首先在express中设置模板引擎为ejs, 然后设置模板文件目录,注意模板文件目录中的渲染文件的后缀为.ejs,语法为html标签混合ejs语法。

express-generator

express-generator是一个组件库,用来快速生成express框架下的文件夹体系。

全局安装:

express i -g express-generator

初始化(最好是空文件夹)

express -e

安装依赖

npm i

启用服务

npm start

分析:首先全局安装是在本地环境安装express-generator脚本,类似于安装git,然后对选定的文件夹进行初始化,类似于git的init,然后是安装依赖,根据package.json中的内容通过npm自动配置依赖环境,最后可以直接用该文件夹下预设定的start脚本在npm环境下运行服务。

这里的start命令其实是调用了 node ./bin/www.js

也就是服务的入口是bin目录下的www.js文件。

然后通过bin目录www.js引入根目录下的app.js脚本并且启用app.js所涉及到的全部服务并启用端口监听使得服务上线。

app.js除了本身包含的服务,还会再引入routes中的index.js和users.js脚本,routes文件夹负责处理路由和中间件,并且通过render渲染引入views中的ejs文件(html),

ejs文件再通过链接引入public目录中的静态css、js等文件。

对于引入的组件都保存在node_modules文件夹中。

像这样,express-generator的五大目录,五大文件都分析好了。从npm start的命令开始到每个文件之间的链接以及服务的启用过程都分析清楚了。

未完待续,,,