打开node官网,可以看到首页正中写着:
Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js' package ecosystem, npm, is the largest ecosystem of open source libraries in the world.
其中:event-driven —— 事件驱动;non-blocking I/O —— 非阻塞I/O,就是说的node的特点。
实际上,我所理解的node的特点有三个:单线程、事件驱动、非阻塞I/O,这三者是三个特点,但理解起来都是一个意思。
单线程
像java、PHP等这样的后端语言,都是多线程的,即当有一个请求过来的时候,开启一个CPU,它使计算机能够在同一时间执行多个线程。而node的单线程是指当遇到需要加载数据库、读取磁盘等请求的时候,它会将其放入“队列”中执行,待下一轮事件循环的时候再判断能否执行它的回调函数,若此时它的回调函数需要加载I/O则放入“队列”中,它的特点是线程利用率是100%。
事件驱动
举一个通俗点的例子,你在餐厅吃饭,如果当时店内生意比较好,你坐下来,服务员过来招待你,这时,另一桌也刚坐下并呼叫服务员。正常情况下,服务员肯定会想给你个菜单让你自己看看,看好了再叫他,接着去招呼那一桌的客人了,完了再给你端茶什么的。
这就是事件驱动。通过监听事件的状态变化来做出相应的操作。当你发出一个请求的时候,如果这个请求需要等待,那这个请求便会被放入“队列”中,在处理这个请求的同时,后续的无需请求也在被处理,事件处理结束后,调用请求的回调函数。注:在处理无需等待的事件时,事件循环是暂停的。
非阻塞I/O
阻塞I/O就是当用户发一个读取文件描述符的操作的时候,进程就会被阻塞,直到要读取的数据全部准备好返回给用户。那非阻塞I/O呢,就与上面的情况相反,用户发起一个读取文件描述符操作的时,函数立即返回,不作任何等待,进程继续执行。但是程序如何知道要读取的数据已经准备好了呢?最简单的方法就是轮询,即事件循环。
好了,文章开始我说这三个特点就是一个特点,是因为总结起来,它们可以用下面这个图表示。
刚启动node文件,第一轮循环,处理无需等待的事件,需要读取磁盘、网络通信的事件会被放入“队列”,待它们处理完毕且事件循环到时,会处理它们的回调函数,如果回调函数需等待,则再放入“队列”,再循环。
node中,只有I/O操作是异步的,对于大量计算,它仍然会被阻塞,所以node不适合有大量计算的工作。