目录
Node.js
Node.js简介
- Node.js是一个能够在服务器端运行JavaScript的开源代码、跨平台JavaScript运行环境。
- Node是Ryan Dahl为了帮助客户解决Web服务器的高并发性能问题而写出来的。
- Node采用Google开发的V8引擎运行js代码,使用事件驱动、非阻塞和异步I/O模型等技术来提高性能,可优化应用程序的传输量和规模。
- Node.js的推出不仅从工程化的角度自动化掉更多繁琐费时的工作,更打破了前端后端的语言边界。
注释:I/O(Input/Output) I/O操作指的是对磁盘读写操作
Node.js的引用场景
1.前端工程化
再早期ajax,jQuery比较流行的时候,使用这些库我们在页面中直接引入需要的库(lib)即可,不太需要那么多服务端的能力。后续随着模块化、transpile逐渐成熟、需求逐渐增多,对后端能力的需求也逐渐强烈。反过来说,也是Node.js赋予了js开发者在浏览器外运行代码的能力了,加速催生了这些项目的出现。
- Bundle(打包工具):Webpack、vite、esbuild、parcel
- Uglify(代码压缩):uglifyjs
- Transpile(语法转换):bablejs、TypeScript
- 其他语言加入前端工程化的竞争:esbuild、parcel、prisma
- Node.js仍然处于难以替代的地位
2.Web服务端应用
- 学习曲线平缓,开发效率极高
- 运行效率接近常见的编译语言
- 社区生态丰富及工具链成熟(NPM, V8 inspector)
- 与前端结合的常见会有优势(SSR(服务端渲染))
- 现状:竞争激烈,但是Node.js有自己独特的优势
3.Electron 跨端桌面应用
- 商业应用:VsCode,slack, discord,zoom
- 大型公司内部的效率工具
- 现状:大部分场景在选型时,都值得考虑使用Node.js
Node.js 运行时结构
- 社区npm代码: acron,node-inspect(诊断调试工具),npm
- 用户代码:自己写的业务代码,还包括npm安装的包
- Node.js Core(JavaScript): 内部由JavaScript代码写的模块
- Node.js Core(C++):Node底层有更多是由C++代码去编写的
- N-API:是在一些特定的场景,通过JavaScript代码没法满足的需求,举个例子当你有一些需要性能的需求,比如某个场景用JavaScript写的代码效率比较低,所以就需要一些N-API的一系列模块来实现这些能力
- V8:JavaScript Runtime,由V8自带的工具inspector(诊断调试工具)
- libuv:封装了各类操作系统的API,还提供了Node.js最核心的eventloop(事件循环) 和 syscall(系统调用) ,它会去封装各类操作系统上的API,然后来提供一个跨平台I/O操作
- nghttp2:提供了http2相关的模块
- zlib:常见的压缩和解压缩的算法
- c-ares:做的dns查询的库
- llhttp: 做http协议的解析(序列化或反序列化)
- OpenSSL:常用在网络层面上的加密和解密的工具
例子: 用node-fatch发起请求时,大概调用顺序
- 首先通过npm去安装node-fatch模块就到达用户代码
- 然后再用户代码里面调用node-fatch模块,因为这些代码都是JavaScript代码,所以它会到底层V8里面去执行
- 然后v8会调用Node.js Core(JavaScript) 里面的http模块
- 然后http模块调用更底层的Node.js Core(C++) 的API
- 然后到llhttp去做http协议的序列化和反序列化,把得到的数据通过libuv
- libuv可能会创建一个TCP/IP链接,再把这个数据发送给远端
- 反之接收即流程反过来走一次,从libuv到用户代码就完成了接收
Node.js特点
1.异步I/O
当Node.js执行I/O操作时,会在响应返回后回复操作,而不是阻塞线程并占用额外内存等待
常见异步处理
setTimeout(()=>{
console.log("B");
})
console.log("A");
先打印A,后打印B
下面是异步读取文件场景
在调用异步API读取文件的时候,就会触发一个异步调用,然后就会将这个读取文件的操作交给libuv
的线程池去做,主线程就不需要等待文件读取完成,就可以先去做其他调用,等线程池的读取文件操作结束返回数据给主线程就行了
2.单线程
这里主要是指Node.js的JavaScript的线程是单线程的,所以Node.js不适合做一些cpu密集的操作,例如下面这种例子,但是从版本12开始,就可以使用worker_thread模块单独起独立JavaScript线程,但每个线程的模型和主线程没有太大的变化,使用上也不会有太大的区别
function fibonacci(num){
if(num === 1 || num === 2){
return 1;
}
return fibonacci(num - 1) + fibonacci(num - 2);
}
fibonacci(42);
fibonacci(43);
JS单线程: 一般说Node.js是单线程是指
JavaScript线程
为单线程
- 实际上Node还有其他线程:
JS线程
+libuv线程池(4个线程)
+V8任务线程池
+V8 Inspector线程
;
- libuv线程池: Node.js为了不阻塞一些操作,就会把一些操作(例如:异步读取文件)交给
libuv线程池
里面的线程执行,还包括一些对cpu消耗时间比较大的操作(例如加密解密)也会交给libuv线程池去操作- V8任务线程池:调整V8本身的线程池,大部分不会单独去调整这个
- V8 Inspector线程: 当V8做调试的时候,会使用这个线程,当做调试的时候,比如在代码中写了个死循环,只有这类调试工具在单独的线程上,这样才能调试死循环的问题
优点:- 不用考虑多线程状态同步问题,也就不需要锁
- 同时还能比较高效的利用系统资源
缺点:- 如果出现阻塞问题会产生更多的负面影响
- 解决办法:多进程或多线程
3.跨平台
例如我想使用Linux系统上面的socket,在windows上使用同样的代码也是做类似的事情,在跨平台方面上,使用Node.js的时候,它已经完成了绝大部分的工作,就不用考虑太大跨平台的问题
const net = require('net');
const socket = new net.Socket('./tmp/scoket.sock')
- 大部分功能、API是跨平台的
- Node.js跨平台 + JS无需编译环境( + Web 跨平台 + 诊断工具跨平台 ) = 开发成本低(大部分场景无需担心跨平台问题),整体学习成本低