应用场景
1. 前端工程化
我们最早期写前端页面的时候,往往一个页面就是一个文件,HTML、CSS、JS都写在一起,没有吧结构层、表现层和行为层分离。随着前端工程的复杂化,我们需要考虑多人协作、项目维护、开发效率的问题。所以,前端工程化的主要作用就是将前端项目当成系统工程进行分析、组织和构建,从而达到项目结构清晰、分工明确、团队配合默契、开发效率提高的目的。
有许多前端工程化相关的库都是由node.js写的,比如说连接js依赖文件的bundle.js
,压缩js的uglify.js
,转换js模块格式的Transpile.js
等等。
2. web服务端应用
Node开发web服务端应用,也就是后端服务。服务端应用需要接受客户端的请求(request),并对请求做出响应(response)。
特点:
- 学习曲线平缓,开发效率较高
- 运行效率接近常见的编译语言
- 社区生态丰富,工具链成熟(npm,V8inspector)
- 与前端相结合的场景会有优势(SSR)
SSR:服务端渲染。SSR通过将组件或页面通过服务器生成html字符串,再发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应用程序。渲染时请求页面,返回的body里已经存在服务器生成的html结构,之后只需要结合css显示出来。这就节省了访问时间和优化了资源的请求。
SSR能够更有利于网站的SEO(搜索引擎优化)和首屏页面的渲染。
-
Electron 跨端桌面应用
Electron现在所开发的商业应用有:vscode,slack,discord,zoom。这种商业应用一般是大型公司内的效率工具。大部分场景在选型时都值得考虑通过node.js开发桌面应用。总体来说,现阶段还有更多其它语言加入竞争,但node.js从生态和效率上都有其独特的优势,难以被替代。
-
node.js在字节
- BFF应用、SSR应用:Modern.js
- 服务端应用:头条搜索,西瓜视频,懂车帝
- Electron应用:飞连,飞书
- 每年新增1000+Node.js应用
Node.js运行时结构
V8 inspector在上面有讲过,是一个node服务工具,能够查看JavaScript Runtime和诊断调试页面。
libuv是一个跨平台聚焦于异步I/O的库,event-loop(事件调用)和syscall(系统调用)就源自于libuv。
举例:当node-fetch发起请求时,
用户代码-> V8 -> Node.jsCore(JavaScript) -> Node.js Core(C++) -> HTTP传输-> Node.js Core(JavaScript) -> 用户代码
- 特点:
- 异步I/O
setTimeout(()=>{
console.log('B')
})
console.log('A')
- 单线程
JS单线程实际上是:JS线程+uv线程池+V8任务线程池+V8 Inspector线程
在单线程中,我们不需要考虑多线程状态同步问题,也就不需要锁;能够更高效地利用系统资源。
但阻塞时会产生更多负面影响,有些场景下还是需要多进程或多线程。
function fibonacci(num: number): number {
if(num===1 || num===2){
return 1
}
return fibonacci(num-1)+fibonacci(num-2)
}
console.log(fibonacci(42))
console.log(fibonacci(43))
- 跨平台
Node.js大部分功能和API都是跨平台的。
通过node.js跨平台+JS无需编译环境(+Web跨平台+诊断工具跨平台),开发成本较低,学习成本低。
const net = require('net')
const socket = new net.Socket('/tmp/socket.sock')
编写Http Server
const http = require('http')
const port = 3000
const server = http.createServer((req,res)=>{
res.end('hello')
})
server.listen(port,()=>{
console.log(`ser listens on:${port}`)
})
- JSON
const http = require('http')
const port = 3000
const server = http.createServer((req, 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) {
console.error('err!')
}
res.setHeader('Content-Type', 'application/json')
res.end(
JSON.stringify({
echo: reqData.msg || 'Hello',
})
)
})
})
server.listen(port, () => {
console.log(`ser listens on:${port}`)
})
- 静态文件处理
const http = require('http')
const fs = require('fs')
const url = require('url')
const path = require('path')
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},${path}`)
})
- React SSR
const http = require('http')
const React = require('react')
const ReactDOMServer = require('react-dom/server')
function App() {
return React.createElement('h1', {
children: 'Hello',
})
}
const port = 3000
const server = http.createServer((req, res) => {
res.setHeader('Content-Type', 'text/html')
res.end(`
<!DOCTYPE html>
<html lang="en">
<head>
<title>My App</title>
</head>
<body>
<div id="main">
${ReactDOMServer.renderToString(React.createElement(App))}
</body>
</html>
`)
})
server.listen(port, () => {
console.log(`server listens on: ${port}`)
})
SSR难点:
- 需要处理打包代码;
- 需要思考前端代码在服务端运行时的逻辑;
- 移除对服务端无意义的副作用,或重置环境。
- Debug
V8 Inspector是一个调试协议,为用户和嵌入者提供了广泛的调试功能。 - 特点:
- 开箱即用
- 特性丰富强大
- 与前端开发一致
- 跨平台
- 场景
- 查看console.log内容
- breakpoint
- 高CPU、死循环: cpuprofile
- 高内存占用:heapsnapshot
- 性能分析
Node --inspect
Open http://localhost:9229/json
- 部署
部署要解决的问题:
-
守护进程:当进程退出时,重新拉起
-
多进程:cluster便捷地利用多进程
-
记录进程状态,用于诊断
容器环境 -
通常由健康检查的手段,只需要考虑多核CPU利用率即可
延伸话题
- 贡献代码
- 编译Node.js
- 诊断/追踪
- WASM,NAPI
参考资料:
https://zhuanlan.zhihu.com/p/92305600
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch