Node.js入门 | 青训营笔记

117 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天

应用场景

前端工程化

  • Bundle 打包:webpack, vite, esbuild, parcel
  • Uglify 压缩:uglifyjs
  • Transpile:bablejs, typescript
  • 其他语言加入竞争:esbuild, parcel, prisma
  • 现状:难以替代

Web服务端应用

  • 学习曲线平缓,开发效率较高(js已经比较熟悉)
  • 运行效率接近常见的编译语言(js无需编译环境)
  • 社区生态丰富及工具链成熟(npm, V8 inspector)
  • 与前端结合的场景有优势(SSR)
  • 现状:竞争激烈,Node.js有自己独特的优势

Electron跨端桌面应用

  • 商业应用:vscode, slack, discord, zoom
  • 大型公司内的效率工具
  • 现状:大部分场景在选型时,都值得考虑

Node.js在字节的应用

  • BFF应用、SSR应用,如Modern.js
  • 服务端应用,如头条搜索,西瓜视频,懂车帝PC站点
  • Electron应用,如飞书

运行结构

运行步骤

  1. V8引擎解析JavaScript脚本。
  2. 脚本调用Node API。
  3. libuv库执行Node API。
  4. V8引擎再将结果返回给用户。

V8 引擎:由 Google 开源高性能 Javascript 和 WebAssembly 引擎,使用 c++ 编写,用于 Chrome 和 Nodejs。

libuv:一个用 C 编写的支持多平台的异步 I/O 库,主要解决 I/O 操作容易引起阻塞的问题。

特点

  • 异步I/O:在响应返回后恢复操作,不阻塞线程也不占用额外内存
  • 单线程:JS单线程,实际上还是有其它线程的(如uv, v8线程池)
    • 优点:不用考虑多线程的同步问题,资源利用效率较高
    • 缺点:发生阻塞时,会产生等多负面影响
  • 跨平台:Node.js跨平台 + JS无需编译环境 + Web跨平台 + 诊断工具跨平台
    • 大部分场景无需考虑跨屏他问题,学习和开发成本低

编写Http Server

安装Node.js

Server 和 Client

简单的Hello World

 const http = require('http')
 const port = 8848
 const server = http.createServer((req, res)=>{
     res.end('hello')
 })
 server.listen(port, ()=>{
     console.log(`server listen on ${port}`)
 })

服务端Server

定义缓冲区,存储接收到的json数据msg,再发送出来,如果没有接收到,则返回‘hello’

 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', () => {
         // Buffer.concat()将给定数组中的所有缓冲区对象合并为一个缓冲区对象
         const buf = Buffer.concat(bufs).toString('utf8')
         let msg = 'hello'
         try{
             const ret = JSON.parse(buf)
             msg = ret.msg
         } catch(err){
             // res.end('invalid json')
         }
         const resJOSN = {
             msg: `receive: ${msg}`
         }
         res.setHeader('Content-Type','application/json')
         res.end(JSON.stringify(resJOSN))
     })
 })
 server.listen(port, () => {
     console.log(`server listen on ${port}`)
 })

客户端client

发送一个POST请求,内容为json数据,并在后台显示内容

 const http = require('http')
 const body = JSON.stringify({
     msg: 'Hello from my own client'
 })
 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 buf = Buffer.concat(bufs)
         const resJSON = JSON.parse(buf)
         console.log(`json.msg is: ${resJSON.msg}`)
     })
 })
 req.end(body)

优化

  • 以上的例子使用了很多的回调函数,回调函数不好维护,嵌套的函数关系不易理解
  • 技巧:将callback转换成Promise (只适用于只调用一次的函数) + async await
  • 如服务端,将其拆分成接收数据(异步)和回应数据两部分
 const http = require('http')
 const port = 3000
 const server = http.createServer(async (req, res) => {
     // receive
     const msg = await new Promise((resolve, reject) => {
         const bufs = []
         req.on('data', buf => {
             bufs.push(buf)
         })
         req.on('error', err =>{
             reject(err)
         })
         req.on('end', () => {
             const buf = Buffer.concat(bufs).toString('utf8')
             let msg = 'hello'
             try {
                 const ret = JSON.parse(buf)
                 msg = ret.msg
             } catch (err) {
                 // res.end('invalid json')
             }
             resolve(msg)
         })
     })
     // resopnse
     const resJOSN = {
         msg: `receive: ${msg}`
     }
     res.setHeader('Content-Type', 'application/json')
     res.end(JSON.stringify(resJOSN))
 })
 server.listen(port, () => {
     console.log(`server listen on ${port}`)
 })

静态文件服务器

写一个服务,接收请求,获取其中的URL,该URL对应磁盘中的文件路径,依照路径找到对应的文件,并将文件的内容返回给请求。

 const http = require('http')
 const fs = require('fs')
 const path = require('path')
 const url = require('url')
 const port = 3000
 // find folder
 const folderPath = path.resolve(__dirname, './static')
 const server = http.createServer((req, res)=>{
     // expected http://127.0.0.1:3000/index.html
     const info = url.parse(req.url)
     // static/index.html
     const filepath = path.resolve(folderPath, './' + info.path)
     // stream API is better
     const filestream = fs.createReadStream(filepath)
     filestream.pipe(res)
     // type
     //...
 })
 server.listen(port, ()=>{
     console.log(`server listen on ${port}`)
 })

若想获取高性能的可靠服务器,还需要什么?

  • CDN:缓存和加速
  • 分布式存储,容灾机制
  • 考虑外部服务,如cloudflare,阿里云,七牛云,火山云等

React SSR

SSR(server side rendering) 服务端渲染

  • 相比传统的HTML引擎,能避免重复编写代码
  • 相比SPA(single page application),不需要等待所有js加载完成,首屏渲染更快
  • 缺点:qps(Queries Per Second) 每秒查询率较低

难点

  • 需要处理打包代码
  • 需要考虑前端代码在服务端运行时的逻辑
  • 需要移除对服务端无意义的副作用

调试与部署

V8 Inspector

  • 开箱即用,特性丰富强大,跨平台
  • 使用场景:console.log,断点,性能分析等

部署要解决的问题

  • 守护进程:退出和重启
  • 多进程利用
  • 记录状态,用于诊断