来自 Web前端 2020-05-07 05:42 的文章
当前位置: 网上澳门金莎娱乐 > Web前端 > 正文

Node.js到底是什么?

网上澳门金莎娱乐 1

网上澳门金莎娱乐,Javascript 单线程指的是在一个浏览器进程中只存在一个 Javascript 执行线程,所以任务需要顺序排列等待执行,而不能像 Java 等多线程语言一样并发执行。但是这种单线程模型在处理耗时的异步任务是会出现较长时间的线程阻塞,导致后续的任务不能被及时处理。所以在 Javascript 中存在异步的处理方式用于处理这种情况,不过严格来说所谓的异步,本质上还是借助于多线程的宿主实现的,并发 Javascript 语言本身特性。我想尝试着总结一下在不同的宿主环境下,Javascript 的异步实现机制。

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

但凡“ 即是单线程又是异步 ”的语言都有一个共同的特点:它们是 event-driven 的,所以 Javascript 异步的实现也与其事件机制关系密切。

到底什么是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引擎。)在上面这段话中最重要的一点就是运行时。

浏览器端的 Javascript 实现了两个很重要的异步 API,它们分别是 定时器 和 AJAX请求,它们具体都是怎么工作的呢?

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

定时器

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

定时器比如 setTimeout 被执行时,由浏览器的定时器线程执行的定时计数,而并不是 Javascript 执行线程负责计数,可以想象如果是 Javascript 执行线程负责计数,那必定会造成执行线程的阻塞。定时器线程在定时时间触发延时事件并将延时事件推入 Javascript 事件队列。当 Javascript 主线程同步代码执行完毕时,会去轮询该事件队列,取出最开始事件的处理函数推入主线程中被执行。

Node.js为什么要选择JavaScript

解释至此我们可以知道,为什么会说 Javascript 的定时器是不完全准时触发的呢?因为 Javascript 事件队列中的任务是被顺序取出执行的,如果在定时任务之前还存在其它的任务,或者主线程中的同步任务还没有被执行完毕,则定时任务会等到这之前的任务全部执行完毕之后,即主线程空闲出来了,才会被取出执行。

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

一个关于定时器的例子

Runtime

网上澳门金莎娱乐 2

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

我们希望在 500 ms 之后触发定时事件,然而上面这一段代码在 chrome 中执行的结果定时事件却是在 1000 ms 后才被触发。因为我们在定时器的下面写了一个空循环,在还不到 1000 ms 时 Javascript 主线程不会处于空闲状态,主线程同步代码在还没有执行完毕时,Javascript 不会去取出事件队列中的回调执行。

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

AJAX

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

AJAX 请求和定时器类似,同样是委托浏览器线程代为执行耗时任务,这里是借由浏览器的HTTP请求线程发起对服务器的请求,在请求得到响应之后触发请求完成事件,将回调函数推入事件队列等待执行。

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

网上澳门金莎娱乐 3

Node.js内部机制

req.send()方法是 AJAX  向服务器发生数据,它是一个异步任务,而 req.onreadystatechange()属于事件回调,只有在主线程同步代码执行完毕之后才会被从事件队列中取出执行,所以它是在 req.send()方法前面还是后面无关紧要,因为不管处于哪个位置,它都不会被立即执行。

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

网上澳门金莎娱乐 4

回调函数

这让我们想起似乎我们在给 DOM 元素绑定交互事件的时候也是这样,我们不需要去关心在文件的哪个区域声明我们的事件监听函数。其实原理是类似的,当用户点击一个绑定点击处理函数的 DOM 元素时,会有一个点击事件排入事件队列,该点击事件也需要等到当前所有正在运行的代码结束之后(可能还要等待其它此前已排队的事件也依次结束),才会执行。

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

NodeJS端:

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

NodeJS 的异步实现和浏览器端实现有所不同。在 NodeJS 中 Libuv 为 Node.js 提供了跨平台,线程池,事件池,异步 I/O 等能力,是 Node.js 如此强大的关键。Libuv 为上层的 Node.js 提供了统一的 API 调用,使其不用考虑平台差距,隐藏了底层实现。Libuv 本身就是异步和事件驱动的,所以,当我们将 I/O 操作的请求传达给 Libuv 之后,Libuv 开启线程来执行这次 I/O 调用,并在执行完成后,传回给 Javascript 进行后续处理。

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

总结来说,一个异步 I/O 的大致实现流程如下:

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

发起 I/O 调用

回调函数机制:

1 用户通过 Javascript 代码调用 NodeJS 核心模块,将参数和回调传入核心模块

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

2 NodeJS 核心模块将传入参数和回调封装为一个请求对象

同步/异步

3 将这个请求对象推入到I/O线程池中等待执行

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

4 Javascript 发起的异步调用结束,Javascript 线程继续执行后续操作

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

执行回调

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

1 异步任务完成之后,会将结果存放在请求对象的 result 属性上,并发出操作完成通知

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

2 每次事件循环时会检查 I/O 线程池中是否存在已经完成的 I/O 操作,如果有就将请求事件加入到I/O观察者队列当中(事件队列),之后当作事件处理

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

3 处理I/O观察者事件时,会将之前封装在请求对象中的回调函数取出,并将 result 参数传入执行,以完成 Javascript 回调的目的

本文由网上澳门金莎娱乐发布于Web前端,转载请注明出处:Node.js到底是什么?

关键词: