Node.js 模块加载机制 Require()

require()

不要把这里的Require()和RequireJS混为一谈。不过有意思的是,Typescript的模块定义,甚至同时支持这两种模块机制。

导入和使用外部模块,只是简单的一句require(),看看angular/material/docs下的编译文件gulpfile.js的代码片段。对模块导入和使用有个直观的感觉。

var gulp = require('gulp');
var concat = require('gulp-concat');
var fs = require('fs');

... 

//对模块gulp的使用
gulp.task('demos', function() { ...  

//对模块gulp-concat的使用
gulp.src([
    'node_modules/angularytics/dist/angularytics.js',
    'dist/docs/js/*/.js'
  ])
    .pipe(concat('docs.js'))

//对模块fs的使用
fs.writeFileSync(dest + '/demo-data.js', file);

gulp.task 用于定义了一个任务;cancat用于合并文件;fs是一个对磁盘文件操作的模块。可以看出,有模块的引入,代码更为清晰而明确,这些常用模块相当于对基本语言功能的扩展。

这里,关键词require()把一切联系在一起。那么这句简单的语句背后发生了什么事情呢?

require其实不是一个语言的关键词,在文章后面的研究,我们就可以看到。
还没有使用过require()或者对它实现机制不感兴趣的开发人员,可以略个这一部分。确实,后面实现机制不太影响使用。
以下大部分内容都来自原文: How require() Actually Works

因为NodeJS是开源的,我们可以追溯require()到node的核心代码中去。但是,我们找到的不是一个简单的函数,而是一个文件module.js。这个文件实现了node的整个模块加载系统。涵盖的过程有加载、编译和缓存。而我们使用的require()只是其冰上一角。

module.js

function Module(id, parent) {
    this.id = id;
    this.exports = {};
    this.parent = parent;
    //...

我们可以看到module.js首先定义了一个类型(函数)Module。这个类型有两个功能。一个,它是所有模块的基类,之后每个模块都是这个Module的一个实例。这也是我们前面探讨的module.exports最终来源。 这个Module的第二个功能就是完成Node模块的加载过程。我们使用的require()最终就是调用module.require方法,而这个方法又调用了另外一个内部方法Module._load。最终的这个load方法才是真正加载模块文件的地方,也就是我么将要分析研究的。

Module._load

Module._load = function(request, parent, isMain) {
    //1. Check Module._cache for the cached module.
    //2. Create a new module instance if cache empty
    //3. Save it to the cache
    //4. Call module.load() with your the given filename, this will call module.compile() after reading the file contents.
    //5. If there was error loading /parsing the file, delete the bad module from cache.
    //6. return module.exports
};

Module._load负责装载新模块和管理模块的缓存。缓存机制在每个模块载入时减少重复读取文件,从而提高系统性能。另外,共享模块实例还可以使得单例模块在整个项目中保留状态。 如果在缓存中没有找到该模块,Module._load就会为该文件创建一个新的Module实例。并用该实例读取文件内容,然后发送给Module._compile。 注意到在上面第6步,返回了module.exports。这个返回语句可以解释,为什么在你的模块文件中要把公开的接口(方法)赋给module.exports(或者别名exports);这也解释了,require()返回的变量,可以直接调用导入模块的方法。到此,可以看到没有任何神奇或特别的地方。

module._compile

Module.prototype._compile = function(content, filename) {
    // 1. Create the standalone require function that calls module require.
    // 2. Attach other helper methods to require.
    // 3. Wraps the JS code in a function that provides require, module, etc. variables locally to the module scope.
    // 4. Run that function.

这个方法,一开始就创建require函数,这就是我们非常熟悉的那个require()(模块装载机制不仅仅有载入模块的处理,也有导入和调用的流程,这里可以看作调用方的流程,如我们提到的main.js)。而这个个函数本身只是简单的封装了Module.require和添加了一些帮助属性和方法,如下:

  • require() 就是我们使用的require()
  • require.main 主模块
  • require.cache 所有缓存的模块
  • require.extensions 不同文件类型(后缀)的编译方法

在构建require之后,所有原文件的代码被封装的一个新函数中,这个函数把require,module,exports作为参数。 这也可以解释为什么我们可以直接调用require(),为什么exports是module.exports的别名。

(function(exports,require, module, __filename, __dirname){
    //原模块文件的所有代码注入在这
});
了解模块
模```
式(Module Pattern)的人,很容易看出这段看时毫无意义的重新分帐,就是模块模式,就是为了防止模块文件中的定义污染系统的命名空间(记住javascript时全局变量)。 最后,这个新创建的函数直接被运行,这其实也是完整模块模式的一部分,最后那对空括弧就是运行部分:

```javascript
(function(...){
    //...
})();

如果不熟悉模块模式(Module Patter),可以看看我另外一篇文章深入探索AngularJS的一个章节《模块模式 - Module Pattern》

小结

至此,我们就走完了模块加载的全部流程。从创建模块module.exports到调用require,以及调用实现的内部过程。虽然这些对你代码还没有任何影响,原来该怎么做还怎么做。但是,可以让你写同样代码时,心里更有底,不再强行记忆模块的语法。最重要的事通过学习良好的代购架构,提高自己的架构水平。
文章转载自 开源中国社区 [http://www.oschina.net]

时间: 2024-12-20 14:41:48

Node.js 模块加载机制 Require()的相关文章

Node.js模块加载详解_node.js

JavaScript是世界上使用频率最高的编程语言之一,它是Web世界的通用语言,被所有浏览器所使用.JavaScript的诞生要追溯到Netscape那个时代,它的核心内容被仓促的开发出来,用以对抗Microsoft,参与当时白热化的浏览器大战.由于过早的发布,无可避免的造成了它的一些不太好的特性. 尽管它的开发时间很短,但是JavaScript依然具备了很多强大的特性,不过,每个脚本共享一个全局命名空间这个特性除外. 一旦Web页面加载了JavaScript代码,它就会被注入到全局命名空间,

linux 3.10 的模块加载机制

问题描述 linux 3.10 的模块加载机制 跪求有关linux 3.10的模块自动加载有关的见解,资料,连接等,中英文都可---以 灰常感谢各位大神咯!!! 解决方案 http://www.cnblogs.com/image-eye/archive/2011/08/19/2145858.html

概述如何实现一个简单的浏览器端js模块加载器_javascript技巧

在es6之前,js不像其他语言自带成熟的模块化功能,页面只能靠插入一个个script标签来引入自己的或第三方的脚本,并且容易带来命名冲突的问题.js社区做了很多努力,在当时的运行环境中,实现"模块"的效果. 通用的js模块化标准有CommonJS与AMD,前者运用于node环境,后者在浏览器环境中由Require.js等实现.此外还有国内的开源项目Sea.js,遵循CMD规范.(目前随着es6的普及已经停止维护,不论是AMD还是CMD,都将是一段历史了) 浏览器端js加载器 实现一个简

js模块加载seajs框架的教程

有些时候,我们不得不写一些前段的东西,但是总感觉前段加载的太乱,管理起来不方便,加载也是问题,其实以前不会写页面,但是偶然机会开始一直写页面+写程序了.我的使用搭配方案是 seajs + angularjs + bootstrap + jquery + grunt + less . 为什么使用的是 Grunt 其实很简单,因为我也需要编译 less 文件,实施监控文件的改动和对 html . js .css 进行压缩.当然不仅仅是这些,功能很强大,我就不一一介绍了.只是个人喜好而已.感觉这东西跟

JavaScript循环加载模块的方法及模块加载技术思考

"循环加载"(circular dependency)指的是,a脚本的执行依赖b脚本,而b脚本的执行又依赖a脚本.     // a.js    var b = require('b');     // b.js    var a = require('a'); 通常,"循环加载"表示存在强耦合,如果处理不好,还可能导致递归加载,使得程序无法执行,因此应该避免出现. 但是实际上,这是很难避免的,尤其是依赖关系复杂的大项目,很容易出现a依赖b,b依赖c,c又依赖a这样

简单模拟node.js中require的加载机制_node.js

一.先了解一下,nodejs中require的加载机制 1.require的加载文件顺序 require 加载文件时可以省略扩展名:           require('./module');      // 此时文件按 JS 文件执行           require('./module.js');      // 此时文件按 JSON 文件解析           require('./module.json');      // 此时文件预编译好的 C++ 模块执行          

学习Node.js模块机制_node.js

一.CommonJS的模块规范 Node与浏览器以及 W3C组织.CommonJS组织.ECMAScript之间的关系 Node借鉴CommonJS的Modules规范实现了一套模块系统,所以先来看看CommonJS的模块规范. CommonJS对模块的定义十分简单,主要分为模块引用.模块定义和模块标识3个部分. 1. 模块引用 模块引用的示例代码如下: var math = require('math'); 在CommonJS规范中,存在require()方法,这个方法接受模块标识,以此引入一

加快页面的载入速度:异步模块加载器In.js

文章简介:用In.js颗粒化管理.加载你的Javascript模块. 近一年来,国内外都十分热衷于异步加载的研究,为了加快页面的载入速度,无阻塞加载Javascript的方法和框架成为了前端开发的焦点和亮点之一. 国外的像基于jQuery的RequireJs,YUI Loader,LabJs,RunJs,国内也有淘宝的SeaJs,豆瓣的DoJs等,这些都是一些十分优秀的模块加载器.但是本文将会向大家介绍一个新的开源的轻量级"多线程"异步模块加载器In.js,In的开发借鉴了Do的一些思

异步-require.js配置文件加载顺序?

问题描述 require.js配置文件加载顺序? require.jsdata-main=""../js/main""我想引入的是独立的配置文件,下面在写一行加载页面逻辑比如a.js但data的方式是异步加载,导致a比main先加载,有什么办法吗?我不用data的方式另起一行的话是可以 解决方案 require.js 配置文件研究 解决方案二: require(['a']function(){//先请求完a,再回调里面在请求湖区main,不用直接script加载,会