nodejs在2010年就活跃于各大网站,但是我在2015年才开始知道这个东西,因为当时公司是做媒体的,需要不停的采集一些数据同步到mongoDb中,就使用了nodejs写服务,但当时那还是别的大神写的代码,像我等小菜鸡根本无法企及。
那时才知道原来javascript还能做服务。之前的认识就停留在HTML中的脚本语言,比如,动态处理一些Dom元素,添加属性、修改样式、元素间组织等,最多发送一下get、post请求给后台服务,从没想过js也可以像java、C++等一样,可以作为服务端,处理请求、文件操作、数据库查询、异步操作等,并且请求处理速度还要优于传统的javaWeb。现在公司要求做一个restApi,这就又把nodejs拉回了我的视线。
概念:nodejs是依赖于Chrome的V8引擎的一套javascript运行环境。
既然是基于V8引擎的,那么我们先了解一下这个v8(https://v8.dev/docs),摘取了部分信息做了简单概括:谷歌的V8引擎是一个开源的高性能js引擎,使用C++编写,在谷歌浏览器、NodeJs等很多地方都有使用。它可以单独运行也可以嵌入到任意的C++程序中运行(这使得v8引擎和c++无缝融合)。 v8引擎可以执行js源代码,可以给对象分配内存,并且能像java一样垃圾回收(v8引擎的开发团队leader之前是开发jvm团队的核心成员)。
从GitHub(https://github.com/nodejs/node)上可以看到nodejs项目结构:
由此可见,宣称的nodejs实现的种种功能,实际上是c++做的底层,然后这个c++的项目又嵌入了v8引擎的源码,使得能在javascript平台上运行c++实现的功能,达到了借助c++强化javascript的目的。但是这并不是全部,nodejs宣称的“非阻塞”、“单线程”、“异步I/O”,高并发并没有体现,它得益于js本身的,基于事件驱动的特性(类似,onload、onclick、ondbclick、onmouseover...),并且针对此特性使用c++做了足够的优化(libuv等),于是就有了网上多如牛毛的nodejs高并发架构描述:
- Application:调用方,写js代码
- V8:js代码一次性编译成机器码,并调用对应nodeAPI
- Bindings:看作一个adaptor,例如Windows、Linux、Mac等不同系统都有自己的适配器,跨平台的核心内容
- Libuv:任务执行库,单线程、高并发、异步IO的核心实现
回顾一下js:
单线程,(HTML5中新增了webWorker内容,允许在主线程执行期间插入耗时操作,但不会阻塞主线程的执行,不会干扰页面,且子线程受主线程控制,不得修改DOM元素,因此不违背js是单线程的),如果有需要事件驱动的,也就是需要回调的,主线程会顺序执行,将此类函数会加到event stack中,待主线程所有语句都执行完后才会去循环遍历event stack,取事件去执行。
setTimeout(function (){console.log(2);}, 1000);
setTimeout(function (){console.log(3);}, 1000);
console.log(1);
console.log(4);
// ================================
console.log(1);
console.log(4);
setTimeout(function (){console.log(2);}, 1000);
setTimeout(function (){console.log(3);}, 1000);
两种效果一样:
1
4
2
3
浏览器的事件循环机制(Event Loop),说白了也就是代码执行流程控制,实际上是通过一个执行栈和三个任务队列完成的,任务队列分为:global queue(全局队列);micro queue(微队列 / job); macro queue(宏队列 / task),执行优先级递减。global queue中存储的是立即执行的不需等待,不需回调,不需等事件触发的(实际上回调就是等待事件触发);micro queue中存储的一般是callback,比较常见的是Promise对象的then方法;macro queue中存储的是js本身的一些耗时操作,例如 setTimeout、setInterval、mutationObserve(H5)。先将global代码放入执行栈,执行完成后依次取micro队列执行,执行完成后执行macro队列的内容,如果macro队列中代码又引入了micro类型的方法,那么新的micro方法优先级依然高于macro方法,可以看出 macro 的优先级低于 micro,那么 UI rendering 之前我们可以做很多数据上的处理,比如 Vue 使用 mutationObserver 可以处理完所有数据处理后再统一做 UI render。
引用一个例子:
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3)
});
});
new Promise((resolve, reject) => {
console.log(4)
resolve(5)
}).then((data) => {
console.log(data);
Promise.resolve().then(() => {
console.log(6)
}).then(() => {
console.log(7)
setTimeout(() => {
console.log(8)
}, 0);
});
})
setTimeout(() => {
console.log(9);
})
console.log(10);
// 正确答案
// 注意:答案仅限于浏览器运行,如果使用nodejs运行练习代码结果会不同
1
4
10
5
6
7
2
3
9
8