使用 Chrome DevTool 调试 Node
运行带有 --inspect-brk 标志的 node
启动时在 node 后面加上 --inspect-brk
标志,Node.js 将监听调试客户端,默认情况下监听在 127.0.0.1:9229 地址,也可以显式指定地址 --inspect-brk=_host:port_
node --inspect-brk index.js
node --inspect 与 node --inspect-brk 的区别:--inspect 不会中断,--inspect-brk 在用户代码启动之前会中断,也就是代码在第一行就会暂停执行。
chrome 浏览器打开 chrome://inspect/
fs
获取文件描述符
异步获取
fs.open('./a.txt', 'r', (err, fd) => {
// 文件描述符
console.log(fd)
})
同步获取
try {
const fd = fs.openSync('./a.txt', 'r')
console.log(fd)
} catch (err) {
console.log(err)
}
第二个参数描述:
r+ 打开文件用于读写
w+ 打开文件用于读写,将流定位到文件的开头。如果文件不存在则创建文件。
a 打开文件用于写入,将流定位到文件的末尾。如果文件不存在则创建文件。
a+ 打开文件用于读写,将流定位到文件的末尾。如果文件不存在则创建文件。
获取文件的属性
fs.stat
返回stat
对象该对象提供了关于文件的很多信息,例如文件大小、创建时间等。其中有两个方法 stats.isDirectory()
、stats.isFile()
用来判断是否是一个目录、是否是一个文件。
异步获取
fs.stat('./a.txt', (err, stats) => {
console.log(stats)
})
同步获取
try {
const stats = fs.statSync('./a.txt')
console.log(stats)
} catch (err) {
console.log(err)
}
文件的信息
stats.size
获取文件的大小(以字节为单位)atime
上次访问文件数据的时间mtime
上次修改文件数据的时间ctime
上次更改文件状态(修改索引节点数据)的时间birthtime
文件创建时间。 创建文件时设置一次。 在创建时间不可用的文件系统上,该字段可能改为保存ctime
或1970-01-01T00:00Z
(即 Unix 纪元时间戳0
)stats.isFile()
判断是否是文件stats.isDirectory()
判断文件是否目录stats.isSymbolicLink()
判断文件是否符号链接
读取文件
异步读取
fs.readFile('./test.txt', 'utf8', (err, data) => {
if (err) {
console.error(err)
return
}
console.log(data)
})
同步读取
try {
const data = fs.readFileSync('./test.txt', 'utf8')
console.log(data)
} catch (err) {
console.error(err)
}
fs.readFile()
和 fs.readFileSync()
都会在返回数据之前将文件的全部内容读取到内存中。这意味着大文件会对内存的消耗和程序执行的速度产生重大的影响。在这种情况下,更好的选择是使用流来读取文件的内容。
若只是检查文件是否存在,推荐使用 fs.access
写入文件
异步写入
fs.writeFile('./a.txt', content, err => {
if (err) {
console.log(err)
return
}
console.log('success')
})
同步写入
try {
fs.writeFileSync('./a.txt', content)
console.log('success')
} catch (e) {
console.log(e)
}
以上默认情况下会替换文件内容。
可以通过指定标志来修改默认行为:r+
、w+
、a
、a+
追加到文件
异步追加
const content = '一些内容'
fs.appendFile('a.txt', content, err => {
if (err) {
console.error(err)
return
}
})
同步追加
try {
fs.appendFileSync('./a.txt', content)
} catch (e) {
console.log(e)
}
所有这些方法也是在将全部内容写入文件之后才会返回。
在这种情况下,更好的选择是使用流写入文件的内容。
检查文件是否存在且是否具有访问权限
检查 path
指定的文件或目录的权限。
fs.access(path[, mode], callback)
,mode
参数是可选的整数,指定要执行的可访问性检查。
const { access, constants } = require('fs')
const file = 'package.json'
// 检查当前目录中是否存在该文件。
access(file, constants.F_OK, err => {
console.log(`${file} ${err ? 'does not exist' : 'exists'}`)
})
// 检查文件是否可读。
access(file, constants.R_OK, err => {
console.log(`${file} ${err ? 'is not readable' : 'is readable'}`)
})
// 检查文件是否可写。
access(file, constants.W_OK, err => {
console.log(`${file} ${err ? 'is not writable' : 'is writable'}`)
})
// 检查当前目录中是否存在文件,是否可写。
access(file, constants.F_OK | fs.constants.W_OK, err => {
if (err) {
console.error(
`${file} ${err.code === 'ENOENT' ? 'does not exist' : 'is read-only'}`
)
} else {
console.log(`${file} exists, and it is writable`)
}
})
在调用 fs.open()
、fs.readFile()
或 fs.writeFile()
之前,不要使用 fs.access()
检查文件的可访问性。 这样做会引入竞争条件,因为其他进程可能会在两次调用之间更改文件的状态。 而是直接打开/读取/写入文件,并处理无法访问文件时引发的错误。
判断路径是否存在
如果路径存在则返回 true
,否则返回 false
fs.existsSync(path)
文件夹
创建文件夹
fs.mkdir('../views', err => {})
fs.mkdirSync('../views')
读取目录的内容
fs.readdir('../dist', err => {}) // [ 'img', 'main.js' ]
try { fs.readdirSync('../dist')} catch(e) { //}
也可以过滤结果仅返回文件(排除文件夹)
const folderPath = '../dist'
const files = fs
.readdirSync(folderPath)
.map(fileName => path.join(folderPath, fileName))
.filter(url => fs.lstatSync(url).isFile())
console.log(files)
重命名文件夹
fs.rename('/Users/joe', '/Users/roger', err => {
if (err) {
console.error(err)
return
}
})
try {
fs.renameSync('/Users/joe', '/Users/roger')
} catch (err) {
console.error(err)
}
删除文件夹
使用 fs.rmdir()
或 fs.rmdirSync()
删除文件夹,删除包含内容的文件夹可能会更复杂。推荐使用 fs-extra
模块。
安装 fs-extra
npm i fs-extra
const fs = require('fs-extra')
const folder = '/child/test'
fs.remove(folder, err => {
console.error(err)
})
与 promise 一起使用:
fs.remove(folder)
.then(() => {
// done
})
.catch(err => {
console.error(err)
})
使用 async/await:
async function removeFolder(folder) {
try {
await fs.remove(folder)
//完成
} catch (err) {
console.error(err)
}
}
const folder = '/child/test'
removeFolder(folder)
逐行读取
const readline = require('readline')
const fs = require('fs')
const rl = readline.createInterface({
input: fs.createReadStream('./sample.txt')
})
rl.on('line', line => {
console.log(`Line from file: ${line}`)
})
Buffer
什么是 Buffer
V8 引擎外部分配的固定大小的内存区域。
Buffer 被引入用以帮助开发者处理二进制数据,在此生态系统中传统上只处理字符串而不是二进制数据。
Buffer 与流紧密相连。 当流处理器接收数据的速度快于其消化的速度时,则会将数据放入 buffer 中。
如何创建 Buffer
- Buffer.from()
- Buffer.alloc()
- Buffer.allocUnsafe()
虽然 alloc
和 allocUnsafe
均分配指定大小的 Buffer
(以字节为单位),但是 alloc
创建的 Buffer
会被使用 00
进行初始化,而 allocUnsafe
创建的 Buffer
不会被初始化。 这意味着,尽管 allocUnsafe
比 alloc
要快得多,但是分配的内存片段可能包含可能敏感的旧数据。
当 Buffer
内存被读取时,如果内存中存在较旧的数据,则可以被访问或泄漏。 这就是真正使 allocUnsafe
不安全的原因,在使用它时必须格外小心。
使用 Buffer
访问 Buffer 的内容
const buf = Buffer.from('Hey!')
console.log(buf[0]) //72
console.log(buf[1]) //101
console.log(buf[2]) //121
这些数字是 Unicode 码,用于标识 buffer 位置中的字符(H => 72、e => 101、y => 121)
使用 toString()
打印 Buffer 的内容。
获取 Buffer 的长度
Buffer.from('hello').length // 5
迭代 Buffer 的内容
const buf = Buffer.from('Hey!')
for (const item of buf) {
console.log(item) //72 101 121 33
}
更改 Buffer 的内容
使用 write
将整个数据字符串写入 buffer
const buf = Buffer.alloc(4)
buf.write('Hey!')
buf // <Buffer 48 65 79 21>
也可以像修改数组一样:
const buf = Buffer.from('Hey!')
buf[1] = 111 //o
console.log(buf.toString()) //Hoy!
复制 Buffer
使用 copy()
复制 Buffer, 默认情况下,会复制整个 buffer。
const buf = Buffer.from('Hey!')
let bufcopy = Buffer.alloc(4) // 分配 4 个字节
buf.copy(bufcopy)
还可以接收另外三个参数:buffer.copy(target[, targetStart[, sourceStart[, sourceEnd]]])
- target: 目标 Buffer
- targetStart:
target
中开始写入之前要跳过的字节数。默认值:0
- sourceStart:
buf
中开始拷贝的偏移量。默认值:0
- sourceEnd:
buf
中结束拷贝的偏移量(不包含)。默认值: buf.length
切片 Buffer
如果要创建 buffer 的局部视图,则可以创建切片。 切片不是副本:原始 buffer 仍然是真正的来源。 如果那改变了,则切片也会改变。
使用 slice()
方法创建它。 第一个参数是起始位置,可以指定第二个参数作为结束位置:
const buf = Buffer.from('Hey!')
buf.slice(0).toString() //Hey!
const slice = buf.slice(0, 2)
console.log(slice.toString()) //He
buf[1] = 111 //o
console.log(slice.toString()) //Ho
OS
os.arch()
标识底层架构的字符串
os.arch() // x64 arm arm64
os.cups()
cpu 信息
os.cups()
/**
[
{
model: 'Intel(R) Core(TM) i5-8600 CPU @ 3.10GHz',
speed: 3100,
times: { user: 5648300, nice: 0, sys: 3620210, idle: 81109530, irq: 0 }
},
{
model: 'Intel(R) Core(TM) i5-8600 CPU @ 3.10GHz',
speed: 3100,
times: { user: 4415850, nice: 0, sys: 2205430, idle: 83755910, irq: 0 }
},
{
model: 'Intel(R) Core(TM) i5-8600 CPU @ 3.10GHz',
speed: 3100,
times: { user: 3647530, nice: 0, sys: 1772620, idle: 84957020, irq: 0 }
},
{
model: 'Intel(R) Core(TM) i5-8600 CPU @ 3.10GHz',
speed: 3100,
times: { user: 3099430, nice: 0, sys: 1472320, idle: 85805410, irq: 0 }
},
{
model: 'Intel(R) Core(TM) i5-8600 CPU @ 3.10GHz',
speed: 3100,
times: { user: 2495760, nice: 0, sys: 1157880, idle: 86723510, irq: 0 }
},
{
model: 'Intel(R) Core(TM) i5-8600 CPU @ 3.10GHz',
speed: 3100,
times: { user: 2048720, nice: 0, sys: 912180, idle: 87416240, irq: 0 }
}
]
*/
os.endianness()
根据是使用[大端序或小端序]编译 Node.js,返回 BE
或 LE
。
os.freemem()
返回代表系统中可用内存的字节数。
`os.homedir() 返回到当前用户的主目录的路径。
'/Users/joe'
os.hostname()
返回主机名。
os.loadavg()
返回操作系统对平均负载的计算。
这仅在 Linux 和 macOS 上返回有意义的值。
例如:
;[3.68798828125, 4.00244140625, 11.1181640625]
os.networkInterfaces()
返回系统上可用的网络接口的详细信息。
例如:
{ lo0:
[ { address: '127.0.0.1',
netmask: '255.0.0.0',
family: 'IPv4',
mac: 'fe:82:00:00:00:00',
internal: true },
{ address: '::1',
netmask: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
family: 'IPv6',
mac: 'fe:82:00:00:00:00',
scopeid: 0,
internal: true },
{ address: 'fe80::1',
netmask: 'ffff:ffff:ffff:ffff::',
family: 'IPv6',
mac: 'fe:82:00:00:00:00',
scopeid: 1,
internal: true } ],
en1:
[ { address: 'fe82::9b:8282:d7e6:496e',
netmask: 'ffff:ffff:ffff:ffff::',
family: 'IPv6',
mac: '06:00:00:02:0e:00',
scopeid: 5,
internal: false },
{ address: '192.168.1.38',
netmask: '255.255.255.0',
family: 'IPv4',
mac: '06:00:00:02:0e:00',
internal: false } ],
utun0:
[ { address: 'fe80::2513:72bc:f405:61d0',
netmask: 'ffff:ffff:ffff:ffff::',
family: 'IPv6',
mac: 'fe:80:00:20:00:00',
scopeid: 8,
internal: false } ] }
os.platform()
返回为 Node.js 编译的平台:
darwin
freebsd
linux
openbsd
win32
- ...等
os.release()
返回标识操作系统版本号的字符串。
os.tmpdir()
返回指定的临时文件夹的路径。
os.totalmem()
返回表示系统中可用的总内存的字节数。
os.type()
标识操作系统:
Linux
- macOS 上为
Darwin
- Windows 上为
Windows_NT
os.uptime()
返回自上次重新启动以来计算机持续运行的秒数。
os.userInfo()
返回包含当前 username
、uid
、gid
、shell
和 homedir
的对象。
path
文件路径
从路径中获取信息
const url = '/user/mi/test.txt'
dirname
: 获取文件的父文件夹
path.dirname(url) // '/user/mi'
basename
: 获取路径的最后部分,第二个参数可以过滤掉文件的扩展名
path.basename(url) // 'test.txt'
path.basename(url, path.extname(url)) // 'test'
extname
: 获取文件的扩展名
path.extname(url) // '.txt'
path.extname('user/foo') // ''
isAbsolute()
如果是绝对路径,则返回 true
path.isAbsolute('/test/something') // true
path.isAbsolute('./test/something') // false
parse()
解析对象的路径为组成其的片段:
-
root
: 根路径。 -
dir
: 从根路径开始的文件夹路径。 -
base
: 文件名 + 扩展名 -
name
: 文件名 -
ext
: 文件扩展名
path.parse('/users/note/test.txt')
/**
{
root: '/',
dir: '/users/note',
base: 'test.txt',
ext: '.txt',
name: 'test'
}
*/
relative(path1, path2)
接受两个参数,返回第一个路径到第二个路径的相对路径
path.relative('/user/node', '/user/node/foo/bar.js') // 'foo/bar.js'
path.relative('/user/node/foo', '/user/node/bar.js') // '../bar.js'
使用路径
path.join()
连接多个片段
path.join('/', 'users', 'name', 'notes.txt')
// '/users/name/notes.txt'
path.resolve()
获取相对路径的绝对路径
path.resolve('a.txt')
// '/user/Descope/test/a.txt'
// a.txt的上级目录是程序的运行目录
path.resolve('note', 'a.txt')
// '/user/Descope/test/note/a.txt'
// 同上: note的上级目录是程序的运行目录
如果第一个参数以斜杠开头,则表示它是绝对路径:
path.resolve('/etc', 'a.txt')
// '/etc/a.txt'
path.normalize()
当包含诸如 .
、..
或双斜杠之类的相对说明符时,它会尝试计算实际的路径:
path.normalize('/users/joe/..//test.txt')
// '/users/test.txt'
解析和规范化都不会检查路径是否存在。 其只是根据获得的信息来计算路径。
process
process.stdout
返回一个对象,表示标准输出
const fs = require('fs')
fs.createReadStream('test.txt').pipe(process.stdout)
process.stdin
返回一个对象,表示标准输入
process.stdin.pipe(process.stdout)
由于 process.stdout 和 process.stdin 与其他进程的通信,都是流(stream)形式,所以必须通过 pipe 管道中转
process.argv
返回一个数组,由命令行执行脚本时的各个参数组成。第一项是 Node,第二项是脚本地址,其余是脚本文件的参数。
node test_process.js a b
[
'/Users/mifi/.nvm/versions/node/v12.18.4/bin/node',
'/Users/mifi/Desktop/event_loop/test_process.js',
'a',
'b'
]
process.execPath
返回执行当前脚本的 Node 二进制文件的绝对路径,也就是process.argv
的第一项
process.env
返回一个对象,包含了当前 Shell 的所有环境变量。比如,process.env.HOME
返回用户的主目录
通常是新建一个环境变量NODE_ENV
,确定当前所处的开发环境,生产阶段设为production
,然后在脚本中读取process.env.NODE_ENV
即可
export NODE_ENV=production && node app.js
#
NODE_ENV=production node app.js
// process.env.NODE_ENV: production
process.cwd()
返回运行当前脚本的工作目录的绝对路径
process.cwd() 和 __dirname 的区别
process.cwd()是进程执行时的位置
__dirname 是脚本的位置
process.chdir()
切换工作目录到指定目录
process.exit()
退出当前进程,如果参数大于 0,表示执行失败;如果等于 0 表示执行成功
process.nextTick()
指定回调函数在当前执行栈的尾部、下一次 Event Loop 之前执行
process.on()
process
对象部署了 EventEmitter 接口,可以使用on
方法监听各种事件,并指定回调函数
process 支持的事件:
data
事件:数据输出输入时触发
SIGINT
事件:接收到系统信号SIGINT
时触发,主要是用户按Ctrl + c
时触发
SIGTERM
事件:系统发出进程终止信号SIGTERM
时触发
exit
事件:进程退出前触发
process.kill()
对指定 ID 的线程发送信号,默认为SIGINT
信号
TLS 服务
创建数字证书
- 生成 1024 长的 ca 私钥文件
openssl genrsa -out ca.key 1024
- 生成 CSR 文件
openssl req -new -key ca.key -out ca.csr
- 生成 crt 证书
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt
创建服务器证书
- 生成 1024 长的 ca 私钥文件
openssl genrsa -out server.key 1024
- 生成 CSR 文件
openssl req -new -key server.key -out server.csr
- 生成签名证书
openssl x509 -req -CA ../ca/ca.crt -CAkey ../ca/ca.key -CAcreateserial -in server.csr -out server.crt
创建客户端证书
- 生成 1024 长的私钥文件
openssl genrsa -out client.key 1024
- 生成 CSR 文件
openssl req -new -key client.key -out client.csr
- 生成签名证书
openssl x509 -req -CA ../ca/ca.crt -CAkey ../ca/ca.key -CAcreateserial -in client.csr -out client.crt
Server
const tls = require('tls')
const fs = require('fs')
const options = {
// 私钥key
key: fs.readFileSync('./server.key'),
// crt 文件
cert: fs.readFileSync('./server.crt'),
requestCert: true,
// CA 证书
ca: [fs.readFileSync('../ca/ca.crt')]
}
tls
.createServer(options, stream => {
console.log('stream: ', stream.authorized ? 'authorized' : 'unauthorized')
stream.write('hello tls')
stream.setEncoding('utf-8')
stream.pipe(stream)
})
.listen(8000, () => {
console.log('tls run...')
})
Client
const tls = require('tls')
const fs = require('fs')
const option = {
key: fs.readFileSync('./client.key'),
cert: fs.readFileSync('./client.crt'),
// 服务端证书
ca: [fs.readFileSync('../ca/ca.crt')]
}
const stream = tls.connect(8000, option, () => {
process.stdin.pipe(stream)
})
stream.setEncoding('utf-8')
stream.on('data', chunk => {
console.log(chunk)
})
stream.on('end', () => {
console.log('end')
})