2018年8月以来读过最好的node.js深入的书!
正好今天了解到”费曼学习法“,这本神书我将结合试探一番。
- 小核心
- 将功能最小集合,其余留给所用用户平台
- 好处:社区自由化、快速迭代、广泛试验、可维护性高
- 小模块
- 遵循UNIX哲学:“小即是美”、“让一个程序做好一件事”
- 好处:高可复用性、易于理解和使用、测试和维护简单、与浏览器共享
DRY
原则
- 小接触面
- 一个模块通常只暴露出最小的一组功能特性
- 简单和使用
- Keep It Simple, Stupid (KISS)原则
- 简单是复杂的最高境界
- 设计必须是简单的,无论是实现还是接口。更重要的是实现要比接口更简单。简单是设计中最重要的考虑因素。
const
并不意味着赋值是恒定不变的,但是该值的引用是静态的- 当需要引入一个模块时,使用
const
声明是最佳实践 - 如果需要创建一个不可变的对象,只使用const是不够的,还需要使用ES5的
Object.freeze()
或其他库 - 箭头函数时绑定到它们的词法作用域内。说大实话就是:一个箭头函数中的
this
的值和父块中的值时相同的 Map
真正又去的地方是使用函数和对象作为key,而这也是普通对象无法完成的,因为对象的所有key都会自动转换为字符串- 当
Map
迭代时,仍然遵循条目被插入的循序,这也是普通对象无法保证的 - WeakMap和WeakSet类似,只允许对象作为主键。当其主键key的对象被赋值
undefined
时,集合中的元数据也会跟着被清除! - WeakMap和WeakSet集合并不比map和set更好或更烂,只是适合不同的使用场景
Reactor模式是Node.js异步特性的和核心:单线程架构和非阻塞I/O。
使用阻塞I/O实现的Web服务间无法同时处理同一线程中的多个链接。Socket上的每个I/O将阻止任何其他连接的处理,因此在Web服务器中处理并发的传统方法是为需要处理的没饿并发连接启动一个线程或进程(或重用资源池中的进程)。这样,当线程被I/O才注意阻塞时,不会影响其他请求的可用性,因为他们在单独的线程中处理。
目前绝大多数操作系统都采用了 非阻塞I/O 访问资源的机制。在此模式下,系统调用总是立即返回,而无需等待数据读取或写入。如果调用时没结果,函数将简单地返回预定义的常量,指示在此时没有可用的返回数据。
例如:UNIX系统中 fcntl()
函数用于操作现有文件描述符,将其操作模式更改为 非阻塞模式,一旦资源处于非阻塞模式,任何读取操作将失败,返回码为 EAGAIN
,以防该资源还没有准备好读取任何数据。这种非阻塞的最基本模式就是在循环内主动轮询资源,知道返回一些实际数据,这被称为 忙碌等待
resources = [socketA, sockteB, pipeA]
while(!resources.isEmpty()) {
for (i=0; i < resources.lenght; i++) {
resource = resources[i]
// try to read
let data = resource.read()
if (data === NO_DATA_AVAILABLE) {
continue
}
if (data === RESOURCE_CLOSED) {
// the resource was closed, remove it form the list
resources.remove(i)
} else {
// some data was received, process it
consumeData(data)
}
}
}
使用这一的非阻塞技术,已经可以在同一个线程中处理不同资源,但是它仍然效率不高。实际上代码演示中,循环仅消耗宝贵的CPU时间来对多数时间不可用的资源进行迭代。轮询算法通常会导致大量的CPU时间浪费。
现代操作系统中并不用 忙碌等待
来处理非阻塞资源,其提供了一种更有效的并发和非阻塞资源。这种机制称为 同步事件多路分解器
或 事件通知接口
。
socketA, pipeB;
wachedList.add(socketA, FOR_READ);
wachedList.add(pipeB, FOR_READ);
while(events = demultiplexer.watch(wachedList)) {
// 事件循环
foreach(event in events) {
// 永远不会阻塞,并且总会有返回值
data = event.resource.read();
if (data === RESOURCE_CLOSED) {
// 资源已经被释放,从观察者队列移除
demultiplexer.unwatch(event.resource);
} else {
// 获得数据进行处理
consumeData(data);
}
}
}
- 应用程序 带着回调函数 向
Event Demultiplexer
提交请求从而生成新的 异步操作,任务完成后执行指定的回调函数,并且向Event Demultiplexer
提交的请求都是非阻塞的,它会立马把控制权返回给 应用程序 - 当一组异步操作完成时,事件多路分解器将新的事件推入
Event Queue
(事件队列) - 此时,
Event Loop
遍历Event Queue
的每个项目 - 执行每个事件对应的回调函数
- 回调函数也是 应用程序的一部分,它执行完毕后还会把控制权交回给
Event Loop
,但也会因为回调里再嵌套异步操作而重新向Event Demultiplexer
提交请求 - 当
Event Loop
中所有项目都被执行完后,循环将再次阻塞Event Demultiplexer
,当有新事件可用是,Event Demultiplexer
将触发另一个周期
打个比方,我们去点心店吃碗面,首先先得去收银台点单付钱,同步阻塞的情况是:我点了碗辣酱加辣肉面,然后我就在收银台等着,等到面来了,我拿着面去吃了,后面所有的人都无法点单无法下单。而reactor(同步非阻塞)的情况是我点了碗辣酱加辣肉面,钱付好以后我就拿着号去座位上坐下了,等面好了后,服务员会叫“XXX号,你的面好了,自己来取”(服务员帮你送上来的叫proactor),这里收银台就是reactor或者叫dispatcher,店里会有一个小二定时的轮询去看XXX号的XXX面有没有好,好了以后就通知XXX你可以来拿面了,没好你就等着呗。
异步行为:应用程序表示有兴趣在一个时间点(不阻塞)访问资源,并提供一个回调函数,当操作完后时,应用程序将在另一个时间点被调用。
模式通过阻塞来出现I/O,直到一组被观察资源的新事件可用,然后将每个事件分配到相关联的处理程序来做出反应。
每个操作系统都有不同的接口来实现事件多路复用器,Linux 是 epoll,Mac OSX 是 kqueue,Windows 的 IOCP API,即使是在相同的操作系统中对于不同资源的 I/O 操作也不同,所以 Node.js 使用libuv来统一处理 I/O 操作,来达到兼容不同操作系统的目的。