March 2, 2023

Node.Js

官方文档

https://nodejs.org/docs/latest-v16.x/api/
官方文档永远是最好的老师

Node.js简介

node意为节点,直译过来就是节点js,通俗的来说Nodejs就是运行在服务端的js环境
之前写的JS全是在浏览器运行的,而Node.js可以让JS代码直接在服务器运行

使用Nodejs执行js文件

从简单的Hello Node开始,我们再命令行窗口输入node就可以进入交互界面:
image.png
可以用js的语句输出一段话

1
2
3
let name="hello";
console.log(name+"nodejs");
// alert("hello nodejs");

运行node hello.js即可在服务端运行js文件了
image.png

Node.js基础模块化简介

为了敲代码有更舒适的体验,编写js代码没啥问题,但是假如要用nodejs的函数和代码没有tab提示那就难受了所以IDEA配置一下:
image.png
enable那个code assistance即可

CommonJS规范:
ES的缺陷(ECMAscript):

在Node.js中模块也就是其他语言中的库,比如python的import这样式儿的
require("文件名")这种形式去引入模块,文件名中的.js可以省略
一个模块中的变量和函数在其他js文件中无法访问,因为它不是全局用域,相当于包裹在一个函数内,想要解决这个问题,我们得向外部暴露(export)属性
exports.x="test",这样就向外部暴露了属性
这样console.log(require('xx.js"))返回的是以下内容
image.png
想要获得这个x只需var md=require("xx.js");console.log(md.x)即可,方法也同理
想暴露在外用export,不想暴露就直接写里边儿即可

模块化详解

模块分为2大类:核心模块,文件模块
核心模块就是Node.js自带的模块,文件模块就是开发者自己写的js文件做成的模块,这两本质一样
核心模块的标识就是模块的名字,文件模块的标识是文件的路径
在Node中没有windows全局变量,取而代之的是global
在js里定义全局变量就不要再变量名前加var即可
要证明程序运行在函数内,就使用console.log(arguments),arguments参数只在函数体里面有,arguments.callee返回的是当前的函数是谁
arguments.callee+''把这个对象变为字符串,输出完整的函数:
image.png

1
2
3
4
5
function (exports, require, module, __filename, __dirname) {
let name="hello";
console.log(arguments.callee+'');
// alert("hello nodejs");
}

也就是说Node会自动给我们添加function (exports, require, module, __filename, __dirname)
所以为什么能用export呢,因为他是我们函数的一个属性
console.log(__filename)打印当前模块的路径image.png
__dirname就是js文件所在文件夹的路径

exports和module.exports

这两东西有啥区别呢?

1
2
3
4
5
6
7
8
module.exports={
name:"kino",
age:19,
sayname:function (){
console.log("i am boogipop");
}
}

1
2
3
4
var test=require("./module");
console.log(test.name);
console.log(test.age);
test.sayname();

image.png
在module.exports模式中可以这样取定义暴露变量,而exports是不可以的
同时:

1
2
3
4
5
var obj={};
obj.a={};
var a=obj.a;
a.name="kino";
console.log(obj.a.name);

image.png
由于a指向了obj.a,所以obj.a也跟着改变了,这一点和其他语言貌似有些不同,他等号就是指向了

1
2
3
4
5
6
var obj={};
obj.a={};
var a=obj.a;
a.name="kino";
a=new Object();
console.log(obj.a.name);

假如把a重新赋值一个对象,那obj.a仍然不会改变的:
image.png
这里要搞清楚,这是深拷贝和浅拷贝,这里涉及到栈内存堆内存
image.png
这样普通的基本类型赋值是在栈内存进行的,所以不会有指向问题
但对象类型就不一样了:
image.png
当我们new了一个对象时,会在堆内存中创建一个对象地址,我们赋值操作给的是地址,所以才会有上面的结果
image.png
但是当我们再给obj2进行赋值时,是在堆内存进行赋值,所以没有影响堆内存中的对象,因此对obj一点影响也没有

包简介

包可以理解为,更高级的模块,里面有很多简单模块整合在一起
image.png
image.png

npm简介

npm全程node package manager,叫做node包管理系统
常用命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
npm -v
查看版本
npm
帮助说明
npm search 包
搜索包名
npm install 包
下载包(安装路径)
npm install 包 -g
全局模式安装包
npm remove 包
删除包
npm install xxx --save
将包安装到依赖

假如想在当前目录去安装包,在当前位置执行npm init先初始化,再npm install即可
要引入就要在package.json文件所在路劲创建js文件,上面的init就是创建json文件
--save:它的特点是,在安装的同时,把这个包设置为了依赖文件:
image.png
加了之后下次npm install后面啥也不跟,就会自动安装math类

配置cnpm

cnpm是啥呢,由于npm用的是外国的网,所以会很慢,中国人为了解决这一问题搞了个镜像,叫做cnpm,这样就可以对着中国的镜像服务器去下载包了,cnpm大大的加快了我们下载的速度
配置cnpm:
运行npm install -g cnpm --registry=https://registry.npmmirror.com下载cnpm:
image.png
然后我们就可以用cnpm去下载express框架的包,cnpm的指令和npm一模一样,在这之前我们先初始化一下我们需要安装包的文件夹:
image.png
entrypoint就是哪个js文件需要引用包,这就是入口,这里最好用一下index.js
接下来在这个文件夹安装express即可:
image.png
cnpm大大的加快了我们的speed
image.png

node搜索包的流程

node在使用模块名引入时,它首先在当前目录的node_moudle去找到是否有这个模块,如果没有就去上一级找node_moudle,如果上级也没有,那就上上级,一直到根目录都没有,那就报错

[*]Buffer缓冲区

啥是buffer缓冲区呢?实际上buffer和数组十分的类似,但是数组能存音乐,能存视频吗,那肯定是不可以的,buffer就是专门用来储存二进制数据的,使用buffer不需要引入模块,直接使用即可

1
2
3
4
var str="hello kino";
//创建buffer
var buf=Buffer.from(str);
console.log(buf);

image.png
不是说二进制吗,为什么是十六进制
确实是二进制,但是在计算机中,二进制是以十六进制去呈现的
buffer中的每一个元素都是从00-ff,也就是0-255
image.png
也就是我们的hello中一个字母就是一个字节
buf.length表示占用内存大小
创建指定大小的buffer:

1
2
var buf2=new Buffer(1024);//创造大小为1024也就是1k的buffer
console.log(buf2.length);

image.png
但是这种方法已经被废弃了,所以会出现报错:
image.png
因为有安全隐患,因此使用下面的方法:

1
2
3
4
var buf2=Buffer.alloc(20);//20字节大小
console.log(buf2);
buf2[0]=88;
console.log(buf2);

image.png
可以看到可以用数组的样子去操控buffer,但是为什么是58呢?因为是十六进制
image.png
改为0x58也可以,可以识别十六进制字符串
buffer的大小一经确定,再也不可以修改了,buffer是对底层内存的直接操作
加入我们console.log(buf[1])这样不会输出十六进制,只会输出十进制
假如需要转换就在后面加一个toString(16)即可也就是console.log(buf[2].toString(16))

1
Buffer.allocUnsafe(20);//创建指定大小的内存,但buffer中可能有敏感信息

敏感信息就是初始化的时候可能不是00,也就是这个内存空间可能是之前其他代码用过的
如果我们需要将buffer缓冲区的数据转化为字符串的话直接console.log(buf.toString())即可

[*]同步文件写入

fs全称file system也就是文件系统,顾名思义也就是对文件进行操作的系统,那必然也有I/O操作,也就是input和output
使用fs模块需要先引入一下,由于是核心模块所以不需要下载
image.png
可以看到有很多方法

1
2
3
4
5
6
7
8
9
10
11
12
var fs=require("fs");
//同步文件的写入
var fd=fs.openSync("test.txt",'w');//打开一个文件,赋予写的权限
fs.writeSync(fd,"Hello,world",5);//往文件写入数据,从第五个位置开始写
/*
fs.writeSync(fd,string[,position[,encoding]])
-fd 文件的描述符,需要传递要写入的文件的描述符
-string 要写入的内容
-postion 写入的起始点
-encoding 编码
*/
fs.closeSync();

image.png
这儿就是同步文件写入的方法

[*]异步文件写入

在说异步之前,先说一下同步和异步文件写入之间的区别吧

同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;

异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其
他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。

异步会大大的加大我们写入的效率

1
2
3
4
5
6
7
8
9
10
11
var fs=require("fs");
//异步文件的写入
var fd=fs.open("test.txt",'w',function (){
console.log(arguments);
});//打开一个文件,赋予写的权限
/*
fs.open(path,flags[,mode],callback)
- callback是必要参数,是一个函数
- 异步调用方法,结果都是通过回调函数返回的
*/

简单地通过异步方法打开一个文件,异步方式中callback是必不可少的参数:
image.png
回调函数里有2个参数,第一个是err错误对象,第二个是fd,是文件的描述符
若没错误则err为null,假如出错了就会报错,所以err是第一个参数

1
2
3
4
5
6
7
8
9
10
var fs=require("fs");
//异步文件的写入
var fd=fs.open("test.txt",'r',function (err,fd){
if(!err){
console.log(fd);
}
else{
console.log(err);
}
});

image.png
当我们只给了一个r权限,就报错了,如上

1
2
3
4
5
6
7
8
9
10
11
12
13
var fs=require("fs");
//异步文件的写入
var f;
var fd=fs.open("test.txt",'w',function (err,fd){
if(!err){
console.log("open下的代码执行");
f=fd;
}
else{
console.log(err);
}
});
console.log(f);

按照逻辑来看,f应该是有值的对不对,但是这是错的,因为异步的方式是没有顺序可谈的:
image.png
可以看到是undefined也就是先执行了,这就是异步方式,不会造成堵塞

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
var fs=require("fs");
//异步文件的写入
var fd=fs.open("test.txt",'w',function (err,fd){
if(!err){
//如果没出错则写入
/*
fs.write(fd,string,position,encoding,callback)
callback分为:
-err 错误
-written 指定写入多少字节
-string 内容
*/
fs.write(fd,"Here is Boogipop",function (err){
if(!err){
console.log("写入成功");
}
//关闭
fs.close(fd,function (err){
if(!err){
console.log("文件关闭");
}
})
})
}
else{
console.log(err);
}
});

image.pngimage.png

[*]简单文件写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
fs.writeFile(file, data[, options], callback)
fs.writeFileSync(file, data[, options])
-File 要写入文件的路径
-data 要写入的数据
-options 选项,可以对写入进行一些设置
-callback 当写入完成以后执行的函数

*/
var fs=require('fs');
fs.writeFile("test2.txt","简单文件写入",function (err){
if(!err){
console.log("success")
}

})

image.png
不需要打开和关闭
image.png
有很多打开状态,假如我们想追加内容,我们只需要用a模式

[*]流式文件写入

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
/*
同步和异步,简单文件的方式都不适合大文件的写入,性能较差,容易导致内存溢出
*/
var fs=require("fs");
//创建流
/* fs.createWriteStream(path[, options])
-path 路径
-options 配置参数
*/
var ws=fs.createWriteStream("test3.txt");//创建可写流
//通过监听流的open和close事件来判断流的打开和关闭
// ws.on("open",function (){
// console.log("open")
// })
//一次性,触发一次后自动失效
ws.once("open",function (){
console.log("openonce")
})
ws.once("close",function (){
console.log("close")
})
ws.write("i");
ws.write("am");
ws.write("very");
ws.write("HOLLOWHOLLOWHOLLOWHOLLOWHOLLOWHOLLOWHOLLOWHOLLOWHOLLOWHOLLOWHOLLOWHOLLOWHOLLOWHOLLOWHOLLOWHOLLOW");
//只要流没关闭就可以一直写
//关闭流
// ws.close();
ws.end();
// 同样也可以关闭推荐使用

[*]简单文件读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
1.同步文件读取
2.异步文件读取
3.简单文件读取
fs.readFile(path[, options], callback)
fs.readFileSync(path[, options])
-path 路径
-options 配置,编码权限
-callback 回调函数
4.流式文件读取
*/
var fs=require("fs");
fs.readFile("jsFileDict.txt",function (err,data){
if(!err){
console.log(data.toString());
}
})

image.png
也可以读取图片,视频,假如不加tostring读出来的是buffer二进制内容

1
2
3
4
5
6
7
8
9
var fs=require("fs");
fs.readFile("jsFileDict.txt",function (err,data){
if(!err){
// console.log(data.toString());
fs.writeFile("hello.png",data,function (err){
console.log("文件复制")
})
}
})

这样可以实现图片复制

[*]流式文件读取

适用一些大文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var fs=require("fs");
//创建可读流
var rs=fs.createReadStream("yimoto.png");
rs.once("open",function (){
console.log("open")
});
rs.once("close",function (){
console.log("close")
});
//如果要读取一个可读流的数据,必须绑定一个data事件,data事件绑定完毕,它会开始读取数据
rs.on("data",function (data){
console.log(data);
});

image.png
文件内容一大就会分次读:
pipe():可以直接将可读流文件传到可写流:
image.png

fs模块其他方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fs.existsSync(path)//验证路径是否存在
fs.stat(path,callback)//获取文件状态,异步
fs.statSync(path)//获取文件状态,同步
fs.unlick(path,callback)//删除文件,异步
fs.unlinkSync(path)//同步
fs.readdir(path,[,options],callback)//列出目录文件
fs.readdirSync(path,[,options])
fs.truncate(path,len,callback)//截断文件
fs.truncateSync(path,len)
fs.mkdir(path,[,mode],callback)//创建目录
fs.mkdirSync(path,[,mode])
fs.rmdir(path,callback)//删除文件夹
rs.rmdirSync(path)
rs.rename(oldpath,newpath,callback)//重命名
rs.renameSync(old,newpath)
rs.watchFile(filename,options,listener)//监听文件

image.png
stat就是文件的状态,stat.size获取大小

完结撒花

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#开发