模块化概念

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

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

进行模块化拆分的好处:

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

Node.js 模块化

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

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

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

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

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

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

模块作用域

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

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

CommonJS 模块化规范

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

CommonJS规定:

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

Module 对象

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

Module {
  id: '.',
  exports: {},
  parent: null,
  filename: '/root/study-js/modularization/module-object.js',
  loaded: false,
  children: [],
  paths: [ 
      '/root/study-js/modularization/node_modules',
      '/root/study-js/node_modules',
      '/root/node_modules',
      '/node_modules' 
    ] 
}

module对象的属性:

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

向外共享模块内成员

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

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

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

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

    // 定义 module.exports 对象
    module.exports = {
        username: 'Linner',
        hello () {
            console.log('Hello Orther Module!');
        },
        COLOR: 'black'
    }
    

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

const me = require('./module-exports');

// 打印其他模块的 module.exports
console.log(me);

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

exports-object.js中编写:

exports.username = 'Linner'
exports.age = null
exports.hello = function () {
    console.log('Hello!');
}

// 验证 exports 和 module.exports 指向的是否为同一个对象
console.log('exports', exports);
console.log('module.exports', module.exports);
console.log('Are they equal?', exports === module.exports);

/**
 * 如果直接为 exports 对象赋值
 * 此时 exports 和 module.exports 指向的就不是同一个对象了
 */
exports = {
    username: 'Zhangsan',
    age: '20'
}
console.log('Assign a value to the exports:');
console.log('exports', exports);
console.log('module.exports', module.exports);
console.log('Are they equal?', exports === module.exports);

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

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

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

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

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

/* 省略其它代码... */

module.exports = {
    username: 'Lisi',
    age: '23',
    gender: 'man'
}
console.log('Assign a value to the module.exports:');
console.log('exports', exports);
console.log('module.exports', module.exports);
console.log('Are they equal?', exports === module.exports);

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

exports { username: 'Linner', age: null, hello: [Function] }
module.exports { username: 'Linner', age: null, hello: [Function] }
Are they equal? true
Assign a value to the exports:
exports { username: 'Zhangsan', age: '20' }
module.exports { username: 'Linner', age: null, hello: [Function] }
Are they equal? false
Assign a value to the module.exports:
exports { username: 'Zhangsan', age: '20' }
module.exports { username: 'Lisi', age: '23', gender: 'man' }
Are they equal? false
The 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