1. node是什么?
node是一个基于V8引擎的js运行环境,其特点为:
(1)异步I/O:用户线程在操作I/O时根本不考虑IO的执行,全部交给IO线程池去处理,用户线程只用等待一个完成信号即可—从语言层面上可以很自然进行并行I/O操作,每个调用之间都无需等待之前的IO调用结束。
eg:以读文件为例:
* 同步IO方式:耗时M+N;
* fs.readFileSync('1.text'); //耗时M
* fs.readFileSync('1.text'); //耗时N
* 异步IO方式:耗时max(M, N);
* fs.readFile('1.text', function(){ ... }); //耗时M
* fs.readFile('2.text', function(){ ... }); //耗时N
(2)事件与回调函数:
* 将事件从前端浏览器引入后端,配合异步IO,将事件点暴露给业务逻辑;
* 回调函数作为最好的接收异步调用返回数据的方式;
(3)单线程:Node自身是多线程的,而js线程与其余线程是无法共享任何状态的,而且js代码无法并行执行(同一时间段只能执行一段代码);
* 优点:
* 不存在多线程中死锁、状态同步等问题;
* 不存在创建线程和执行期上下文切换的开销;
* 缺点:
* 无法利用多核CPU;
* 错误会引起整个应用退出,健壮性不高;
* 大量计算占用CPU导致cpu时间片不能释放 — 后续的异步IO发不出调用,已完成的异步IO回调函数得不到执行;
* 解决长时间计算:
* 编写c/c++扩展(后续文章会介绍)的方式更高效利用CPU;
* 通过子进程的方式,讲一部分node进程当作常驻服务进程用于计算,然后利用进程间的消息来传递结果,将计算和IO分离,充分利用多CPU;
(4)跨平台:基于libuv实现;
2. node的构成
(1)V8:以C++实现的高性能的js引擎;
* 将编写的js代码编译成机器码,然后再执行;
* 提供c++函数接口,为nodejs提供v8初始化,创建context、scope等;
(2)libuv:为builtin modules提供了API,这些API用来支持请求和数据返回的异步处理方式(回调函数在libuv触发);
(3)builtin modules:由c++写的各类模块,包含file stream、crypto、zlib等基础功能;
(4)native modules:js写成供应用程序调用的库,依赖于builtin modules来获取相应的服务支持;
综上:暴露给开发者的接口是native modules,当我们发起请求,请求自上而下穿越native modules,通过builtin modules将请求传送至v8、libuv和其他辅助服务,请求结束则从下回到上层,最终调用我们的回调函数。
3. 模块分类
(1)文本模块
* js文本模块:自己编写并保存的.js文件;
* JSON模块:JSON文件;
* c/c++扩展:使用node-gyp扩展构建工具将扩展文件编译为.node文件(windows下是.dll 文件,*nix下是.so文件,libuv屏蔽了平台的差异,在node中统一为.node文件) 进入node命名空间中,通过process.dlopen()加载代码执行;
(2)核心模块
* js核心模块:node项目的lib目录下,Node项目编译时先会将lib目录下的js文件编译成c/c++文件—以字符串的形式进入二进制文件(存在于node命名空间中),在调用js核心模块时通过process.binding('Natives')加载并编译执行—获取js核心模块的源代码字符串,再将其转为函数编译并执行,将其结果缓存在 NativeModule._cache对象上;
注:两个过程的编译不一样:第一个编译过程只是将js源代码转为字符串后注册进c里的数组中,然后生成.h文件进入node环境中;第二次编译是将js源代码编译成可执行的二进制文件;
* c/c++核心模块:内建模块,node项目的src目录下,也是通过.h头文件将模块添加到node_module_list数组中,将src目录的内建模块名写入node-gyp的‘target_name’的'node'节点的source中,在编译整个Node项目的时候,将其代码编译为可执行文件,在调用时通过process.binding(内建模块名) 协助加载内建模块编译后的代码,直接执行即可;