最近项目服务端是采用node来搭建,我在这之前是没有真正的接触node,在node方面也算是一个菜鸟,本文是将我在项目中碰到的一些知识点进行记录。
在接触到一个新的东西的时候首先要去了解他诞生的历史背景和意义,这样会让我更深的对他进行了解。
1. node的特点
作为后端JavaScript的运行平台,node保留了前端浏览器JavaScript中那些熟悉的接口,没有改写语言本身的任何特性,依旧基于作用域和原型链。
a. 异步I/O
在node中绝大多数的操作都是以异步的方法进行调用。作者在底层构建了很多异步的I/O的API,比如文件读取等操作。
这样的意义在于在node中,我们可以从语言层面上很自然的进行并行的I/O操作,每个调用之间无须等待之前的I/O调用结束,极大地提高了效率。
b. 事件和回调函数
事件的编程方式具有轻量级,松耦合,只关注事务等优势。
以下以node创建一个web服务器,监听3000端口为例来说明:
1 | var http = require('http'); |
从以上例子可以看出,在node处理过程中基本上都是以事件和回调函数来处理。
c. 单线程
node保持了JavaScript在浏览器中单线程的特点。而且在node中JavaScrip与其余线程是无法共享任何状态的。单线程的最大好处就是不用象多线程那样处处在意状态的同步问题。
单线程的缺点主要是以下三个方面:
- 无法利用多核CPU;
- 错误会引起整个应用退出,应用的强壮性不行;
- 大量计算占用CPU导致无法继续调用异步I/O;
d. 跨平台
起初node只能在Linux平台上运行,但是在v0.6.0版本后node已经能够直接在windows平台上运行了。
2. node的模块机制
对于JavaScript自身而言,它的规范依然是很薄弱,并且还有以下的缺点:
- 没有模块系统;
- 标准库很少;
- 没有标准接口;
- 缺乏包管理系统;
commonJS规范的出现为JavaScript制定了一个美好的愿景,希望JavaScript能够在任何地方运行。
在commonJS规范中涵盖了模块,二进制,Buffer,字符集编码,I/O流,进程环境,文件系统,套接字,单元测试,web服务器网关接口,包管理等。
node借鉴commonJS的Modules规范实现了一套非常易用的模块系统,NPM对Packages规范的完好支持使得Node应用在开发过程中事半功倍。
a. commonJS的模块规范
commonJS对模块的定义十分简单,主要分为模块引用,模块定义,模块标识等3个部分。
模块引用
在commonJS规范中存在一个require()方法,这个方法接受模块标识,依次引入一个模块的API到当前上下文中。1
var math = require('math');
模块定义
在模块中,上下文提供requier()方法来引入外部模块。对应引入的功能,上下文提供了exports对象用于导出当前模块的方法或者变量,并且它是唯一导出的出口。在模块中还存在一个module对象,他代表模块本身,而exports是module的属性。
1 | //导出 |
- 模块标识
模块标识就是传递给require()方法的参数,他必须是符合小驼峰命名的字符串,或者以. , ..开头的相对路径,或者绝对路径。可以省略文件名后缀
.js。
b. node的模块实现
在node中引入模块需要经历如下3个过程:
- 路径分析;
- 文件定位;
- 编译执行;
在node中模块分为两类: 一类是node提供的模块,称为核心模块;另一类是用户编写的模块,称为文件模块。
核心模块: 在node源代码的编译过程中,编译进了二进制执行文件中。在node进程启动的时候,部分核心模块就被直接加载进了内存中,所以这部分核心模块引入时。文件定位和编译执行这两个步骤可以省略掉,并且在路径分析中优先判断,所以他的加载速度是最快的。
文件模块: 是在运行时动态加载的,需要完整的路径分析,文件定位,编译执行的过程,速度比核心模块慢。
c. npm的模块加载机制:
如果require的是绝对路径文件,查找不会去遍历每个node_modules目录, 其速度最快。
- 从module.paths数组中(由当前执行文件目录到磁盘根目录)取出第一个目录作为查找基准;
- 直接从目录中查找该文件,如果存在则结束查找,如果不存在则进行下一条查找;
- 尝试添加.js,.node, .json后缀之后查找,如果存在文件则结束查找,如果不存在则进行下一条查找;
- 尝试将require的参数作为一个包来进行查找,读取目录下的package.json文件,取得Main参数指定的文件;
- 尝试查找该文件,如果存在则结束查找,如果不存在则进行第三条查找;
- 如果继续失败,则取出module.paths数组中的下一目录作为基准查找,循环1-5个步骤;
- 如果继续失败,循环1-6个步骤,直到module.paths数组中的最后一个值;
- 如果继续失败,则抛出异常。