这是我参与「第五届青训营」伴学笔记创作活动的第 7 天
7.1 Node.js的应用场景
- 前端工程化(基于Node.js开发的工具大量出现)
- Web服务端应用
- Electron跨端桌面应用(VSCode就是基于它实现的)
前端工程化
- Bundle:打包工具webpack, vite
- Uglify:代码优化(压缩)
- Transpile:代码转换babeljs, ts
- 其它语言加入竞争:esbuild, parcel, prisma
- 现状:Node.js难以替代
Web服务端应用
- 学习曲线平缓,开发效率较高
- 运行效率接近常见的编译语言(不需要编译环境)
- 社区生态丰富(npm),工具链成熟(V8 inspector就是前端的F12开发者工具)
- 与前端结合的场景有优势(SSR)
- 现状:竞争激烈,Node.js有自己的优势
Electron跨端桌面应用
- 商业应用
- 大型公司的效率工具
- 现状:大部分场景选型时值得考虑
Node.js在字节
- BFF应用:谁需要接口,谁去开发;后端不再需要和前端结合那么紧密
- SSR应用:服务端渲染
- 服务端应用:后端直接用Node.js
- Electron:飞书飞连
- Node.js在字节的生态:健康发展
7.2 Node.js运行时结构
- npm外部包代码,acron,node-inspect,用户代码
- Node.js核心代码(JS),N-API
- Node.js核心代码(底层C++)
- V8引擎JS运行时,libuv操作系统api,nghttp2,zlib压缩和解压算法,c-ares查询DNS,llhttp协议解析,OpenSSL网络层加解密……
V8, libuv
- V8:JS运行时,诊断调试工具
- libuv:事件循环、系统调用
- 举例:node-fetch发起请求
- 先安装到用户代码处
- 调用时会在V8运行
- 调用底层JS模块和C++模块
- 数据通过libuv和llhttp传输
特点
- 异步IO
- 执行I/O操作的时候,在响应返回之后恢复操作,而不是阻塞线程等待
- 当前线程可以调用其它的东西了,效率更高
- 单线程
- JS主线程是单线程,实际上还有其它线程(uv线程池,V8任务线程池,V8 inspector线程)
- 优点:不用考虑多线程状态同步问题(不需要锁),比较高效地利用系统资源
- 缺点:阻塞会产生更多的负面影响(用多进程/多线程解决)
- 跨平台
- 大部分功能和api可以跨平台
- Node.js跨平台+JS无需编译环境+Web工具跨平台+诊断工具跨平台
- 开发成本低,学习成本低
7.3 编写Http Server
其实是举个例子
7.3.0 安装Node.js
- Mac, Linux推荐nvm,多版本管理
- WIndows推荐nvm4w或官方安装包
设置安装源
NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node nvm install 16
7.3.1 编写http server
一个简单的server,返回一个helloworld
const http = require('http')
const port = 3000
const server = http.createServer((req, res) => {
res.end('hello')
})
server.listen(port, () => {
console.log(`server listening on port ${port}`)
})
运行:node http_server.js
取出request内容的一个server
const http = require('http')
const port = 3000
const server = http.createServer((req, res) => {
const bufs = [] //缓冲区
req.on('data', (buf) => {
bufs.push(buf) //读取数据存到缓冲区
})
req.on('end', () => {
const buff = Buffer.concat(bufs).toString('utf-8') //拼接并转换成字符串
try {
const ret = JSON.parse(buff) //JSON解析
const msg = ret.msg || 'Hello'
const responseJSON = {
msg: `reveived: ${msg}`
}
res.setHeader('Content-Type', 'application/json') //添加头
res.end(JSON.stringify(responseJSON)) // 序列化
} catch (error) {
res.end('Invalid JSON !')
}
})
})
server.listen(port, () => {
console.log(`server is listening on port ${port}`)
})
这段代码实现了一个监听在3000端口的服务器,它会读取请求,并将msg(如有)传回,如果没有msg则传回hello。回传的数据格式为JSON
7.3.2 编写http client
const http = require('http')
const msg = JSON.stringify({
msg: 'Test message.',
})
const req = http.request('http://127.0.0.1:3000', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
}, (res) => {
const bufs = []
res.on('data', (buf) => {
bufs.push(buf)
})
res.on('end', () => {
const buff = Buffer.concat(bufs)
const json = JSON.parse(buff)
console.log(`get message: ${json}`)
})
}) //创建请求以及对于响应的回调
req.end(msg)
这段代码的客户端向服务端发送了一个请求,body是一个键值对(msg),受到服务器的响应之后会将其打印在控制台上
7.3.3 通过Promise重构
技巧:callback转换为Promise
function wait(t) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, t)
})
}
上面的代码将定时器写成了promise形式,在结束后调用的事件写在.then里面
promise适合调用一次的函数
改写思路:
- createServer的参数函数写成async
- 在里面,将对于返回数据的处理拆到后面
- 对于请求的处理用await Promise包住,resolve发生在成功读取信息,reject发生在处理异常
7.3.4 编写一个静态文件服务器
const http = require('http')
const fs = require('fs')
const path = require('path')
const url = require('url')
const folderPath = path.resolve(__dirname, './static')
const server = http.createServer((req, res) => {
const info = url.parse(req.url)
const filepath = path.resolve(folderPath, './' + info.path)
const filestream = fs.createReadStream(filepath)
filestream.pipe(res)
})
const port = 3000
server.listen(port, () => {
console.log(`server listening on ${port}`)
})
这个例子中,index.html存放在static文件夹下,用fs和path寻找到文件,使用文件流将HTML文件输出
7.3.5 React SSR
- SSR相比传统HTML模板引擎,避免重复写代码
- 相比SPA(single page application),首屏渲染更快,SEO友好
- 缺点:通常qps较低,前端代码编写时需要考虑服务端渲染情况
需要使用React, ReactDOM
难点:
- 需要处理打包代码
- 需要思考前端代码在服务端运行时的逻辑
- 移除对服务端无意义的副作用或重置环境
7.3.6 调试Debug
围绕V8 Inspector
启用:node --inspect test.js
场景:
- 查看console.log
- breakpoint
- 高CPU、死循环:cpuprofile
- 高内存占用:memory面板
- 性能分析
7.3.7 部署
要解决的问题:
- 守护进程:退出的时候拉起
- 多进程
- 记录状态便于诊断
容器环境:通常有健康检查手段
7.4 延伸话题
Node.js贡献代码
- 从使用者角色逐步理解底层细节,解决更复杂的问题
- 助于职业发展
- 解决社区问题,促进社区发展
- 比较花时间
编译Node.js
- 黑盒到白盒
诊断/追踪
- 低频,重要,有挑战
- 技术咨询行业的热门角色
- 需要了解Node.js底层,操作系统及工具
- 需要经验
WASM, NAPI
- Node.js支持WASM
- NAPI执行C接口代码,同时保留原生代码的性能
- 不同编程语言通信的一种方案