1. 什么是CommonJS?
CommonJS是一种模块化解决方案,为JavaScript语言提供了一种通用的模块标准。它被广泛应用于服务器端JavaScript开发中,而Nodejs正是基于CommonJS规范来实现模块化编程的。
在CommonJS规范中,一个模块就是一个JavaScript文件,定义了一组函数、对象或者变量,这些函数、对象或者变量可以被其他模块调用。同时,每个模块都有自己的作用域,在该作用域内定义的变量和函数在模块外是不可见的,这样就避免了全局变量的污染。
在 CommonJS 规范中,每个模块都是一个单独的文件,它们的模块名对应于文件名。模块可以通过 require
方法来加载其他模块,也可以通过 exports
对象来将模块中的数据或方法暴露给外部使用。
// 定义一个模块,导出一个 power 函数
function power(x, n){
return Math.pow(x, n);
}
exports.power = power;
// 加载模块并使用其导出的 power 函数
var calc = require('./calc.js');
var result = calc.power(2, 3); // 8
2. Nodejs中如何自定义模块?
2.1 模块导出
在Node.js中,我们通过 module.exports
和 exports
来实现模块的导出。两者区别如下:
exports 是 module.exports
的一个引用,最终导出的是 module.exports
。
如果直接给 exports
赋值,则会断开其和 module.exports
的引用关系,从而导致模块导出失效。
// 导出一个变量
exports.name = 'Node.js';
// 导出一个函数
module.exports.add = function(a, b){
return a + b;
}
// 导出一个类
class Person{
constructor(name, age){
this.name = name;
this.age = age;
}
sayHi(){
console.log('Hi, my name is ' + this.name + ', I am ' + this.age + ' years old.');
}
}
module.exports.Person = Person;
2.2 模块加载
在Node.js中,我们通过 require
方法来加载其他模块,具体使用如下:
// 加载一个变量
var name = require('./name.js').name;
// 加载一个函数
var add = require('./math.js').add;
// 加载一个类
var Person = require('./person.js').Person;
2.3 模块路径
在使用 require
方法加载模块时,需要指定模块的路径,具体如下:
如果指定的是一个相对路径,则该路径以当前模块所在目录作为相对路径的起点。
如果指定的是一个绝对路径,则该路径以当前根目录作为路径的起点。
如果指定的路径不是一个相对路径或绝对路径,则Node.js会从当前模块的 node_modules
目录下查找相应的模块。
// 加载当前目录下的 ./math.js
var math = require('./math.js');
// 加载上级目录下的 ./config.js
var config = require('../config.js');
// 加载其他模块的 /Users/username/node_modules/request/request.js
var request = require('request');
2.4 模块缓存
在Node.js中,每个模块都会被缓存起来,这样可以避免重复加载同一个模块,提高程序的性能。而当我们在程序运行时进行代码修改后,这些修改并不会立即生效,因为Node.js会缓存之前加载过的模块。
要想让修改生效,我们可以采取以下方法之一:
退出当前的Node.js进程,重新启动程序;
使用Node.js内置的模块 fs 监听文件修改事件,当文件变化时强制清空模块缓存重新加载。
// 监听文件修改事件,强制清空缓存
var fs = require('fs');
fs.watch('./math.js', function(){
delete require.cache[require.resolve('./math.js')];
});
2.5 模块循环依赖
在Node.js中,循环依赖是一种常见的问题。它指的是模块A依赖于模块B,而模块B又依赖于模块A,从而导致无法正常加载。
为了解决这个问题,Node.js采取了一种特殊的处理方式:
在执行A模块之前,先将A模块标记为已经加载,然后将exports对象传入A模块所依赖的模块B;
当执行B模块时,发现B模块依赖于A模块,但是A模块已经被加载了,此时直接从缓存中获取A模块的exports对象;
当A模块执行完毕后,将A模块的exports对象返回给B模块继续执行,这样就避免了循环依赖的问题。
虽然Node.js通过特殊处理避免了循环依赖的问题,但是在开发过程中还是应该尽可能避免模块之间的循环依赖,以减少程序的复杂度。
2.6 模块的加载顺序
在Node.js中,模块的加载顺序是按照以下规则进行的:
首先在当前目录下查找模块,如果找不到,则继续往上级目录查找,直到找到为止;
如果找到同名的目录和模块,优先加载目录。
如果在同一级目录下同时存在 index.js
和 index.json
文件,Node.js会优先加载 index.js
文件。
如果直接指定了一个文件夹作为模块名,则Node.js会先在该文件夹下查找 package.json
文件,读取其中的 main
属性指定的文件作为入口文件;如果不存在 package.json
文件或者 main
属性不存在,则默认加载 index.js
文件。
需要注意的是,模块的加载顺序是非常重要的,因为它决定了程序的正确性和性能。
2.7 模块覆盖与覆盖范围
在Node.js中,模块可以被其他模块覆盖。当一个模块被覆盖时,覆盖的范围取决于何处进行了模块覆盖。
模块覆盖分以下两种情况:
全局模块覆盖:当我们在程序中使用 require
方法加载一个模块时,Node.js会从以下两个位置查找模块:
全局模块,即在命令行中使用 -r
参数加载的模块;
当前目录下的 node_modules
目录(或其他指定的模块路径)中的模块。
局部模块覆盖:当在模块内部覆盖另一个模块时,它会影响到所有使用该模块的代码,因为在Node.js中模块只会被加载一次,并且被缓存起来。
需要注意的是,在开发过程中不要随意覆盖模块,因为这样会导致代码的可维护性和可读性大大降低,这对于大型项目来说是无法承受的。
总结:Node.js 中的模块化开发是非常重要的,而 CommonJS 规范则是 Node.js 支持的模块化规范。在实际的应用中,我们需要遵循一定的规范来实现模块化管理,这样可以避免代码的混乱和冗余,提高代码的可维护性。