Node.js 的module 系统

  相较于原生的JavaScript,不一样的JavaScript文件之间很难共享变量。有鉴于此,Node.js在JavaScript的基础上进行了扩充,引入了require,exports,module三个global object。javascript

1、absolute module 和 relative module

  Smashing Node.js 的做者将node.js 中的modules 分红了两类,一类是absolute modules,一类是 relative modules。java

  <1> absolute modules,指的是是node core自带的重要modules,如http,fs等,咱们使用这些modules时,只须要 require(‘module_name’)便可;还包括用npm安装的第三方module,这些module 默认安装的位置是./node_modules/ 路径下,使用这些modules时,一样只须要require(‘module_name’)便可。可是,在package.json文件中要添加这些module的name,以便使用npm安装。node

  <2> relative modules,指的是咱们本身写的modules,这些modules通常存在于工程文件夹内部,引用时咱们须要以require(‘相对路径/module_name’)的方式引用。相对路径,指的是这些modules相对工程文件夹的存放的位置。npm

  注意:即便本身编写的modules位于package.json相同的位置,也须要使用require('./module_name')来引用,不然会按照第一种方式寻找且出错。json

  经过这些modules,咱们能够将复杂的node.js javascript代码分割在不一样的文件中,简化咱们的工程的管理与维护。缓存

 

  每一个module中的变量,除了用exports或者module.exports 声明的属性,都是局部变量,只用用于本模块内。 ide

 

2、什么是require?

 

Modules are cached after the first time they are loaded. This means (among other things) that every call to require('foo') will get exactly the same object returned, if it would resolve to the same file.

Multiple calls to require('foo') may not cause the module code to be executed multiple times. This is an important feature. With it, "partially done" objects can be returned, thus allowing transitive dependencies to be loaded even when they would cause cycles.

If you want to have a module execute code multiple times, then export a function, and call that function.

  这里的require,跟咱们普通的理解的加载不同,除了把被加载模块的经过module.exports 导出的object或者function放到执行环境中去,也会在加载的过程当中执行这个模块的其余代码,由于js是解释型语言,语意上是一句一句执行的。函数

  node.js 使用require引入模块时,会历经如下四个步骤。因为核心模块已经被编译执行过,因此核心模块只会执行第四步。对于用户编写的非核心模块,如下四步都会执行。因此,若是模块内有须要执行的js语句,会在第一次require的时候执行。oop

  • 路径分析
  • 文件定位
  • 编译执行
  • 加入内存

  因为node.js 的缓存机制,每一个模块都只会被加载一次,也即执行一次,即便屡次require。如要想这个module的代码被屡次执行,要将这些代码放到function中去,并经过module.exports 引出。测试

  <1> 简单的require

module_a.js

1
name_a = 'a'; //全局做用域
var a = 'a'; //执行做用域仅限于 module_a.js中
2 console.log(name_a); //第一次require的时候执行。 3 //console.log(name_b);//会出错,name_b未定义
module_b.js

1
name_b ='b' 2 console.log(name_b);
main.js

1
require('./module_a'); //relative module,须要给出module的路径, module_a除了被导出的object,未被导出的代码也会被执行。 2 require('./module_b'); 3 //console.log(a); --> 出错,不能访问未被引出来的模块变量。 4 console.log(name_a); 5 console.log(name_b);

<2> circular require

  circular require:顾名思义,循环导入,a.js 要导入b.js, 同理b.js 也要导入a.js。咱们先看一下官方给出的例子

1 filename: a.js
2 
3 console.log('a starting');
4 exports.done = false;
5 var b = require('./b.js');
6 console.log('in a, b.done = %j', b.done);
7 exports.done = true;
8 console.log('a done');
1 filename: b.js
2 
3 
4 console.log('b starting');
5 exports.done = false;
6 var a = require('./a.js');
7 console.log('in b, a.done = %j', a.done);
8 exports.done = true;
9 console.log('b done');
1 filename: main.js
2 
3 console.log('main starting');
4 var a = require('./a.js');
5 var b = require('./b.js');
6 console.log('in main, a.done=%j, b.done=%j', a.done, b.done);

When main.js loads a.js, then a.js in turn loads b.js. At that point, b.js tries to load a.js. In order to prevent an infinite loop an unfinished copy of the a.js exports object is returned to the b.js module. b.js then finishes loading, and its exports object is provided to the a.js module.

  当main.js 加载a.js的时候,a.js 又会加载b.js。b.js加载的过程当中又会尝试加载a.js。为了防止陷入无限的循环之中,b.js会直接引用已经加载可是尚未彻底加载的a.js(require ('b.js') 以前的代码)。当b.js加载并执行完成后,a.js才会接着加载执行,而后main.js加载执行。

测试结果

$ node main.js
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done=true, b.done=true

 

3、module.exports和exports

  每个模块(js文件)都有一个局部变量:module,这个module指向当前的模块。这个module有多个成员field,其中一个就是module.exports。module.exports 是一个javascript object,会被引出,返回给经过require加载的模块。因此咱们只须要把要返回的object或者function赋给module.exports,就能够返回任意咱们想要返回的object或者function了。

  

 <1> 何时该用exports,何时该用module.exports

 1 The exports variable that is available within a module starts as a reference to module.exports.
As with any variable, if you assign a new value to it, it is no longer bound to the previous value. 2 3 4 function require(...) { 5 // ... 6 function (module, exports) { 7 // Your module code here 8 exports = some_func; // re-assigns exports, exports is no longer 9 // a shortcut, and nothing is exported. 10 module.exports = some_func; // makes your module export 0 11 } (module, module.exports); 12 return module; 13 } 14 15 As a guideline, if the relationship between exports and module.exports seems like magic to you, ignore exports and only use module.exports.

  上面是摘抄自官方的关于exports和module.exports 的解释。 exports就是模块内对module.exports 的一个引用,因此咱们给这个引用增长任何的属性(exports.field = value),最后在其余模块require的时候均可以访问到。

  可是若是给直接给exports 赋予某个object(exports = object),试图返回一个object的话,是不能经过exports来返回的,由于 exports = object这个过程就会把exports的指向给改变,再也不指向module.exports。而咱们用require 加载的object是module.exports 指向的object。

 

  <2> 经过exports 返回对象的实例:

  默认,每一个module经过exports返回一个空的对象实例,咱们能够经过给该module的exports增长属性来改变该module返回的对象实例。

1 module_a.js
2 
3 exports.name = ‘john’;  
4 exports.data = ‘this is some data’;
5 var privateVariable = 5;
6 exports.getPrivate = function () {
7 return privateVariable;
8 };
1 index.js
2 
3 var a = require(‘./module_a’);  //注意,exports默认返回的是一个已经建立好的对象实例,因此能够直接调用属性,而不须要new。
4 console.log(a.name);
5 console.log(a.data);
6 console.log(a.getPrivate());

<3> 经过module.exports 返回constructor 函数:

  虽然经过module.exports能够传递建立好的对象实例,但有时候咱们但愿在传递时能控制这个对象的建立过程,此时咱们须要传递constructor函数:

1 module.exports = Person;
2 
3 function Person(name){
4     this.name = name; 
5 }
6 
7 Person.prototype.talk = function() {
8     console.log('my name is ', this.name);
9 }
1 var Person = require(‘./person’);
2 var john = new Person(‘john’); //Person 为constructor函数,必须new以后才能使用
3 john.talk();

相关文章
相关标签/搜索