前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《深入浅出Node.js》:node的模块规范与模块实现

《深入浅出Node.js》:node的模块规范与模块实现

作者头像
前端_AWhile
发布2019-08-29 13:10:38
1.1K0
发布2019-08-29 13:10:38
举报
文章被收录于专栏:前端一会前端一会

Node前言介绍

Node的目标是成为一个构建快速、可伸缩的网络应用平台,通过通信协议来组织许多Node,非常容易通过扩展来达成构建大型网络应用的目的。

Node作为后端JavaScript的运行平台,保留了前端浏览器JavaScript中那些熟悉的接口,没有改写语言本身的任何特性,依旧基于作用域和原型链,区别在于它将前端中广泛运用的思想迁移到服务器端。

Node支持异步I/O事件与回调函数单线程,并且跨平台

基于以上支持点,Node擅于应用的场景包括:I/O密集型CPU密集型分布式应用

Node使用模块化来组织JS代码,模块规范采用CommonJS规范

对于JavaScript语言本身来说,有几个方面的天然缺陷:

  • 没有模块系统。
  • 标准库较少。ES仅定义部分核心库,对于文件系统、I/O流等常见需求却没有标准API。
  • 没有标准接口。js中没有定义过如Web服务器或数据库之类的标准统一接口。
  • 缺乏包管理系统。这导致js应用中没有自动加载和安装依赖的能力。

在ES6中模块之前,CommonJS可以一定程度上弥补没有标准的缺陷。

CommonJS快速介绍

CommonJS对于模块的定义很简单,分为模块定义模块引用模块标识3个部分。

代码语言:javascript
复制
 1//math.js  模块定义文件
 2function add(){
 3    var sum = 0,
 4        i = 0,
 5        args = arguments,
 6        l = args.length;
 7    while( i<l ){
 8        sum += args[i++];
 9    }
10    return sum;
11}
12module.exports = {add};
13
14//test.js   模块引用文件,假设与math.js文件在同一目录下
15var math = require("./math");   // 字符串 ./math 就是模块标识;本行代码就是模块引用
16math.add(10, 2);
17
18// 执行test.js文件: node test.js
19// 打印:
20// 12

模块引用:在CommonJS规范中,存在require()方法,这个方法接受模块标识,以此引入一个模块的API到当前上下文中。

模块定义:在模块中,上下文提供require()方法来引入外部模块。对应引入的功能,上下文也提供了module.exports对象用于导出当前模块的方法和变量,并且它还是唯一导出的出口。这里的module是一个对象,表示模块本身,而exports就是它的属性。在Node中,一个文件就是一个模块,将方法挂载在exports对象上作为属性就能导出。然后在另一个文件中,通过require()方法引入模块后,就能调用定义的属性和方法了。

模块标识:它是传递给requrie()方法的参数,它必须是符合小驼峰命名的字符串,或者是以...开头的相对路径,或者绝对路径,它可以没有文件名后缀.js

CommonJS构建的这套模块导出和引入机制使得用户完全不要考虑变量污染。

Node的模块实现

Node中,也不会完全套用CommonJS规范的,而是有一定取舍,也增加些新的特性。对于module.exportsrequire()Node实现起来主要有三个步骤:路径分析、文件定位和编译执行。

Node中的模块包含两种:一类是由Node提供的模块叫核心模块;一类是用户编写的模块叫文件模块。

  • 其中核心模块在Node源代码的编译过程中,编译进了二进制执行文件。Node进程启动时核心模块就直接加载进内存中,所以当其被引入时,直接省去文件定位和编译执行两步,并且在路径分析中优先判断,所以其加载速度最快。
  • 而文件模块则是在运行时动态加载,需要执行完整三步,所以加载速度略慢。

Node通常优先从缓存中加载,不管要加载的是核心模块还是文件模块。区别仅在于核心模块的缓存检查先于文件模块的缓存检查。

在路径分析中,Node会基于require()方法中的模块标识符进行模块查找。模块标识符主要有以下几类:

  • 核心模块,如httpfspath等,加载速度最快
  • ...开始的相对路径文件模块
  • /开始的绝对路径文件模块
  • 非路径形式的文件模块,如自定义的connect模块

在文件定位中,首先会按照缓存加载的优化策略加载二次引入的模块,否则就按照首次加载策略执行文件定位。

最后就是编译执行阶段。当定位到具体文件后,Node会新建一个模块对象,然后根据路径载入并编译。对于不同的文件扩展名,其载入方法也不同:

  • .js文件。通过fs模块同步读取文件后编译执行
  • .node文件。这是用C/C++编写的扩展文件,通过dlopen()方法加载最后编译生成的文件
  • .json文件。通过fs模块同步读取文件后,用JSON.parse()解析返回结果
  • 其余扩展名文件,则者被当作.js文件载入。

这里补充下核心模块相关。核心模块又分为JavaScript核心模块和C/C++核心模块,后者又被称为内建模块。核心模块中有的模块全部由C/C++编写,部分是由C/C++完成核心部分,其他部分则由JavaScript实现包装或向外导出,以满足性能平衡需求。Node中的osfsbuffer等都是部分通过C/C++写的。

在Node的所有模块类型中,存在着这样的依赖层级关系:文件模块依赖核心模块,核心模块依赖内建模块。通常文件模块不推荐依赖内建模块,如需调用则直接调用核心模块即可,因为核心模块中都已基本封装了内建模块。

除了JavaScript模块外,Node中还可以写C/C++扩展模块,注意这与内建模块是不同的。C/C++扩展模块加载的是.node文件,Node会调用process.dlopen()来加载文件。使用C/C++扩展模块的好处是加载后不需要编译,直接执行之后就可以被外部调用了,加载速度略快于JavaScript模块。

以上简单介绍了Node中的模块:文件模块、核心模块、内建模块和C/C++扩展模块它们各自的区别,下面弄清下它们之间的调用关系:

C/C++内建模块属性最底层的模块,它属于核心模块,主要提供API给JavaScript核心模块和第三方JavaScript文件模块调用。如果不是很了解C/C++内建模块的,尽量避免使用process.binding()方法直接调用。

JavaScript核心模块主要扮演的职责有两类:一类是作为C/C++内建模块的封装层和桥接层,供文件模块调用;一类是纯粹的功能模块,它不需要跟底层打交道,但又非常重要。

文件模块通常由第三方编写,包括普通的JavaScript模块和C/C++扩展模块,主要调用方向为普通JavaScript模块调用扩展模块。

多模块兼容写法

Node使用JavaScript语言有一个很好的优点,那就是一些模块可以在前后端实现共用,这是因为很多API在各个宿主环境下都提供。但实际情况下,前后端的环境有时还是会有区别的。

可以看到Node的模块引入过程主要都是同步的,因为服务器端从磁盘加载资源,所以速度很快,加载的瓶颈在于CPU和内存等资源。而前端由于UI在初始化过程中用户体验的问题,应尽可能减少同步引入模块,避免阻塞,其加载瓶颈在带宽。

所以CommonJS规范更适合于后端,而前端的模块引入使用AMD规范更适宜,或者也可以使用CMD规范。我更习惯于用AMD规范。

为了让同一个模块可以运行在前后端,在写模块时就需要考虑兼容前端也实现模块规范的环境。为保持前后端一致性,类库代码可以包装在一个闭包内,这方面比较典型的就是JQuery了。下面实现一个简单的模块兼容示例,它将兼容Node、AMD、CMD和常见浏览器环境:

代码语言:javascript
复制
 1(function (name, factory) {
 2    // 检测上下文环境是否为AMD或CMD
 3    var hasDefine = typeof define === "function",
 4    // 检测上下文环境是否为Node,也就是支持CommonJS规范
 5        hasModule = typeof module !== "undefined" && module.exports;
 6
 7    if(hasDefine){
 8        // AMD环境或CMD环境
 9        define("method", [], factory);
10    }else if(hasModule) {
11        // 定义为普通Node模块
12        module.exports = factory();
13    }else {
14        //将模块的执行结果挂在window变量中,在浏览器中this指向window对象
15        this.name = factory();
16    }
17
18})("privateModule", function () {
19    var hello = function () {
20        return "Hello Nitx!";
21    }
22
23    return hello;
24})
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-01-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端小二 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Node前言介绍
  • CommonJS快速介绍
  • Node的模块实现
  • 多模块兼容写法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档