@FunC
2017-11-12T14:32:32.000000Z
字数 1454
阅读 2550
Node.js
Node.js的核心代码仅包含最核心的功能,其余部分留给生态(ecosystem)以模块(modules)的方式实现。一方面使核心代码便于维护,另一方面也使Node.js生态形成了良好的风气。
Node.js解决了“依赖地狱”的问题。每个安装的包(package)都有着自己独自的依赖,因此当一个程序同时依赖大量的包时也不会发生冲突。Node.js把可复用原则发挥到了极致,每个Node.js程序都是有大量小而功能专一的依赖组成。
Small modules 除了可复用之外,还有着易用、易理解、易测试、易维护,便于和浏览器共享的优点。总而言之,是将”Don’t Repeat Yourself (DRY)”应用到一个新的境界。
Node.js modules 的一个特点就是只暴露尽可能少的功能,提高API的可用性。经常能见到Node.js modules 只对外暴露一个函数,其它次要特性则作为对外暴露函数的属性(properties)存在。优点是让使用者能分清主次。
Node.js modules 的另一个特点就是:modules 是拿来用,而不是拿来扩展(extend)的。虽然看似不利于扩展,但这样做能减少应用场景,简化实现,使模块更容易维护,
与完美而周全的设计相比,简单的设计,能够花费更少的精力实现,更容易迁移、维护与理解。另外JavaScript中字面量对象的写法减少了大量繁琐的类的继承过程。
软件应该总是一个对现实世界的近似。但实际上,与用大量的代码去完美模拟现实相比,用合理的复杂度把一个功能尽快实现更容易取得成功。
在处理并发时,若使用阻塞I/O,进行I/O时会阻塞线程,导致要开启新的线程去处理新的请求。而一个线程的系统资源开销并不低,它需要消耗内存,会导致上下文的切换。所以给每一个连接分配一个长时间运行但却大量时间闲置的线程,是不那么高效的做法。
非阻塞I/O有两种常见实现方式,其中一种是“busy-waiting”。简单来说就是循环地尝试读取要用的资源,如果不可用便进行下一次循环;如果可用,就进行相应的操作。下面是对应的伪代码:
然而这种轮询算法最大的问题就是浪费了大量的CPU时间(因为通常I/O很慢,而循环很快)
另外一种方式就是 event notification interface,核心在于它将I/O事件收集并放入队列,而查询这个队列的过程是阻塞的。也就是说,只有当队列中有事件可用时,才会返回,避免浪费大量CPU时间。下面是伪代码:
该模式的核心在于给每一个I/O操作都附上一个对应的handler(在Node.js 中表现为callback函数),只要在event loop中处理到了这个事件,就会触发相应的handler。
一图蔽之:
每个操作系统都有其对Event Demultiplexer 的实现,例如Linux 中的epoll,Mac OSX 中的kqueue,Windows 中的 IOCP API。为了屏蔽不同操作系统之间的差异,libuv 对其进行了一层抽象。
主要有libuv,V8(JavaScript引擎)以及一些Node 的核心API: