广告:宝塔Linux面板高效运维的服务器管理软件 点击【 https://www.bt.cn/p/uNLv1L 】立即购买
本篇文章带大家了解一下Nodejs中常见的内置模块(路径、文件系统、Events),希望对大家有所帮助!
内置模块path路径的演练
path模块用于对路径和文件进行处理,提供了很多好用的方法
并且我们知道在Mac OS
、Linux
和windows
上的路径分隔符是不一样的
/
来作为文件路径的分隔符windows上会使用``或者\
来作为文件路径的分隔符,当然目前也支持/
那么如果我们在windows上使用\
来作为分隔符开发了一个应用程序,要部署到Linux上面应该怎么办呢?
如果我们盲目的将两个路径进行拼接,比如path1 + '/' + path2
,可能在当前所在的操作系统中是可以跑起来的,但如果我们换了一个操作系统执行代码,可能就识别不到该路径了,因为不同的操作系统要求的路径分割符是不一样的
const path = require('path')// 我们path1和path2两部分路径故意写错误的分隔符出来const path1 = 'User\ASUS\DCC'const path2 = 'MyWork\5月4日'// path对象中的resolve方法可以实现两个路径之间根据当前操作系统选取正确的路径分隔符进行拼接const fileName = path.resolve(path1, path2)// 可以看到我们原字符串中原本是用\拼接的,通过path.resolve方法之后都换成了使用\进行拼接console.log(fileName); // C:\Users\ASUS\Desktop\前端学习\算法\User\ASUS\DCC\MyWork\5月4日登录后复制
path模块的其他方法
1. 获取路径的信息
path.dirname()
方法返回一个 path 的目录名path.basename()
方法返回一个 path 的最后一部分,一般来说是文件名path.extname()
方法返回 path 的扩展名const path = require('path')const file = '/foo/bar/baz/asdf/quux.html'const path1 = path.dirname(file)const path2 = path.basename(file)const path3 = path.extname(file)console.log(path1); // /foo/bar/baz/asdfconsole.log(path2); // quux.htmlconsole.log(path3); // .html登录后复制
2. join路径拼接
path.join()
方法使用平台特定的分隔符把全部给定的 path 片段连接到一起,并规范化生成的路径。简单来说就是根据当前的操作系统选取合适的路径分隔符将多个路径拼接在一起,当然其也会纠正原先路径中不正确的分隔符
const path = require('path')const path1 = '/USER/ASUS'const path2 = 'My/DCC'const fileName = path.join(path1, path2)console.log(fileName); // \USER\ASUS\My\DCC登录后复制
3. resolve方法拼接(用的最多)
path.resolve()
方法会把一个路径或路径片段的序列解析为一个绝对路径
const path = require('path')const path1 = 'asd/sad'const path2 = 'My/DCC'const fileName1 = path.join(path1, path2)const fileName2 = path.resolve(path1, path2)console.log(fileName1); // asd\sad\My\DCCconsole.log(fileName2); // C:\Users\ASUS\Desktop\前端学习\算法\asd\sad\My\DCC登录后复制
4. resolve和join方法的区别
如果处理完全部给定的 path 片段后还未生成一个绝对路径,则当前工作目录会被用上,且其能识别../
、./
和/
path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif');// 如果当前工作目录为 /home/myself/node,// 则返回 '/home/myself/node/wwwroot/static_files/gif/image.gif'登录后复制给定的路径的序列是从右往左被处理的,后面每个 path 被依次解析,直到构造完成一个绝对路径
const path = require('path')const path1 = 'asd/sad'const path2 = '/My/DCC'const fileName1 = path.join(path1, path2)const fileName2 = path.resolve(path1, path2)console.log(fileName1); // asd\sad\My\DCCconsole.log(fileName2); // \My\DCC登录后复制生成的路径是规范化后的,且末尾的斜杠会被删除,除非路径被解析为根目录
const path = require('path')const path1 = '/USER/ASUS'const path2 = 'My/DCC/'path.join(path1, path2) // \USER\ASUS\My\DCC\path.resolve(path1, path2) // \USER\ASUS\My\DCC登录后复制
path.resolve方法在webpack中也有大量使用
比如我们在react
项目使用craco
来配置路径别名的时候
使用ES Module也是可以引入node中的核心模块的
因为上面也有说过,ES Module
导入CommonJS
的模块是被允许的,所以自然也可以导入嵌入在node中的核心模块
// test.mjsimport path from 'path'console.log(path); // 是正常的path对象登录后复制内置模块fs
fs是File System
的缩写,表示文件系统
对于任何一个为服务器端服务的语言或者框架通常都会有自己的文件系统
因为服务器需要将各种数据、文件等放置到不同的地方比如用户数据可能大多数是放到数据库中的比如某些配置文件或者用户资源(图片、音视频)都是以文件的形式存在于操作系统上的Node也有自己的文件系统操作模块,就是fs
windows、Mac OS、Linux
)上面直接去操作文件这也是Node可以开发服务器的一大原因,也是它可以成为前端自动化脚本等热门工具的原因fs的API介绍
大多数API都提供了三种操作方式:
方法一:同步操作文件——代码会被阻塞,不会继续执行const fs = require('fs')const filename = './test.html'const file = fs.statSync(filename)console.log(file);登录后复制
statSync
方法可以同步读取我们的文件信息, 返回一个 fs.Stats 实例
fs.stat
使用方法:fs.stat(path, callback)
,回调有两个参数 (err
, stats
) ,其是异步读取文件的,在发生错误或者读取完成之后都会去执行我们的回调函数
const fs = require('fs')const filename = './test.html'// 回调函数的两个参数,一个是错误信息,另一个是文件信息const file = fs.stat(filename, (err, info) => { console.log(info); // Stats类})console.log(file); // undefined,因为是异步的,所以并不会阻塞代码执行,打印的时候还没有获取到文件信息登录后复制
方法三:异步Promise
操作文件——代码不会被阻塞,通过fs.promises
调用方法操作,会返回一个Promise,可以通过then
、catch
进行处理
const path = require('path')const file = '/foo/bar/baz/asdf/quux.html'const path1 = path.dirname(file)const path2 = path.basename(file)const path3 = path.extname(file)console.log(path1); // /foo/bar/baz/asdfconsole.log(path2); // quux.htmlconsole.log(path3); // .html0登录后复制
文件描述符
文件描述符(File desciptors
)是什么呢?
POSIX
系统上,对于每个进程,内核都维护着一张当前打开着的文件和资源的表格每个打开的文件都分配了一个称为文件描述符的简单的数字标识符在系统层,所有文件系统操作都是用这些文字描述符来标识和跟踪每个特定的文件Windows
系统使用了一个虽然不同但概念上类似的机制来跟踪资源为了简化用户的工作,Node.js抽象出操作系统之间的特定差异,并为所有打开的文件分配一个数字型的文件描述符。也就是说node中的api很多把文字描述符的东西屏蔽掉了,相当于内部帮你做了这些操作
fs.open()
方法用于分配新的文件描述符一旦被分配,则文件描述符可用于从文件读取数据、向文件写入数据、或请求关于文件的信息const path = require('path')const file = '/foo/bar/baz/asdf/quux.html'const path1 = path.dirname(file)const path2 = path.basename(file)const path3 = path.extname(file)console.log(path1); // /foo/bar/baz/asdfconsole.log(path2); // quux.htmlconsole.log(path3); // .html1登录后复制
文件的读写
如果我们下网对文件的内容进行操作,这个时候可以使用文件的读写
fs.readFile(path, options, callback)
:读取文件的内容fs.wraiteFile(file, data, options, callback)
:在文件中写入内容1. 文件写入
const path = require('path')const file = '/foo/bar/baz/asdf/quux.html'const path1 = path.dirname(file)const path2 = path.basename(file)const path3 = path.extname(file)console.log(path1); // /foo/bar/baz/asdfconsole.log(path2); // quux.htmlconsole.log(path3); // .html2登录后复制
我们原本是没有这个文件的,但由于options
参数中的flag
属性默认是w
,所以会帮我们自动创建一个文件并将对应的值写入
在上面的代码中,你会发现有一个大括号没有填写任何的内容,这个就是写入时填写的options参数
flag
:写入的方式,默认是 'w'
const path = require('path')const file = '/foo/bar/baz/asdf/quux.html'const path1 = path.dirname(file)const path2 = path.basename(file)const path3 = path.extname(file)console.log(path1); // /foo/bar/baz/asdfconsole.log(path2); // quux.htmlconsole.log(path3); // .html3登录后复制
我们将flag改为a+之后,做的就是文件的追加操作了,发现我们要写入的文字出现在了目标文件的末尾
encoding
:字符的编码,默认是'utf8'
2. 文件读取
在文件读取时,如果不填写encoding
,则返回的结果是Buffer
,类似是一串二进制编码
因为在fs.readFile
方法中,encoding
属性的默认值为null
,也就是说他是没有默认值的,需要我们手动指定才行,其flag的默认值是'r'
const path = require('path')const file = '/foo/bar/baz/asdf/quux.html'const path1 = path.dirname(file)const path2 = path.basename(file)const path3 = path.extname(file)console.log(path1); // /foo/bar/baz/asdfconsole.log(path2); // quux.htmlconsole.log(path3); // .html4登录后复制
文件夹操作
1. 新建一个文件夹 — fs.mkdir(path[, mode], callback)
const path = require('path')const file = '/foo/bar/baz/asdf/quux.html'const path1 = path.dirname(file)const path2 = path.basename(file)const path3 = path.extname(file)console.log(path1); // /foo/bar/baz/asdfconsole.log(path2); // quux.htmlconsole.log(path3); // .html5登录后复制
发现对应的文件夹已经创建好了,如果我们再执行一遍这个程序发现会报错,说明了相同的文件夹是不可以重复创建的,同时也说明了当我们使用fs.mkdir
方法创建文件时,可以传入绝对路径,也能传入相对路径
2. 获取文件夹的所有文件 — fs.readdir(path[, options], callback)
const path = require('path')const file = '/foo/bar/baz/asdf/quux.html'const path1 = path.dirname(file)const path2 = path.basename(file)const path3 = path.extname(file)console.log(path1); // /foo/bar/baz/asdfconsole.log(path2); // quux.htmlconsole.log(path3); // .html6登录后复制
思考:如果我们现在想把该文件夹里面的所有文件读取出来,比如说文件夹中其它文件夹的文件,应该要怎么实现呢?
其实我们可以通过传入参数的形式让readdir
方法读取目录下面文件的时候,把它对应的文件类型也传递出来,也就是将options
所对应的withFileTypes
属性更改为true
那么每一个文件信息都对应一个Dirent
对象,且每个对象中都有一个isDirectory
方法(在原型上)用来判断当前文件是不是文件夹,如下图所示:
既然文件夹里面还有可能会套文件夹,所以想要读取出所有文件的路径就必须要用递归的方法来实现了
const path = require('path')const file = '/foo/bar/baz/asdf/quux.html'const path1 = path.dirname(file)const path2 = path.basename(file)const path3 = path.extname(file)console.log(path1); // /foo/bar/baz/asdfconsole.log(path2); // quux.htmlconsole.log(path3); // .html7登录后复制
从打印结果可以得知,确实已经递归实现了打印一个目录下面的所有文件名称
3. 文件夹重命名
重命名可能操作可能需要以管理员身份运行编辑器才被允许
const path = require('path')const file = '/foo/bar/baz/asdf/quux.html'const path1 = path.dirname(file)const path2 = path.basename(file)const path3 = path.extname(file)console.log(path1); // /foo/bar/baz/asdfconsole.log(path2); // quux.htmlconsole.log(path3); // .html8登录后复制
文件夹的复制案例
场景:一个文件夹中有很多个文件夹,每一个文件夹中又有很多的文件,现要求将这些文件按照原先所在的文件目录格式选取出指定后缀名的文件拷贝到另一个文件夹中
const path = require('path')const file = '/foo/bar/baz/asdf/quux.html'const path1 = path.dirname(file)const path2 = path.basename(file)const path3 = path.extname(file)console.log(path1); // /foo/bar/baz/asdfconsole.log(path2); // quux.htmlconsole.log(path3); // .html9登录后复制
当我们执行 node test.js ./dir1 ./dir2 .txt
命令之后,发现以txt
为后缀名的文件都被拷贝过去了,说明我们的程序没有问题
events基础方法
Node中的核心API都是基于异步事件驱动的
在这个体系中,你某些对象(发射器(Emitters
))发出某一个事件我们可以监听这个事件(监听器(Listeners
)),并且传入的回调函数会在事件被触发时调用发出事件和监听事件都是通过EventEmitter类来完成的,他们都属于events对象
emitter.on(eventName, listener)
:监听事件,也可以使用addListener
emitter.off(eventName, listener)
:移除监听事件,也可以使用removeListener
emitter.emit(eventName[, ...args])
:发出事件,可以携带一些参数我们从events模块中导入的内容和其它模块有所不同,因为其是一个类。我们可以根据这个类创建出一个“发射器”
const path = require('path')const path1 = '/USER/ASUS'const path2 = 'My/DCC'const fileName = path.join(path1, path2)console.log(fileName); // \USER\ASUS\My\DCC0登录后复制
通过发射器,我们可以监听、取消、发射相应的事件
可以同时监听多个相同的事件,绑定的函数都会被执行发射事件的时候可以携带多个参数,在监听的回调函数中,我们可以用...剩余运算符来将他们集中到一个变量中如果绑定的都是相同的事件,那么触发的时候按照监听的顺序来执行,下面代码就是先执行“我被点击了1”,然后再执行“我被点击了2”const path = require('path')const path1 = '/USER/ASUS'const path2 = 'My/DCC'const fileName = path.join(path1, path2)console.log(fileName); // \USER\ASUS\My\DCC1登录后复制
如果我们把setTimeout
里面的函数改写一下,然后再将第二个注册事件的回调函数抽离出去,打印结果会发生什么变化呢?
const path = require('path')const path1 = '/USER/ASUS'const path2 = 'My/DCC'const fileName = path.join(path1, path2)console.log(fileName); // \USER\ASUS\My\DCC2登录后复制
第一次发射事件的时候,两个注册的事件都会被触发,但是当我们使用emitter.off
取消了第二个注册事件的后,下次发射相同事件时,第二个事件就不会再被触发了
events获取信息
emitter.eventNames(eventName)
返回一个列出触发器已注册监听器的事件的数组
emitter.listenerCount(eventName)
返回正在监听名为 eventName
的事件的监听器的数量
emitter.listeners(eventName)
返回名为 eventName
的事件的监听器数组的副本
const path = require('path')const path1 = '/USER/ASUS'const path2 = 'My/DCC'const fileName = path.join(path1, path2)console.log(fileName); // \USER\ASUS\My\DCC3登录后复制
events中不常用方法
emitter.once
绑定的事件只监听一次。添加一个单次 listener
函数到名为 eventName
的事件。 下次触发 eventName 事件时,监听器会被移除,然后再调用
const path = require('path')const path1 = '/USER/ASUS'const path2 = 'My/DCC'const fileName = path.join(path1, path2)console.log(fileName); // \USER\ASUS\My\DCC4登录后复制
emitter.prependListener
将监听事件添加到最前面,但是添加 listener
函数到名为 eventName
的事件的监听器数组的开头。 不会检查 listener
是否已被添加。多次调用并传入相同的 eventName
和 listener
会导致 listener
被添加与调用多次
const path = require('path')const path1 = '/USER/ASUS'const path2 = 'My/DCC'const fileName = path.join(path1, path2)console.log(fileName); // \USER\ASUS\My\DCC5登录后复制
mitter.prependOnceListener
:将监听事件添加到最前面,但是只监听一次
emitter.removeAllListeners([eventName])
移除全部或指定 eventName
的监听器;注意,在代码中移除其他地方添加的监听器是一个不好的做法,尤其是当 EventEmitter
实例是其他组件或模块(如 socket
或文件流)创建的。
更多node相关知识,请访问:nodejs 教程!
以上就是深入浅析Node.js中常见的内置模块的详细内容,更多请关注9543建站博客其它相关文章!
发表评论