这是我参与「第四届青训营 」笔记创作活动的第9天
Node.js的应用场景
优点
-
作为不需要编译的脚本语言,运行效率很快
-
社区生态很丰富,可以大大减少开发一个web应用的时间
-
前端工程化
- Bundle(构建):webpack,vite,esbuild,parcel
- Uglify:uglifyjs
- Transpile(语法转换):bablejs,typescript
- 其他语言加入竞争:esbuild,parcel,prisma
- 现状:难以替代
-
Web服务端应用
- 学习曲线平缓,开发效率较高
- 运行效率接近常见的编译语言
- 社区生态丰富工具链成熟(npm,V8 inspector)
- 与前端结合的场景会有优势(SSR)
- 现状:竞争激烈,Node.js有自己独特的优势
-
Electron跨端桌面应用
- 商业应用:vscode,slack,discord,zoom
- 大型公司内的效率工具
- 现状:大部分场景在选型的时候都值得考虑
-
Node.js在字节
- BFF应用、SSR应用,距离:Modern.js
- 服务端应用:头条搜索,西瓜视频,懂车帝
- Electron应用:飞连,飞书
- 每年新增1000+Node.js应用
Node.js运行时结构
异步I/O
场景分析:
当调用异步API(fs.readFile())读取文件时,会把读取文件的操作交给libuv的线程池去做,此时就不用等到文件读取完,就可以做其他调用,之后文件读取的操作从node线程处理完返回给主线程执行回调,然后在做其他事情。那么异步I/o的好处是什么呢?、
异步I/O好处: 当Node.js执行I/O操作时,会在响应返回后恢复操作,而不是阻塞线程并占用额外的内存等待
单线程
-
js单线程
- 实际:JS线程+ uv线程 +V8任务线程池 + V8 Inspector线程
-
优点:不用考虑多线程状态同步问题,也就不需要锁🔒;同时还能比较有效的利用系统资源
-
缺点:阻塞会产生更多的负面影响
- 解决问题:多线程或多进程
跨平台
-
跨平台(大部分功能,api)
-
Node.js跨平台+JS无需编译环境(+web跨平台+诊断工具跨平台)
- = 开发成本低(大部分场景无需担心跨平台问题),整体学习成本低
编写Http Server
- json
//创建server
const server = http.createServer((req,res)=>{
const bufs=[]
//从request取出body,绑定data事件,当连接返回数据收集起来
req.on('data',(buf)=>{
bufs.push(buf)
})
req.on('end',()=>{
//bufs
const buf = Buffer.concat(bufs).toString('utf8')
let msg='hello'
try{
const ret = JSON.parse(buf)
msg = ret.msg
}catch(err){
//如果用户没有传给我们json数据,返回该msg
}
const responseJson = {
msg: `receive:${msg}`
}
res.setHeader('Content-Type','application/json')
//序列化返回json
res.end(JSON.stringify(responseJson))
})
})
//监听端口(3000)
const port = 3000
server.listen(port,()=>{
//成功监听,触发回调函数
console.log(`server listens on: ${port}`)
})
这里实际没有给浏览器发送json数据
- Client
const body = JSON.stringify({msg:'hello from my client'})
const req = http.request('http://localhost: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 buf=Buffer.concat(bufs)
const json = JSON.parse(buf)
console.log('json.msg is:',json.msg)
})
})
req.end(body)
回调函数不容易维护和管理,不好确定回调函数在什么时候触发 ——>用Pronise+async await重写这两个例子 技巧:将callback转换成promise
//创建server
const server = http.createServer(async(req,res)=>{
//接收body数据
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',()=>{
//bufs
const buf = Buffer.concat(bufs).toString('utf8')
let msg = 'hello'
try{
const ret = JSON.parse(buf)
msg = ret.msg
}catch(err){
//如果用户没有传给我们json数据,返回该内容
//res.end('invalid json')
}
resolve(msg)
})
})
//根据client数据,返回服务端数据
const responseJson = {
msg: `receive:${msg}`
}
res.setHeader('Content-Type','application/json')
//序列化返回json
res.end(JSON.stringify(responseJson))
})
//监听端口(3000)
const port = 3000
server.listen(port,()=>{
//成功监听,触发回调函数
console.log(`server listens on: ${port}`)
})
-
静态文件
-
静态文件服务
-
与高性能可靠的服务相比还差什么
- CDN:缓存+加速
- 分布式存储,容灾
-
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)=>{
//期望收到http://127.0.0.1:3000/index.html?abc=10
const info = url.parse(req.url)
//static/index.html
const filepath=path.resolve(folderPath,'./'+info.path)
//stream风格api的好处:内部做了处理,占用尽可能少的空间,按需返回给client
const filestream = fs.createReadStream(filepath)
filestream.pipe(res)
})
const port = 3000
server.listen(port,()=>{
//成功监听,触发回调函数
console.log(`server listens on: ${port}`)
})
-
React SSR
-
SSR(server side rendering)特点
- 相比传统html引擎:避免重复编写代码
- 相比SPA(single page application):首屏渲染更快,SEO友好
- 缺点;通常qps较低,前端编写时需要考虑服务端渲染情况
-
SSR难点
- 需要处理打包代码
- 需要思考前端代码在服务端运行的逻辑
- 移除服务端无意义的副作用,或充值环境
-
const React = require('react')
const ReactDOMServer = require('react-dom/server')
const http = require('http')
function App(props){
return React.createElement('div',{},props.children|| 'hello')
}
const server = http.createServer((req,res)=>{
res.end(
`
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
${ReactDOMServer.renderToString(React.createElement(App,{},'my_content'))}
<script>
//初始化React应用
</script>
</body>
</html>
`
)
})
const port = 3000
server.listen(port,()=>{
//成功监听,触发回调函数
console.log(`server listens on: ${port}`)
})