Node调试和常用模块

使用 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)
}

第二个参数描述:

  1. r+ 打开文件用于读写

  2. w+ 打开文件用于读写,将流定位到文件的开头。如果文件不存在则创建文件。

  3. a 打开文件用于写入,将流定位到文件的末尾。如果文件不存在则创建文件。

  4. 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 文件创建时间。 创建文件时设置一次。 在创建时间不可用的文件系统上,该字段可能改为保存 ctime1970-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+aa+

追加到文件

异步追加

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()

虽然 allocallocUnsafe 均分配指定大小的 Buffer(以字节为单位),但是 alloc 创建的 Buffer 会被使用 00 进行初始化,而 allocUnsafe 创建的 Buffer 不会被初始化。 这意味着,尽管 allocUnsafealloc 要快得多,但是分配的内存片段可能包含可能敏感的旧数据。

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,返回 BELE

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()返回包含当前 usernameuidgidshellhomedir 的对象。

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 服务

创建数字证书

  1. 生成 1024 长的 ca 私钥文件
openssl genrsa -out ca.key 1024
  1. 生成 CSR 文件
openssl req -new -key ca.key -out ca.csr
  1. 生成 crt 证书
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt

创建服务器证书

  1. 生成 1024 长的 ca 私钥文件
openssl genrsa -out server.key 1024
  1. 生成 CSR 文件
openssl req -new -key server.key -out server.csr
  1. 生成签名证书
openssl x509 -req -CA ../ca/ca.crt -CAkey ../ca/ca.key -CAcreateserial -in server.csr -out server.crt

创建客户端证书

  1. 生成 1024 长的私钥文件
openssl genrsa -out client.key 1024
  1. 生成 CSR 文件
openssl req -new -key client.key -out client.csr
  1. 生成签名证书
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')
})
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容