学习Node.js | 青训营笔记

81 阅读6分钟

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

一、本堂课重点内容:

本堂课的知识要点有哪些?

  • Node.js的应用场景
  • Node.js的运行结构
  • 编写Http Server
  • 延伸话题

二、详细知识点介绍:

Node.js的应用场景

Node.jsJavaScript语言的服务器运行环境

  • Node.js 就是运行在服务端的 JavaScript。
  • Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台。
  • Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。

Node.js的产生

Node.js的产生可追溯到服务器的阻塞问题:

传统服务器每次产生一个请求,就对应着一个线程。 那么就有一个问题:请求的速度由用户决定,响应的速度也可以通过提升带宽等方法比较容易的提升速度。但是I/O的输入输出速度是比较难提升的。而每一个请求就会产生一个线程,数据请求又比较慢就会出现有很多线程再缓存中等待,就会造成大量的内存浪费。所以使用Node.js编写的服务器采用单线程模式,即不管产生多少请求就只有一个线程,这样就可以节省空间,大大降低了成本。

我们常说JS是单线程的,这也与它运行在Node.js上有关。

Node.js特点

  • Node.js采用Google开发的V8引擎运行js代码,使用事件驱动、非阻塞和异步模型等技术来提高性能,可优化应用程序的传输量和规模。

  • Node.js是基于V8引擎,V8是Google发布的开源JavaScript引擎,本身就是用于Chrome浏览器的js解释部分,但是Ryan Dahl鬼才般把这个V8搬到了服务器上,用于做服务器的软件。

  • Node.js大部分基本模块都用JavaScript编写。在Node.js出现之前,JS通常作为客户端程序设计语言使用,以JS写出的程序常在用户的浏览器上运行。

  • Node.js的单线程是Node.js的一个最大的优点,但是同时也是它的缺点。

  • 因为毕竟是单线程,如果请求太多会处理不过来的,所以我们一般情况下采用分布式的方法来弥补这个缺点。分布式是指采取多个服务器,Node.js对服务器的要求不高,所以不必担心价格问题。

应用场景

  • 前端工程化

    • 早期ajax、jquery比较流行,直接在页面中引入lib即可

    • 后续模块化、transpile逐渐成熟、需求逐渐增多,对后端能力需求逐渐强烈(在浏览器外运行代码的需求)

    • Bundle: webpack, vite, esbuild, parcel

    • Uglify: uglifyjs

    • Transpile: bablejs, typescript

    • 其他语言加入竞争:esbuild, parcel, prisma

    • Node.js必不可缺,难以替代

  • Web服务端应用

    Node.js可以做到其他传统后端语言做到的事情

    • 学习曲线平缓,开发效率较高
    • 运行效率接近常见的编译语言
    • 社区生态丰富及工具链成熟
    • 与前端结合的场景会有优势
    • 现状:竞争激烈,Node.js有自己独特的优势
    • 例如:头条搜索,西瓜视频,懂车帝
  • Electron 跨桌面应用

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

    • BFF应用、SSR应用,举例:Modern.js

    • 服务端应用,例如:头条搜索,西瓜视频,懂车帝

    • Electron应用:飞连,飞书

    • 每年新增1000+ Node.js应用

Node.js运行结构

image.png

  • V8: JS Runtime, 诊断调试工具(inspector)
  • libuv:eventloop(事件循环)syscall(系统调用)

image.png

image.png

  • 和浏览器相比node.js环境中是没有Dom和Bom的
  • 出于安全问题考虑,浏览器不支持跨域请求和文件读写功能; 而Node.js没有安全限制可以直接访问到终端提供的很多的API方法:
    • 可以直接操作文件系统
    • 进行进程管理(解决单线程问题,在Node下可以创建多个进程)
    • 可以进行跨域请求

特点

  • 异步I/O:

当Node.js执行I/O操作时,会在响应返回后恢复操作,而不是阻塞线程并占用额外内存等待

  • 单线程:

JS线程 + uv线程池 + V8任务线程池 + V8 Inspector线程

优点:不用考虑多线程状态同步问题,也就不需要锁;同时比较高效地利用系统资源

缺点:阻塞会产生更多负面影响

解决办法:多进程或多线程

  • 跨平台:

跨平台(大部分功能、api)

Node.js跨平台+JS无需编译环境(+ Web 跨平台+诊断工具跨平台)

开发成本低(大部分场景无需担心跨平台问题),整体学习成本低

编写Http Server

安装 Node.js可以选择以下一种方式:

  1. 从 Node.js 官网安装 nodejs.org/en/

  2. Mac, Linux 环境可以使用 nvm 进行安装 github.com/nvm-sh/nvm

实战示例

Hello World!

const http =require('http')//导入Http模块
const port = 3000
const server = http.createServer((req,res)=>{ // 创建web服务器实例;每次接到回调请求时回调函数就被触发
res.end("hello")
})
server.listen(port,()=>{
console.log('server listens on: ${port}')
})

JSON

const server = http.createServer((reg,res)=>{
  const bufs =[]
  req.on('data', data=> { 
    bufs.push(data)
})
req.on('end',() => {
  let reqData = {}
  try { 
    reqData = JSON.parse(Buffer.concat(bufs).toString())
    } catch (err){
// receive invalid json data
}
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({
  echo: reqData.msg || 'Hello",
  ))
 })
})

client

const http = require('http')

const body = JSON.stringify({ msg: 'hello from my client' })

const req = http.request('http://127.0.0.1:3000', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Content-Length': body.length,
    }
}, (res) => {
    const bufs = []
    res.on('data', data => {
        bufs.push(data)
    })
    res.on('end', () => {
        const receive = JSON.parse(Buffer.contact(bufs).toString())
        console.log('receive:', receive)
    })
})

req.end(body)

通过promise+async await重写例子
将callback转换成promise

function wait(t) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve()
        }, t)
    })
}

wait(1000).then(() => { console.log('get called') })

静态文件服务

stream api好处:占用尽可能少的内存空间,内存使用率更高
缓存+加速,分布式储存,容灾,外部服务

static_file_server
const http = require('http')
const fs = require('fs')
const path = require('path')
const url = require('url')


const port = 3000

const server = http.createServer((req, res) => {
    const info = url.parse(req.url)
    const file = fs.createReadStream(path.resolve(__dirname, '.' + info.pathname))
    file.pipe(res)
})

server.listen(port, () => {
    console.log('server listens on: ${port}')
})

React SSR

server side rendering

  • 相比HTML模版引擎:避免重复编写代码
  • 相比Single Page Application:首屏渲染更快,SEO友好
  • 缺点:通常qps较低,前端代码编写时需要考虑服务端渲染情况

SSR难点:

1.需要处理打包代码

2.需要思考前端代码在服务端运行时的逻辑

3.移除对服务器无意义的副作用,或重置环境

Debug

V8 Inspector:开箱即用、特性丰富强大、与前端开发一致、跨平台

场景:

  • 查看console.log内容
  • breakpoint
  • 高CPU、死循环:cpuprofile
  • 高内存占用:httpsnapshot
  • 性能分析

部署

  • 守护进程:当进程退出时,重新拉起
  • 多进程:cluster便捷地利用多进程
  • 记录进程状态,用于诊断

容器环境:通常有健康检查的手段,只需考虑多核CPU利用率即可

延伸话题

Node.js贡献代码

快速了解Node.js代码

contributing-to-node-core.pdf

好处

  • 从使用者的角色逐步理解底层细节,可以解决更复杂的问题
  • 自我证明,有助于职业发展
  • 解决社区问题,促进社区发展

诊断/追踪

  • 一个低频,重要,也相当有挑战的方向,是企业衡量自己能否依赖一门语言的重要参考
  • 技术咨询行业中的热门角色

难点: 需要了解Node.js底层,需要了解操作系统以及各种工作 ,更需要经验

WASM,NAPI

Node.js是执行WASM代码的天然容器,WebAssembly/wasm WebAssembly 或者 wasm 是一个可移植、体积小、加载快并且兼容 Web 的全新格式。

NAPI是执行C接口的代码(C/C++/RUS...),同时还能保留原生代码的性能

三、引用参考:

‍‌Node.js 与前端开发实战.pptx