模块化概念

模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、可分解和可更换的单元。

在模块化时,要遵守固定的规则(模块化规范),把一个大文件拆成独立并互相依赖的多个小模块。

进行模块化拆分的好处:

  • 提高了代码的复用性。
  • 提高了代码的可维护性。
  • 可以实现按需加载。
  • 遵守同样的模块化规范,有利于降低沟通的成本,极大地方便了各个模块之间的相互调用,利人利己。

Node.js 模块化

Node.js中根据模块来源的不同,将模块分为了3大类,分别是:

  • 内置模块:由Node.js官方提供,例如fspathhttp等。

  • 自定义模块:用户自定义的模块。在Node.js中,用户创建的每个.js文件,都属于自定义模块。

    在将.js文件加载为模块时,需要指定其路径(.js后缀可以省略,也可以加上):

    1const myModule = require('./custom')
    
  • 第三方模块:由其他用户开发的模块,并非官方提供或用户创建的模块。使用第三方模块前需要先进行下载(例如使用npm安装的模块)。

注:使用require()方法加载模块时,会同时执行被加载模块中的代码。

模块作用域

模块作用域是指在模块中定义的变量、方法等成员,只能在当前模块内被访问。

模块作用域可以防止全局变量污染的问题。

CommonJS 模块化规范

Node.js遵循了CommonJS模块化规范,CommonJS规定了模块的特性和各模块之间相互依赖的方式。

CommonJS规定:

  • 每个模块内部,module变量代表当前模块。
  • module变量是一个对象,它的exports属性(即module.exports)是对外的接口。
  • 加载某个模块,其实是加载该模块的module.exports属性。require()方法用于加载模块。

Module 对象

module对象存放了当前模块中的基本信息。在一个空的js文件中打印module对象的结果如下:

 1Module {
 2  id: '.',
 3  exports: {},
 4  parent: null,
 5  filename: '/root/study-js/modularization/module-object.js',
 6  loaded: false,
 7  children: [],
 8  paths: [ 
 9      '/root/study-js/modularization/node_modules',
10      '/root/study-js/node_modules',
11      '/root/node_modules',
12      '/node_modules' 
13    ] 
14}

module对象的属性:

  • filename:当前模块文件的全路径名。
  • exports:向外共享成员、接口。

向外共享模块内成员

在自定义模块时,可以使用module.exports对象,将模块内的成员向外部的其他模块共享,供外界使用。在外界使用require()方法导入模块时,require()方法提供的就是module.exports所指向的对象。

module.exports对象挂载成员时,有两种方式:

  1. module.exports对象创建成员:

     1// 在 module.exports 对象上挂载属性
     2module.exports.username = 'Linner'
     3
     4// 在 module.exports 对象上挂载方法
     5module.exports.hello = function () {
     6    console.log('Hello Orther Module!');
     7}
     8
     9// 挂载常量属性
    10const COLOR = 'black'
    11module.exports.COLOR = COLOR
    
  2. 定义一个module.exports对象:

    1// 定义 module.exports 对象
    2module.exports = {
    3    username: 'Linner',
    4    hello () {
    5        console.log('Hello Orther Module!');
    6    },
    7    COLOR: 'black'
    8}
    

使用这两种方式来挂载,区别不大,可以在其它模块中打印输出进行观察(假设创建的.js文件名为module-exports.js):

1const me = require('./module-exports');
2
3// 打印其他模块的 module.exports
4console.log(me);

向外共享模块作用域中的成员,除了使用module.exports对象外,还可以使用exports对象。默认情况下,exportsmoudle.exports指向的是同一个对象。最终共享的结果,是以module.exports指向的对象为准。

exports-object.js中编写:

 1exports.username = 'Linner'
 2exports.age = null
 3exports.hello = function () {
 4    console.log('Hello!');
 5}
 6
 7// 验证 exports 和 module.exports 指向的是否为同一个对象
 8console.log('exports', exports);
 9console.log('module.exports', module.exports);
10console.log('Are they equal?', exports === module.exports);
11
12/**
13 * 如果直接为 exports 对象赋值
14 * 此时 exports 和 module.exports 指向的就不是同一个对象了
15 */
16exports = {
17    username: 'Zhangsan',
18    age: '20'
19}
20console.log('Assign a value to the exports:');
21console.log('exports', exports);
22console.log('module.exports', module.exports);
23console.log('Are they equal?', exports === module.exports);

然后在test-exports.js中进行测试:

1const eo = require('./exports-object');
2console.log("The module.exports in exports-object:", eo);

运行test-exports.js输出结果如下:

1exports { username: 'Linner', age: null, hello: [Function] }
2module.exports { username: 'Linner', age: null, hello: [Function] }
3Are they equal? true
4Assign a value to the exports:
5exports { username: 'Zhangsan', age: '20' }
6module.exports { username: 'Linner', age: null, hello: [Function] }
7Are they equal? false
8The module.exports in exports-object: { username: 'Linner', age: null, hello: [Function] }

exports-object.js中添加新的代码:

 1/* 省略其它代码... */
 2
 3module.exports = {
 4    username: 'Lisi',
 5    age: '23',
 6    gender: 'man'
 7}
 8console.log('Assign a value to the module.exports:');
 9console.log('exports', exports);
10console.log('module.exports', module.exports);
11console.log('Are they equal?', exports === module.exports);

再次运行test-exports.js,输出结果如下:

 1exports { username: 'Linner', age: null, hello: [Function] }
 2module.exports { username: 'Linner', age: null, hello: [Function] }
 3Are they equal? true
 4Assign a value to the exports:
 5exports { username: 'Zhangsan', age: '20' }
 6module.exports { username: 'Linner', age: null, hello: [Function] }
 7Are they equal? false
 8Assign a value to the module.exports:
 9exports { username: 'Zhangsan', age: '20' }
10module.exports { username: 'Lisi', age: '23', gender: 'man' }
11Are they equal? false
12The module.exports in exports-object: { username: 'Lisi', age: '23', gender: 'man' }

总结:如果对exportsmodule.exports只进行添加成员对象的操作,不直接给它们赋值,那么exportsmodule.exports指向的始终都是同一个对象。如果对exportsmodule.exports进行了赋值操作,那么exportsmodule.exports指向的就不是同一个对象,但是向外共享的成员还是以module.exports为准

注:为了防止混乱,不要再同一个模块中同时使用exportsmodule.exports。建议使用module.exports