node.js模块化&commonJS规范
nodejs与commonjs
nodejs主要用于服务端编程,文件一般都能够本地读取速度较快,采用的是同步加载的commonjs规范。
关于commonjs:
- 每个文件都是封闭的一个模块,模块里定义的变量、函数、类都是私有的
- module代表当前模块,module是封闭的,但它的exports属性向外提供调用接口
- require加载模块,读取并执行一个js文件,然后返回该模块的exports对象
- commonjs是同步加载的,因此模块加载的顺序严格按照代码书写的顺序执行
- 模块可以多次加载,但在第一次加载之后模块会被编译执行,放入缓存,后续的require直接从缓存里取值,模块代码不再编译执行
require内部处理流程
- 检查Module._cache是否缓存了指定模块
- 如果缓存没有的话,就创建一个新的module实例将它保存到缓存
- module.load()加载指定模块
- 在解析的过程中如果发生异常,就从缓存中删除该模块
- 返回该模块的module.exports
模块化导出方式
- global.address = 'beijing';//导出全局变量,只要导入相应js文件即可调用
- module.exports = "str";//可以
- module.exports.msg = 'str'//可以
- exports.msg = 'str'//可以
- exports = 'str'//不行
关于最后一个导出方式为什么不行的说明:
module是封闭的模块,属性、函数都是私有的,仅对外提供module.exports属性以供调用,而require加载函数的返回值永远是module.exports属性。
由于exports默认指向的是module.exports,如果采用exports={}的方式,exports不再指向module.exports.
因为exports引用关系改变不再指向module.exports,所以无法被require识别调用
模块化原理
module的封闭性,模块的隔离怎么实现?
利用JavaScript函数式编程的特性,采用自执行函数,以其局部作用域实现隔离。
模块化的输出怎么实现?
node在加载js文件之前先准备一个module对象
module{id:'jsfilename',exports:{}}
在加载的时候,把js文件加载进load函数,module作为load函数的形参传进去,最后把js文件的对象保存在module.exports属性中并返回
在require获取module时,只要传入对应的id找到对应的module,就可以在Node中拿到对应module的exports对象,完成模块的输入与输出
代码如下:
// 准备module对象:
var module = {
id: 'hello',
exports: {}
};
var load = function (module) {
// 读取的hello.js代码:
function greet(name) {
console.log('Hello, ' + name + '!');
}
module.exports = greet;
// hello.js代码结束
return module.exports;
};
var exported = load(module);
// 保存module:
save(module, exported);
案例
// a.js
exports.x = 'a1';
console.log('a.js ', require('./b.js').x);
exports.x = 'a2';
// b.js
exports.x = 'b1';
console.log('b.js ', require('./a.js').x);
exports.x = 'b2';
// main.js
console.log('main.js ', require('./a.js').x);
console.log('main.js ', require('./b.js').x);
输出: a1 b2 a2 b2
分析:
前提:commonjs模块的加载是严格同步的且在第一次加载时编译代码并存入缓存,后续加载直接从缓存读取
- 执行main.js第一行,首次加载并执行a.js并将a加入缓存
- a.js中require b.js,此时a被阻塞,需要等待b编译加载并执行完成,此时a的缓存写入了x为a1
- b.js中require a,由于a的已经被main.js第一次加载,此刻读取缓存中a的exports为a1
- b.js打印a1,随后b将x=b2写入缓存,b首次加载完成,此时a.js等待完毕,将x=a2写入缓存,a首次加载完毕
- main.js中,a打印a编译、加载完毕之后x的值,a2。main.js第二行,由于b.js已经存在于缓存中,于是直接从缓存中读取x为b2并打印
- 综上,打印结果为a1,b2,a2,b2
Nodejs模块
主要基于commonJS规范,包括内置模块、自定义模块、第三方模块
内置模块
node自身携带的模块
例如:require('http');require('path');require('url');
自定义模块
require函数的形参path中用相对路径或者绝对路径导入的模块
在本地实现,通过module.exports导出
第三方模块
主要是npm的一些开源的包,当然你也可以发布自己的包