Node.js 系列 - 基础概念

664 阅读9分钟

作为还在漫漫前端学习路上的一位自学者。我以学习分享的方式来整理自己对于知识的理解,同时也希望能够给大家作为一份参考。希望能够和大家共同进步,如有任何纰漏的话,希望大家多多指正。感谢万分!


在 Node.js 系列的第一节里, 我会先介绍 Node.js 的一些基本概念. 让你在看完这篇文章时, 能对 "什么是 Node.js?", "为什么用 Node.js?" 这两个问题有个基本的概念. 并且尝试写下第一句 Node.js 代码.

什么是 Node.js?

先来看看 Node 官网 给的答案:

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。 Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。

基本上, 很多人第一次看到上面这段定义的时候, 完全搞不懂它在说什么...

下面我就来逐步解释 Node.js 的这段定义.

基于 Chrome V8 引擎的 JavaScript 运行环境

看到这句话后, 你可能第一个疑问是 🤔 "什么是 Chrome V8 引擎?"

在解答这个问题之前, 回到我们熟悉的前端领域, 我先问另一个问题 🤔 "JavaScript 代码是如何在浏览器中运行的?"

简单来说, 我们所写的 JavaScript 源代码, 是为了给人看的, 浏览器加载 JavaScript 代码后, 它是看不懂的, 需要翻译成机器码, 也就是 "机器的语言", 才可以让机器执行. 在浏览器中, JavaScript 引擎负责进行代码的解释. V8 引擎是 Chrome 浏览器在用的 JavaScript 引擎. V8 引擎由 C++ 实现, 支持跨平台, 就是说可以在各种操作系统上使用, Node.js 基于 V8 引擎, 意味着它可以让 JavaScript 代码脱离浏览器的束缚, 在各种各样不同的操作系统上运行.

那么一句话总结, Node.js 不是一门语言, 是一个可以让 JavaScript 代码在各种各样的平台上得到执行的运行环境. 之所以支持 JavaScript 语法是因为基于 V8 引擎.

非阻塞式 I/O

"非阻塞式 I/O", 我知道这几个字看起来让人有点懵. 别着急, 那让我换个说法, "不会阻塞 JavaScript 程序执行的 Input/Output 操作", 这样会不会清楚一点? 可能你还是不太懂, 那下面我就逐字地解释.

在说 "非阻塞" 之前, 先了解什么是 "阻塞". 从字面上理解, "阻塞" 就是堵住了, 通不过的意思. 在计算机中, 程序在线程内按顺序被执行, 后面的操作必须等前面的操作结束才能被执行. 如果前一个操作耗时很长, 后面的操作就要一直等着, 直到前面的操作完成. 这个等待的状态, 叫做 "阻塞".

线程: 程序执行流的最小单元。程序代码在其中被 CPU 依次处理. 在一条线程中, 同一时间, 只有一段代码被被执行, 或等待被执行.

现在再来说说什么是 "Input/Output 操作". 从字面上翻译就是 "输入/输出", 那输的是什么呢? 简单说就是 "信息". 在程序执行过程中如果需要操作系统进行 磁盘读写 或 网络通信, 我们就都统称为 "I/O 操作". 例如, 从服务器获取页面, 写入文件, 提交表单, 读取数据库都是属于这个范畴的. 凡是这一类操作, 直观感受是 "花的时间较长". 那么在代码执行 I/O 操作时, 往往要让后面的操作等很长时间. 整条线程一直处于阻塞的状态, 这种 I/O 操作方式称为 "阻塞式 I/O". 拿生活举例, 游戏没下载完, 我就只能干等着玩不了.

在程序设计中, 对于高并发的任务 (同时发生的任务) , 在线程阻塞的情况下, 整条线程不能执行程序, 这会导致任务处理速度极慢. 常见的方案是通过多线程来解决. 但每条线程的利用率并没有增加, 同时也会导致硬件成本高昂.

并发: 在同一个时间段中, 几个任务同时进行. 但是在任意时间点, 有且只有一个任务在执行.

(可以想象成你在同时写五份作业, 每份只写几笔就换下一个, 然后再反回来接着写两笔. 虽然五份作业在一段时间内同时都被写了, 但是在任一时间点你只是在写其中的一份作业.)

Screen Shot 2018-10-13 at 5.55.43 PM

在 Node.js 中, JavaScript 代码在一个单一的主线程中进行处理, 同一时间只能处理一项任务. 为了处理高并发, 采用了 "非阻塞式 I/O", 也可以称为 "异步式 I/O". 当线程遇到 I/O 操作时,不会以阻塞的方式去等待操作完成. 而是将 I/O 操作先放到另一个地方等待处理, 然后 Node.js 继续执行下一条代码. 等主线程代码全部执行完毕后, 再去处理 I/O 操作. 具体的过程, 在下一段介绍.

事件驱动

继续上一段的内容. 在 Node.js 中, 当主线程遇到 I/O 操作时,会先把 I/O 操作封装成一个请求对象, 然后被推入到线程池中等待执行. 然后异步调用的第一阶段结束, JavaScript 主线程继续执行后续操作, 直至所有非阻塞操作都处理完. 因为 I/O 操作在线程池中等到处理, 所以也不会阻塞主线程.

当线程池有空余线程时, 等待的 I/O 操作会被处理. 当 I/O 操作完成, 获取的结果保存在请求对象的 result 属性上. 操作系统会来检查线程池是否有处理完的请求, 如果有, 就把请求对象加入到事件队列中.

在主线程的所有非阻塞代码都处理完后, 事件循环开始逐个处理事件队列中的事件. 保存在请求对象的 result 属性被取出, 作为与请求绑定的回调函数的参数. 然后执行回调来完成此 I/O 操作.

当队列中已经没有未处理的事件了, 程序结束. 可以看出程序的结束与否, 取决于事件是否被全部处理完毕, 因此称 Node 是事件驱动的.

Screen Shot 2018-10-13 at 5.54.42 PM

做个比喻

如果不好理解的话, 可以想象老王一个人去钓鱼.

  • 单线程阻塞式 I/O 』就像老王把鱼竿架好之后, 就在旁边等着, 等鱼漂抖动的时候就拉杆. 在等待的这段时间里, 他是什么也不干的. 当前一条鱼上钩后, 老王才能钓下一条鱼, 或者做其他事情.
  • 多线程阻塞式 I/O 』过了几天, 老王觉得这样效率低, 就买了十把鱼竿同时架起来. 在同一段时间内, 这种方法钓的鱼的确多了, 但老王在一个时间点只能去操纵一个鱼竿, 单个鱼竿的效率很低, 老王仍旧一直守在鱼竿旁, 在等的时候不能做其他的事. 
  • 非阻塞式 I/O 』又过了几天, 老王一个发明家朋友给了他一个 "自动通知鱼竿", 鱼上钩后, 鱼竿会给老王发微信通知他过来收鱼. 这样老王就可以在等鱼上钩的时间里去干别的事情了.

为什么用 Node.js?

先来说 Node.js 的优点:

  • 跨平台性: Node.js 基于 V8 引擎, 使得其可以覆盖到日常所能见的大多数平台.
  • 适合 "数据密集型实时应用": Node.js 采用非阻塞式 I/O 模型, 非常适用于处理涉及大量 I/O 操作的数据密集型应用. 比如, 聊天室, Web 服务器等.
  • "前端开发者" 学习成本低: Node.js 使用 JavaScript 语法, 对于前端开发人员来说, 学习成本非常低.
  • "第三方模块" 丰富: 繁荣的 Node.js 社区 提供了丰富的第三方模块供其使用. 通过这些模块, Node.js 可以拥有更多的应用场景.

当然 Node.js 也是有缺点的:

  • 不适合 "CPU 密集型应用": Node.js 不适合处理涉及大量 CPU 计算的任务. 比如, 视频编码、人工智能等.
  • 不能充分的利用多核 CPU 服务器: Node.js 是单线程的, 在多核设备上运行, 不能充分利用其性能. 但是 Node.js 其实也提供了支持多线程的方法.

安装 Node.js

根据计算机平台的不同, 安装方法也略有差异. 本文侧重于概念, 具体实操, 可能每位读者因偏好不同, 方法各异. 在此就不再赘述了.

最方便的方式是直接上官网去下载对应平台的源码或安装包, 或者通过包管理器直接安装.


Node.js 代码实例

var http = require('http');

function myNodeServer(req, res){
    res.writeHead(200, {'Content-type':'text/plain'});
    res.write('Hello World'); 
    res.end();
}

http.createServer(myNodeServer).listen(3000); //监听 3000 端口

console.log('Server is running!'); 

创建一个空文件, 把上面代码复制到其中, 文件后缀改成 .js. 然后在命令行中, 用 node 命令执行刚刚的文件 ( 注意文件所在目录位置和文件名 ) .

Screen Shot 2018-10-13 at 5.53.27 PM

如果运行成功你的命令行上会显示 "Server is running!" 这段话. 然后用浏览器访问 http://localhost:3000/ 这个地址. 你会看到网页上显示 "Hello World". 至此你就实现了一个简易 HTTP 服务器.

Screen Shot 2018-10-13 at 5.53.03 PM


Node.js 学习资源推荐


😆 好啦,今天的分享就告一段落啦。下一篇中,我会介绍 Node.js 的模块机制。

传送门 - Node.js 系列 - 模块机制

如果喜欢的话就点个关注吧!O(∩_∩)O 谢谢各位的支持❗️