Node.js到底是什么?

更新日期: 2019-09-06阅读: 1.6k标签: node

接触前端也有一段时间了,逐渐开始接触Node.js,刚刚接触Node.js的时候一直都以为Node.js就是JavaScript,当对Node.js有一定的了解之后,其实并不然两者之间有关系,其中的关系又不是必然的,对Node.js进行的一些了解,对其进行一些概述,本篇文章并没有对Node.js的api进行讲解,而是能够更加的明白Node.js是什么。


到底什么是Node.js

先看一下Node.js官网中是如何形容Node.js的,打开官网看到的第一句话就是Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.(Node.js是一个JavaScript运行时建立在Chrome的V8 JavaScript引擎。)在上面这段话中最重要的一点就是运行时。

到底什么是运行时呢?其实在笔者看来运行时就是程序在运行时所需要的组件,可以将其想象成为是一种编程语言的运行环境。然而这个运行环境包含了代码运行时所需要的解释器和底层操作系统的支持等。

文章开头也说过Node.js与JavaScript之间有关系,但是其关系也不是必然,到这里大概也就有点眉目了。对于任何语言来说,其中最终要的就是其解释器如何去处理这些编程语言。Node.js的底层是使用C++实现的,然而语法则是遵循ECMAScript规范,其实完全可以把其实现换乘一种新的编程语言,更换语言的同时也就意味着其解释器发生了翻天覆地的变化。


Node.js为什么要选择JavaScript

到了这里可能有些疑问,编程语言和解释器有关系,那么为什么要选择了JavaScript然而不是其他的语言呢?Node.js作者(Ryan Dahl)说,在创造Node.js的时,其目的是为了实现高性能的Web服务器,其看重的并不是JavaScript这门语言。但是他需要的事一种编程语言来实现其想法,这种编程语言不能带有任何的IO功能,并且需要良好的支持事件机制。说到这里感觉就是在说JavaScript这门语言(感觉就是天命之选,O(∩_∩)O哈哈~)。首先JavaScript完全满足上述的两个条件,然而就顺其自然的JavaScript就成了Node.js的主导者。


Runtime

上面一直提到的就是Runtime,Runtime是什么?运行时刻是指一个程序在运行(cc或者在被执行)的状态。也就是说,当你打开一个程序使它在电脑上运行的时候,那个程序就是处于运行时刻。在一些编程语言中,把某些可以重用的程序或者实例打包或者重建成为运行库。这些实例可以在它们运行的时候被链接或者被任何程序调用(节选自百度百科)。

其实对于开发者来说根本就不用去考虑其背后到底是怎样实现的,我们站在开发的角度来想一想,对于某一种语言的Runtime表示开发者可以在Runtime上运行某种语言所编写的代码,如果把这个概念扩大一下说的话,Chorome也是一个JavaScript运行时依赖于背后的JavaScript引擎来运行JavaScript代码而已。

其对应的Runtime可以对其编程语言进行一些拓展,比如在Node.js中的fs、Buffer就是其对ECMAScript的拓展,Runtime并不包含整个ECMAScript中的全部特性。反过来讲,就算一个特性没有体现在标准里,而大多数的运行时都支持它,也可以变成实际上的规范。通过上述所说我们可以理解到对于任何语言来讲我们无需对其底层的实现,所有的东西都依赖于其运行时的实现而已,运行时环境对其支持情况才能表现出其语言的特性。

同样的一段代码可能在浏览器端可以顺利执行,但是放到Node.js中不一定可以顺利执行,反之也是一样的,这样的就足可以说明上述问题了。


Node.js内部机制

Node.js中有几个很重要的关键词单线程,非阻塞异步IO,在笔者刚刚接触Node.js的时候,这几个词经常听到,有些懵懵懂懂不是太能理解。为了更好的了解其内部机制那么针对这些东西进行说明。

回调函数

为什么要说回调函数呢?对Node.js模块有一定了解的话Node.js中模块都是依赖于回调函数的,那么什么是回调函数呢?

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。(节选自百度百科)。

上面说了一堆套话,其实回调函数就是讲一个函数作为参数传给另一个函数作为参数,并且该函数可以被执行。回调方法和主线程处于同一个线程,假设主线程发起了一个底层的系统调用,操作系统会执行这个系统调用,当这个系统调用完成之后则会再回到主进程去执行后续的方法。

在Node.js中在操作过程中可能会有一个比较耗时的IO操作,当IO操作有了返回结果之后才会继续向下执行,其中在进行IO操作时就造成了代码的阻塞,在Node.js最初设计的时候已经考虑到了这一点,所以提出了异步函数加回调函数的方式,也能实现高并发的处理。对于前端来讲Ajax就是一个异步回调函数,当发起请求时如果有后续代码会先向下继续执行,而不会等待期请求结果。

回调函数机制:

  1. 定义一个回调函数;
  2. 提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者;
  3. 当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。
同步/异步

有关于同步/异步也搜索了一些文献,但是都是简简单单概括一下,没有细致的说明。所谓同步和异步其描述的事进程和线程的调用方式。因为Node.js的单线程,因此同个时间只能处理同个任务,所有任务都需要排队,前一个任务执行完,才能继续执行下一个任务,但是,如果前一个任务的执行时间很长,比如文件的读取操作或网络请求,后一个任务就不得不等着,拿文件的读取操作来说,当用户向后台读取大量的文件时,不得不等到所有数据都读取完毕才能进行下一步操作,后续程序只能在那里干等着,很有可能造成响应超时。因此,Node.js在设计的时候,就已经考虑到这个问题,主线程可以完全不用等待文件的读取完毕,可以先挂起处于等待中的任务,先运行排在后面的任务,等到文件的读取有了结果后,再回过头执行挂起的任务,因此,任务就可以分为同步任务和异步任务。

  1. 同步任务:同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务,当我们打开网站时,网站的渲染过程,比如元素的渲染,其实就是一个同步任务
  2. 异步任务:异步任务是指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程,当我们打开网站时,像图片的加载,音乐的加载,其实就是一个异步任务

上述所说同步调用指的是进程/线程发起调用后,一直等待调用结果返回后才会继续向下执行,但是对于Node.js来说虽然也是这样,但是并不代表的CPU在这段时间内也会一直等待,操作系统多半会切换到另一个进程/线程上等调用返回结果后在切回原有进程/线程。然而异步则恰恰相反,当发起异步调用时,进程/线程会继续向下执行,当调用返回结果后通过某种技术手段通知其调用者已经有其结果。

我们一直都在说的一句话就是JavaScript是一门异步语言,但是对于ECMAScript而言并没有对异步有明确的规范,其实是其解释器(Node.js或浏览器)的runtime的其他线程来实现的,这些并不是JavaScript这门语言本身的功能。

对于异步请参考:浅析JavaScript异步

阻塞/非阻塞

笔者在没有了解阻塞/非阻塞之前一直以为同步/异步与阻塞/非阻塞之间是没有区别的,然而现实就是这么的打脸,阻塞/非阻塞和同步/异步完全就是两组概念,他们之间没有任何的必然关系。很多人大概和我一样同步=阻塞,异步=非阻塞,这种概念是完全不对的。

在了解阻塞与非阻塞之前首先要了解一下什么是IO操作,IO操作其实是内存与外部设备之间复制数据的过程。

在阻塞的情况,是会一直等待直到write完全部的数据再返回。这点行为上与读操作有所不同,究其原因主要是读数据的时候,通常刚开始我们并不知道要读的数据的长度,而是在数据的头部设置了一个长度,在读完指定长度的头部后,才知道整个要读的数据长度。如果一开始就贸然设置一个要读的数据长度,然后像阻塞的write那样去等读完,则很可能会造成死循环;而对于write,由于需要写的长度是已知的,所以可以一直再写,直到写完。不过问题是write是可能被打断造成write一次只write一部分数据,所以write的过程还是需要考虑循环write, 只不过多数情况下一次write调用就可能成功。

非阻塞写的情况,是采用可以写多少就写多少的策略。与读不一样的地方在于,有多少读多少是由网络发送端是否有数据传输到本地内核缓存为准。但是对于可以写多少是由本地的网络堵塞情况为标准的,在网络阻塞严重的时候,网络层没有足够的内存来进行写操作,这时候就会出现写不成功的情况,阻塞情况下会尽可能(有可能被中断)等待到数据全部发送完毕, 对于非阻塞的情况就是一次写多少算多少,没有中断的情况下也还是会出现write到一部分的情况。

其实用一句话来说讲的话,同步调用会造成进程的IO阻塞,而异步不会造成调用进程的IO阻塞。

单线程与多线程

Node.js并没有提供多进程的支持,这代表在程序中所编写的代码只能运行在当前进程中,用于运行代码的事件也是单线程进行的。开发者无法在一个独立进程中增加新的线程吗,但是可以派生出多个进程来达到必行完成任务。

进程

进程是指在操作系统中正在运行的一个应用程序

线程

线程是指进程内独立执行某个任务的一个单元。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)。

对于Node.js,如果说JavaScript的函数式编程方式使得其异步编程的思想对程序员展现得更自然,那么它背后的功臣Libuv,则为异步编程的实现提供了可能。


上图中从左往右分为两部分,一部分是与Network I/O相关的请求,而另外一部分是由File I/O, DNS Ops以及User code组成的请求。

从图中可以看出,对于Network I/O和以File I/O为代表的另一类请求,异步处理的底层支撑机制是完全不一样的。 对于Network I/O相关的请求, 根据OS平台不同,分别使用Linux上的epoll,OSX和BSD类OS上的kqueue,SunOS上的event ports以及Windows上的IOCP机制。 而对于File I/O为代表的请求,则使用thread pool。利用thread pool的方式实现异步请求处理,在各类OS上都能获得很好的支持。Libuv团队为什么要选择thread pool的机制。基本上原因不外乎编码和维护复杂度太高、可支持的API太少且质量堪忧、技术支持较弱,而用thread pool则很好地避开了这些问题。

Node.js的异步调用时由Libuv来支持的,以readFile为例的话,读取文件的系统调用是由Libuv来完成的,Node.js只负责调用Libuv所提供的接口就可以了,等结果返回后在执行对应的回调方法。

并行与并发

自从Node.js出现后,JavaScript开始涉及后端领域,因为其出色的并发模型,被很多企业用来处理高并发请求。

与并发被同时提及到的还有并行,那么并行与并发有有什么区别?并行指在同一时间点同时执行,并发是指在同一时间片段同时执行,上面已将解释进程与线程,此时就可理解,进程之间相互独立,可实现并行,但线程不可以,多线程只能并发执行,实际还是顺执行,只是在同一时间片段,假似同时执行,CPU可以按时间切片执行,单核CPU同一个时刻只支持一个线程执行任务,多线程并发事实上就是多个线程排队申请调用CPU,CPU处理任务速度非常快,所以看上去多个线程任务说并发处理。

并发指的是一个CPU在不同线程来回跳,然后你会看到两个线程抢夺CPU资源所以两个线程输出执行的顺序不固定。

Node.js中的并发任务处理:

  1. 每个Node.js进程只有一个主线程在执行程序代码,形成一个执行栈。
  2. 主线程之外,还维护了一个"事件队列"。当用户的网络请求或者其它的异步操作到来时,Node都会把它放到事件栈之中,此时并不会立即执行它,代码也不会被阻塞,继续往下走,直到主线程代码执行完毕。
  3. 主线程代码执行完毕完成后,然后通过事件循环,也就是事件循环机制,开始到事件栈的开头取出第一个事件,从线程池中分配一个线程去执行这个事件。

接下来继续取出第二个事件,再从线程池中分配一个线程去执行,然后第三个,第四个。主线程不断的检查事件队列中是否有未执行的事件,直到事件队列中所有事件都执行完了。

此后每当有新的事件加入到事件队列中,都会通知主线程按顺序取出交EventLoop处理。当有事件执行完毕后,会通知主线程,主线程执行回调,线程归还给线程池。

我们所看到的Node.js单线程只是一个JavaScript主线程,本质上的异步操作还是由线程池完成的,Node.js将所有的阻塞操作都交给了内部的线程池去实现,本身只负责不断的往返调度,并没有进行真正的I/O操作,从而实现异步非阻塞I/O,这便是Node.js单线程和事件驱动的精髓之处了。


总结

读完本篇文章应该对Node.js有了一个简单的认识,其中提到的EventLoop在本文章并没有进行解释,有时间会对其进一步说明。Node.js完成了它提供高度可伸缩服务器的目标。它使用了Google的一个非常快速的JavaScript引擎,即V8引擎。它使用一个事件驱动设计来保持代码最小且易于阅读。所有这些因素促成了Node.js的理想目标,即编写一个高度可伸缩的解决方案变得比较容易,其Node.js对于高并发的处理也有很好的支持,总之Node.js的强大之处还有很多仍然需要慢慢摸索。

链接: https://www.fly63.com/article/detial/5698

关于 Node.js 里 ES6 Modules 的一次更新说明

关于 Node.js 里 ES6 Modules 的一次更新说明,总结来说:CommonJS 与 ES6 Modules 之间的关键不同在于代码什么时候知道一个模块的结构和使用它。

用node.js开发一个可交互的命令行应用

在这个教程中,我们会开发一个命令行应用,它可以接收一个 CSV 格式的用户信息文件,教程的内容大纲:“Hello,World”,处理命令行参数,运行时的用户输入,异步网络会话,美化控制台的输出,封装成 shell 命令,JavaScript 之外

Node启动https服务器

首先你需要生成https证书,可以去付费的网站购买或者找一些免费的网站,可能会是key或者crt或者pem结尾的。不同格式之间可以通过OpenSSL转换

nodejs 异步转同步

nodej项目在微信环境开发,nodejs的异步特效,会导致请求没有完成就执行下面的代码,出现错误。经过多方查找,可以使用async模块来异步转同步,只有前一个function执行callback,下一个才会执行。

基于node服务器的大文件(G级)上传

3G的大文件分1500个2M二进度文件,通post方法发送给node服务,服务器全部接收到文件后,进组装生成你上文件。

为什么要把 JavaScript 放到服务器端上运行?

JavaScript比C的开发门槛要低,尽管服务器端JavaScript存在已经很多年了,但是后端部分一直没有市场,JavaScript在浏览器中有广泛的事件驱动方面的应用,考虑到高性能、符合事件驱动、没有历史包袱这3个主要原因,JavaScript成为了Node的实现语言。

了解node.js事件循环

node.js的第一个基本论点是I / O的性能消耗是很昂贵。因此,使用当前编程技术的最大浪费来自于等待I / O完成。有几种方法可以处理性能影响

Node.js 应用:Koa2 使用 JWT 进行鉴权

在前后端分离的开发中,通过 Restful API 进行数据交互时,如果没有对 API 进行保护,那么别人就可以很容易地获取并调用这些 API 进行操作。那么服务器端要如何进行鉴权呢?

Node.js 前端开发指南

我们经常跟Node.js打交道,即使你是一名前端开发人员 -- npm脚本,webpack配置,gulp任务,程序打包 或 运行测试等。即使你真的不需要深入理解这些任务,但有时候你会感到困惑,会因为缺少Node.js的一些核心概念而以非常奇怪的方式来编码。

happypack提升项目构建速度

运行在 Node.js 之上的 Webpack 是单线程模型的,也就是说 Webpack 需要处理的任务需要一件件挨着做,不能多个事情一起做。happypack把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!