纯前端的进军7--node.js
更新日期:
《纯前端的进军》系列主要作为曾经的纯前端,对后台和底层的一些弥补,涉及进程、网络通信,以及对node.js和相关框架的学习。前面已经科普过网络进程通信、TCP/IP、HTTP和Socket等,本节我们来简单熟悉一下node.js吧。
node.js
什么是Node.js
- Node.js是一个基于Chrome V8引擎的JavaScript运行环境。
- Node.js使用了一个事件驱动、非阻塞式I/O的模型,使其轻量又高效。
- Node.js的包管理器npm,是全球最大的开源库生态系统。
以上是Node.js官网的说明。
Node.js大部分基本模块都用JavaScript语言编写。
在Node.js出现之前,JavaScript通常作为客户端程序设计语言使用,以JavaScript写出的程序常在用户的浏览器上运行。
Node.js的出现使JavaScript也能用于服务器端编程。Node.js含有一系列内置模块,使得程序可以脱离Apache HTTP Server或IIS,作为独立服务器运行。
Node.js允许通过JavaScript和一系列模块来编写服务器端应用和网络相关的应用。
核心模块包括文件系统I/O、网络(HTTP、TCP、UDP、DNS、TLS/SSL等)、二进制数据流、加密算法、数据流等等。
使用框架可以加速开发。常用的框架有Express.js、Socket.IO和Connect等。
Node.js主要用于编写像Web服务器一样的网络应用,这和PHP和Python是类似的。但是Node.js与其他语言最大的不同之处在于,PHP等语言是阻塞的(只有前一条命令执行完毕才会执行后面的命令),而Node.js是非阻塞的(多条命令可以同时被运行,通过回调函数得知命令已结束运行)。
Node.js是事件驱动的。
开发者可以在不使用线程的情况下开发出一个能够承载高并发的服务器。其他服务器端语言难以开发高并发应用,而且即使开发出来,性能也不尽人意。Node.js正是在这个前提下被创造出来。
Node.js把JavaScript的易学易用和Unix网络编程的强大结合到了一起。
相关技术点
- V8
V8是为Google Chrome设计的JavaScript运行引擎,Google于2008年将其开源。V8用C++写成,它将JavaScript源代码编译成本地机器码而不是随时解释。
Node.js用libuv来处理异步事件,而V8提供了JavaScript的实时运行环境。libuv是一个网络和文件系统功能的抽象层,既可以用于Windows又可以用于匹配POSIX标准的系统,例如Linux、OS X和Unix。
Node.js的核心功能被包含进一个JavaScript库,并通过C++将各部分与操作系统进行联系。
- npm
npm是Node.js附带的包管理器。
npm是一个命令行工具,用于从NPM Registry中下载、安装Node.js程序,同时解决依赖问题。
npm提高了开发的速度,因为它能够负责第三方Node.js程序的安装与管理。
- 统一API
Node模块的API形式简单,降低了编程的复杂度。
Node.js将浏览器、数据(例如MongoDB或CouchDB)等组合到一起,通过JSON提供一个统一的接口。
由于前端框架和一些基本的后端开发技术(如MVC、MVP、MVVM等)变得流行,Node.js也支持客户端和服务器端重新利用相同的模型和接口。
- 单线程
Node.js以单线程运行,使用非阻塞I/O调用,这样既可以支持数以万计的并发连接,又不会因多线程本身的特点而带来麻烦。众多请求只使用单线程的设计意味着可以用于创建高并发应用程序。
Node.js应用程序的设计目标是任何需要操作I/O的函数都使用回调函数。
这种设计的缺点是,如果不使用cluster、StrongLoop Process Manager或pm2等模块,Node.js就难以处理多核或多线程等情况。
- 事件循环
Node.js将其注册到操作系统中,这样可以及时注意到新连接的产生。当新连接产生时,操作系统会产生一个回调。
在Node.js运行时内部,每个连接都被分配一个小型的堆。与其他服务器程序不同的是,Node.js不使用进程或线程处理连接,而是采用事件循环来处理并发连接。
而且Node.js的事件循环不需要手动调用。在回调函数定义之后,服务器进入事件循环。当回调函数均被执行完毕之后,Node.js结束事件循环。
- 异步执行的运行机制
- 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
- 主线程之外,还存在一个“任务队列”(task queue)。只要异步任务有了运行结果,就在“任务队列”之中放置一个事件。
- 一旦“执行栈”中的所有同步任务执行完毕,系统就会读取“任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
- 主线程不断重复上面的第三步。
CommonJS
模块定义:
- 模块中,上下文提供
require()
方法来引入外部模块。 - 对应引入功能,上下文提供了exports对象用于导出当前模块的方法或者变量,exports对象是唯一导出的出口。
- 模块中,存在一个module对象,代表模块自身,而exports是module的属性。
Node中,一个文件就是一个模块,将方法挂载在exports对象上作为属性即可定义导出方式。
模块标识:
模块标识是传递给require()
方法的参数,必须是符合小驼峰命名的字符串,或者以.
、..
开头的相对路径或者绝对路径,可以没有文件名后缀.js
。
Node.js模块
一、模块分类
Node.js的模块分为两类,一类为原生(核心)模块,一类为文件模块。原生模块在Node.js源代码编译的时候编译进了二进制执行文件,加载的速度最快。文件模块是动态加载的,加载速度比原生模块慢。
Node.js中存在4类模块(原生模块和3种文件模块),其中3种文件模块为:
.js
:通过fs模块同步读取js文件并编译执行。.node
:通过C/C++进行编写的Addon。通过dlopen方法进行加载。.json
:读取文件,调用JSON.parse解析加载。
其中文件模块还包括路径形式文件模块(如.
、..
和./
开头的标识符)和自定义文件模块(第三方npm包)。
自定义文件模块的查找最耗时也是最慢的一种,查找顺序为:
- 当前目录下
node_modules
目录 - 父目录下
node_modules
目录 - 向上逐级递归直到根目录下下
node_modules
目录
二、模块载入策略
Node引入模块,经历的三个步骤:路径分析、文件定位、编译执行。
require
方法接受以下几种参数的传递:- 原生模块(http、fs、path等)
- 相对路径的文件模块
- 绝对路径的文件模块
- 非原生模块的文件模块
Node.js对原生模块和文件模块都进行了缓存,于是在第二次require时,是不会有重复开销的。
require方法中的文件查找策略:
从文件模块缓存中加载
尽管原生模块与文件模块的优先级不同,但是都不会优先于从文件模块的缓存中加载已经存在的模块。从原生模块加载
原生模块的优先级仅次于文件模块缓存的优先级。require方法在解析文件名之后,优先检查模块是否在原生模块列表中。
以http模块为例,尽管在目录下存在一个http
/http.js
/http.node
/http.json
文件,require("http")
都不会从这些文件中加载,而是从原生模块中加载。
原生模块也有一个缓存区,同样也是优先从缓存区加载。如果缓存区没有被加载过,则调用原生模块的加载方式进行加载和执行。
- 从文件加载
当文件模块缓存中不存在,而且不是原生模块的时候,Node.js会解析require方法传入的参数,并从文件系统中加载实际的文件,加载过程中的包装和编译细节在前一节中已经介绍过,这里我们将详细描述查找文件模块的过程,其中,也有一些细节值得知晓。
三、Node.js模块与前端模块的异同
通常有一些模块可以同时适用于前后端,但是在浏览器端通过script标签的载入JavaScript文件的方式与Node.js不同。
Node.js在载入到最终的执行中,进行了包装,使得每个文件中的变量天然的形成在一个闭包之中,不会污染全局变量。而浏览器端则通常是裸露的JavaScript代码片段。
所以为了解决前后端一致性的问题,类库开发者需要将类库代码包装在一个闭包内。
参考
- Node.js - 维基百科,自由的百科全书
- 《JavaScript 运行机制详解:再谈Event Loop》
- 《深入浅出Node.js(三):深入Node.js的模块机制》
- 《浅析NodeJS模块加载机制 》
结束语
本节我们主要介绍了Node.js,了解模块加载机制及异步编程,至于具体的核心模块(例如http和fs模块),在后面缘分到了再详细讲。
作为一名纯前端,Node.js与浏览器中JS除了语法等较相似,却又很多不一样和缺少理解的地方,如果真正到使用到的时候,或许会有不一样的理解吧。
查看Github有更多内容噢:https://github.com/godbasin
更欢迎来被删的前端游乐场边撸猫边学前端噢
码生艰难,写文不易,给我家猪囤点猫粮了喵~