百分百源码网-让建站变得如此简单! 登录 注册 签到领金币!

主页 | 如何升级VIP | TAG标签

当前位置: 主页>网站教程>JS教程> Node中模块实现历程的具体介绍(附示例)
分享文章到:

Node中模块实现历程的具体介绍(附示例)

发布时间:09/01 来源:未知 浏览: 关键词:
本篇文章给大家带来的内容是关于Node中模块实现历程的具体介绍(附示例),有必然的参照 价值,有需要的伴侣可以参照 一下,但愿对你有所帮忙。

CommonJS 定义了 module、exports 和 require 模块标准,Node.js 为了实现这个简便的标准,从底层 C/C++ 内建模块到 JavaScript 中心模块,从途径剖析、文件定位到编译施行,经历了一系列复杂的历程。简便的理解 Node 模块的道理,有益于我们从新认识基于 Node 搭建的框架。

一、CommonJS 模块标准

CommonJS 标准或标准简便来说是一种理论,它盼望 JavaScript 可以具备跨宿主环境施行的能力,不仅可以开发客户端利用,还可以开发效劳端利用、命令行工具、桌面图形界面利用等。

CommonJS 标准对模块的定义分为三个部分:

模块定义

在模块中存在module对象代表模块本身,模块上下文供给exports属性 ,将办法挂载在exports对象上即可以定义输出方式,例如:

    // math.js
   exports.add = function(){ //...}

模块援用

module供给require()办法引入外部模块的 API 到当前的上下文中:

   var math = require('math')
模块标识

模块标识实际就是传递给require()办法中的参数,可以是按小驼峰(camelCase)命名的字符串,也可以是文件途径。

Node.js 借鉴了 CommonJS 标准的设计,特殊是 CommonJS 的 Modules 标准,实现了一套模块系统,同时 NPM 实现了 CommonJS 的 Packages 标准,模块和包组成了 Node 利用开发的根基。

二、Node 模块加载道理

上述模块标准看起来十分简便,只要module、exports和require,但 Node 是怎样实现的呢?

需要经历途径剖析(模块的完全途径)、文件定位(文件扩展名或名目)、编译施行三个步骤。

2.1 途径剖析

回忆require()接收 模块标识 作为参数来引入模块,Node 就是基于这个标识符停止途径剖析。不一样的标识符采纳的剖析方式是不一样的,主要分为一下几类:

Node 供给的中心模块,如 http、fs、path

中心模块在 Node 源码编译时存为二进制施行文件,在 Node 启动时直接加载到内存中,途径剖析中优先推断,所以加载速度很快,并且也不消后续的文件定位和编译施行。

假如想加载与中心模块同名的自定义模块,如自定义 http 模块,那必需选用不一样标记符或改用途径方式。

途径情势的文件模块,.、..相对途径模块和/绝对途径模块

以.、..或/开端的标识符都会当做文件模块处置,Node 会将require()中的途径转为真实途径作为索引,然后编译施行。

由于文件模块明白了文件位置,所以缩短了途径剖析时间,加载速度仅慢与中心模块。

自定义模块,即非途径情势的文件模块

即不是中心模块,也不是途径情势的文件模块,自定义文件是非凡的文件模块,在途径查寻时 Node 会逐级查寻该模块途径中的途径。
模块途径查寻战略示例如下:

// paths.js
console.log(module.paths)

// Terminal
$ node paths.js
[ '/Users/tong/WebstormProjects/testNode/node_modules',
'/Users/tong/WebstormProjects/node_modules',
'/Users/tong/node_modules',
'/Users/node_modules',
'/node_modules' ]

从上述示例输出的模块途径数组可以看出,模块的查寻时沿当前途径向上逐级查寻node_modules名目,直到目标途径为止,相似 JS 原型链或作用域链。途径越深速度越慢,所以自定义模块加载速度最慢。

缓存优先机制:Node 会对引入过的模块停止缓存以提高机能,不一样于阅读器缓存的是文件,Node 缓存的是编译和施行后的对象,所以require()对雷同模块的二次加载采纳缓存优先的方式。这个缓存优先是第一优先级的,比中心模块的优先级要高!

2.2 文件定位

模块途径剖析完成后是文件定位,主要包罗文件扩展名的剖析、名目和包的处置。为了表达的更清楚,将文件定位分为四个步骤:

step1: 补充扩展名

平常require()中的标识符是不包括文件扩展名的,这种状况下,Node会依照 .js、.json、.node 的次序尝试补充扩展名。

在尝试补充扩展名时,需要调取 fs 模块同步堵塞式推断文件可否存在,所以这里晋升机能的小技巧,就是 .json 和 .node 文件传递给require()时带上扩展名会加快一些速度。

step2: 名目处置查寻 pakage.json

假如补充扩展名后没有寻到对应文件,但是得到了一个名目,此时 Node会将名目当做一个包处置。根据 CommonJS 包标准的实现,Node 会在名目下查寻pakage.json(包描写文件),通过JSON.parse()解析成包描写对象,从中取main属性指定的文件名定位。

step3: 连续默许查寻 index 文件

假如没有pakage.json或者main属性指定的文件名错误,那 Node 会将 index 当做默许文件名,顺次查寻 index.js、index.json、index.node

step4: 进入下一个模块途径

在上述名目剖析历程中没有成功定位时,自定义模块按途径查寻战略进入上一层node_modules名目,当整个模块途径数组遍历完毕后没有定位到文件,则会抛出查寻失败非常。

缓存加载的优化战略使得二次引入不需要途径剖析、文件定位、编译施行这些历程,并且中心模块也不需要文件定位的历程,这大大提高了再次加载模块时的效力

2.3 编译施行

Node 中每个模块都是一个对象,在详细定位到文件后,Node 会创建该模块对象,然后按照途径载入并编译。不一样的文件扩展名载入办法为:

.js 文件: 通过 fs 模块同步读取后编译施行.json 文件: 通过 fs 模块同步读取后,用JSON.parse()解析并返回结果.node 文件: 这是用 C/C++ 写的扩展文件,通过process.dlopen()办法加载最后编译生成的其他扩展名: 都被当做 js 文件载入

载入成功后 Node 会调取详细的编译方式将文件施行后返回给调取者。关于 .json 文件的编译最简便,JSON.parse()解析得到对象后直接赋值给模块对象的exports,而 .node 文件是C/C++编译生成的,Node 直接调取process.dlopen()载入施行就可以,下面重点介绍 .js 文件的编译:

在 CommonJS 模块标准中有module、exports 和 require 这3个变量,在 Node API 文档中每个模块还有 __filename、__dirname这两个变量,但是在模块中没有定义这些变量,那它们是如何发生的呢?

事实上在编译历程中,Node 对每个 JS 文件都被停止了封装,例如一个 JS 文件会被封装成如下:

(function (exports, require, module, __filename, __dirname) {
    var math = require('math')
    export.add = function(){ //... }
})

第一每个模块文件之间都停止了作用域隔离,通过vm原生模块的runInThisContext()办法(相似 eval)返回一个详细的 function 对象,最后将当前模块对象的exports属性、require()办法、模块对象本身module、文件定位时得到的完全途径__filename和文件名目__dirname作为参数传递给这个 function 施行。模块的exports属性上的任何办法和属性都可以被外部调取,其余的则不成被调取。

至此,module、exports 和 require的流程就介绍完了。

曾经困惑过,每个模块都可以使用exports的状况下,为什么还必需用module.exports。

这是由于exports在编译历程中时通过形参传入的,直接给exports形参赋值只改动形参的援用,不克不及改动作用域外的值,例如:

let change = function (exports) {
  exports = 100
  console.log(exports)
}

var exports = 2
change(exports) // 100
console.log(exports) // 2

所以直接赋值给module.exports对象就不会改动形参的援用了。

编译成功的模块会将文件途径作为索引缓存在 Module._cache 对象上,途径剖析时优先查寻缓存,提高二次引入的机能。

三、Node 中心模块

总结来说 Node 模块分为Node供给的中心模块和会员编写的文件模块。文件模块是在运转时动态加载,包罗了上述完全的途径剖析、文件定位、编译施行这些历程,中心模块在Node源码编译成可施行文件时存为二进制文件,直接加载在内存中,所以不消文件定位和编译施行。

中心模块分为 C/C++ 编写的和 JavaScript 编写的两部分,在编译所有 C/C++ 文件此前,编译程序需要将所有的 JavaScript 中心模块编译为 C/C++ 可施行代码,编译成功的则放在 NativeModule._cache对象上,明显和文件模块 Module._cache的缓存位置不一样。

在中心模块中,有些模块由纯 C/C++ 编写的内建模块,主要供给 API 给 JavaScript 中心模块,平常不克不及被会员直接调取,而有些模块由 C/C++ 完成中心部分,而 JavaScript 实现封装和向外输出,如 buffer、fs、os 等。

所以在Node的模块类型中存在依靠层级关系:内建模块(C/C++)—> 中心模块(JavaScript)—> 文件模块。

使用require()十分的利便,但从 JavaScript 到 C/C++ 的历程十分复杂,总结来说需要经历 C/C++ 层面内建模块的定义、(JavaScript)中心模块的定义和引入乃至(JavaScript)文件模块的引入。

四、前端模块标准

对照前后端的 JavaScript,阅读器端的 JavaScript 需要经历从统一个效劳器端分发到多个客户端施行,通过网络加载代码,瓶颈在于宽带;而效劳器端 JavaScript 雷同代码需要屡次施行,通过磁盘加载,瓶颈在于 CPU 和内存,所之前后端的 JavaScript 在 Http 两端的职责完全不消。

Node 模块的引入几乎是同步的,而前端模块假如同步引入,那足本加载需要太长的时间,所以 CommonJS 为后端 JavaScript 拟定的标准不适合前端。而后显现 AMD 和 CMD 用于前端利用场景。

4.1 AMD 标准

AMD 即异步模块定义(Asynchronous Module Definition),模块定义为:

define(id?, dependencies?, factory);

AMD 模块需要用define明白定义一个模块,其中模块id与依靠dependencies是可选的,factory的内容就是实际代码的内容。例如指定一些依靠到模块中:

define(['dep1', 'dep2'], function(){
    // module code
});

require.js 实现 AMD 标准的模块化,感乐趣的可以查看 require.js 的文档。

4.2 CMD 标准

CMD 模块的定义愈加简便:

 define(factory);

定义的模块同 Node 模块一样是隐式包装,在依靠部分支撑动态引入,例如:

 define(function(require, exports, module){
     // module code
 });

requireexportsmodule通过形参传递给模块,需要依靠模块时直接使用require()引入。

sea.js 实现 AMD 标准的模块化,感乐趣的可以查看 sea.js 的文档。

以上就是Node中模块实现历程的具体介绍(附示例)的具体内容,更多请关注百分百源码网其它相关文章!

打赏

打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

百分百源码网 建议打赏1~10元,土豪随意,感谢您的阅读!

共有150人阅读,期待你的评论!发表评论
昵称: 网址: 验证码: 点击我更换图片
最新评论

本文标签

广告赞助

能出一分力是一分吧!

订阅获得更多模板

本文标签

广告赞助

订阅获得更多模板