前言
在上一篇的文章里我们见证了node的诞生和发展史知道了node是怎么出现的,私下我查阅了很多资料和询问了一些有经验丰富的人在加上一些自己对node的理解,对整体有了一个稍微清晰的认识 希望通过这篇文章来和大家分享下node的特色 相互学习探讨
下面我们废话不多说了直接进入正题
正文
接下来我们先看一个问题:
1.node是个什么东西?
我们第一反应肯定是先去node官网看下官方大佬是如何定义的 我们打开官网可以看到
官方的意思是: node.js是建立在Chrome的V8 上面的一个JavaScript引擎
可能这么说有点迷糊 我们换一个说法 我们知道node主要还是js代码
我们原来在node还没诞生的时候js代码都是放在浏览器上面运行的
如今node大哥的出世,改变了浏览器武林盟主的地位,node大哥让服务器端也可以运行js代码(具体如何运行在下面的博文会做描述,这里不做讲解)
下面我们在通过学术一点的文字解释:js是属性脚本语言,然而脚本语言的运行都需要一个解析器。对于写在页面里的js,浏览器充当了解析器的角色。而对于需要独立运行的js,node就是那个解析器
通过上面的探讨我们可以得出的结论是:node的出现就是给js这个小弟多了一种选择,原来只能跟着浏览器混的才可以发挥自己能力,现在跟着node一样也可以(目前JavaScript的运行环境只有浏览器和node.js环境两种)
我们解决了第一个疑问知道了什么是node
知道了它的一个特点是可以给js代码提供一个运行环境
下面我们再来看第二个问题
2.node的主要特征是什么
我们可能从网上资料看到关于对node特征评价词中出现频率最高无疑是 单线程、非阻塞Io、事件驱动
这几个词我们可能度都很熟悉但他们的结合就是组合node的特性下面我们来一一了解下这几个特性
他们的具体工作原理详解描述将在下篇node本质在那个描述
2.1单线程
注意:我这里说的单线程只是js执行线程也就是主线程是单线程,当遇到I/O操作时会把操作交给专门执行I/O的线程工作
居然是单线程肯定是要和多线程比较
例如像java、php等这样的后端语言,都是多线程的,当有一个请求过来的时候,开启一个CPU,它使计算机能够在同一时间执行多个线程 多线程同步模式是,将cpu分成几个线程,每个线程同步运行。
而node的单线程特征是指当遇到异步I/O请求的时候,它会将其放入I/O线程中执行("I/O" 主要指由libuv支持的,与系统磁盘和网络之间的交互),待下一轮事件循环的时候再去判断是否执行完成能否执行它的回调函数,若此时它的回调函数需要在加载I/O的话则将其在放入I/O线程中执行,它的特点是线程利用率是100%执行线程主线程一直循环检测执行
虽然感觉使用单线程很好 不用考虑多线程 锁 同步等问题,但我们还是理性看待一件事物的两面性
单线程也有它自身的弱点,可靠性底下 不能充分利用多核cpu 单线程也就是程序执行时,程序必须是连续顺序下来的,必须前面的处理好,后面的才会执行到。
这些弱点是学习Node的过程中必须要面对的。积极面对这些弱点,可以享受到Node带来的好处,也能避免潜在的问题,使其可以高效利用
2.2非阻塞Io
和单线程一样我们当然要和阻塞I/O进行对比学习
阻塞
阻塞 是指在 Node.js 程序中,其它 JavaScript 语句的执行,必须等待一个非 JavaScript 操作完成。这是因为当 阻塞 发生时,事件循环无法继续运行JavaScript。
在 Node.js 中,JavaScript 由于执行 CPU 密集型操作,而不是等待一个非 JavaScript 操作(例如I/O)而表现不佳,通常不被称为 阻塞。在 Node.js 标准库中使用 libuv 的同步方法是最常用的 阻塞 操作。原生模块中也有 阻塞 方法。
在 Node.js 标准库中的所有 I/O 方法都提供异步版本,非阻塞,并且接受回调函数。某些方法也有对应的 阻塞 版本,名字以 Sync
结尾。
代码比较
阻塞 方法 同步 执行,非阻塞 方法 异步 执行。
使用文件系统模块做例子,这是一个 同步 文件读取:
const fs = require('fs');
const data = fs.readFileSync('/file.md'); // 在这里阻塞直到文件被读取
这是一个等同的 异步 示例:
const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
});
第一个示例看上去比第二个简单些,但是有一个缺陷:第二行语句会 阻塞 其它 JavaScript 语句的执行直到整个文件全部读取完毕。注意在同步版本中,如果错误被抛出,它需要被捕获否则整个程序都会崩溃。在异步版本中,由作者来决定错误是否如上所示抛出。
让我们稍微扩展一下我们的例子:
const fs = require('fs');
const data = fs.readFileSync('/file.md'); // 在这里阻塞直到文件被读取
console.log(data);
// moreWork(); 在console.log之后执行
这是一个类似但不等同的异步示例:
const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
console.log(data);
});
// moreWork(); 在console.log之前执行
在上述第一个例子中, console.log
将在 moreWork()
之前被调用。在第二个例子中, fs.readFile()
是 非阻塞 的,所以 JavaScript 执行可以继续, moreWork()
将被首先调用。在不等待文件读取完成的情况下运行 moreWork()
通过上面的说明和代码实例我们可以看出node使用非阻塞I/O可以大大提高系统吞吐量
2.2事件驱动
事件驱动(event-driven)
是node
的一大特性也是node很核心的存在。通俗一点描述就是
通过监听事件的状态变化来做出相应的操作
比如读取一个文件,文件读取完毕,或者文件读取错误,那么就触发对应的状态,然后调用对应的回掉函数来进行处理
那么我们知道为什么node要采用事件驱动的方式啦
通俗一点来书就是:前世的因 今生的果 凡事皆有因果 (不开玩笑啦)
其实上面这句话说得也没毛病在我的上一篇博客里面讲述了node的诞生知道node基于js语言然而
js是一种很棒的事件驱动编程语言,因为它允许使用匿名函数和闭包,更重要的是,任何写过代码的人都熟悉它的语法。事件发生时调用的回调函数可以在捕获事件处进行编写。这样可以使代码容易编写和维护,没有复杂的面向对象框架,没有接口,没有过度设计的可能性。只需监听事件,编写一个回调函数,其他事情都可以交给系统处理 所以我们的老大哥node也选择这种方式
当然这不是全部原因单线程等其他性质也决定了他的方式(其实就是不想麻烦我们开发者啊 不用去关心一些其他乱七八糟的东西)
我们现在知道了node为什么要选择面向事件驱动编程而不选择面向对象编程的原由:
那么我们下面先简单叙述一下这个过程(具体细节将在下篇文一一讲解)
因为node
是单线程运行的,通过一个事件循环(event-loop)来循环取出消息队列(event-queue)中的消息进行处理,
处理过程基本上就是去调用该消息对应的回调函数。
消息队列就是当一个事件状态发生变化时,就将一个消息压入队列中
总结
本文描述了node的几大特征 单线程、非阻塞Io、事件驱动
讲述了他们的原由和基本理解,在写博客的过程中通过对各个知识点的熟悉查阅 有了更深的理解
如文中有什么错误问题或有疑问的地方都可指出 大家相互学习
下篇node.js本质核心将会对这几大特征
进行更深入的介绍和node其他核心点,对node有一个更全面更深入的理解