一、Node中的模块系统
使用Node编写应用程序主要就是在使用:
- ECMAScript脚本语言
- 和浏览器不一样,在Node中没有BOM,DOM
- 核心模块
- 文件操作的 fs
- http 服务的 http
- url 路径操作模块
- path 路径处理模块
- os 操作系统信息
- 第三方模块
- art-template
- 必须通过 npm 下载才可以使用
- 自己写的模块
- 自己创建的文件
1.1 什么是模块化
简单地说,模块化就是有组织地把一个大文件拆成独立并互相依赖的多个小模块。
模块内部有许多私有属性,只向外暴露一部分公开的接口(如可以修改私有属性的方法等)。
如果一个平台支持:
-
文件作用域
- 代码运行在模块作用域,不会污染全局
-
具有通信规则
- 加载 require
- 导出
那就说明它符合模块化。
1.2 为什么要模块化
ES6之前,JavaScript语言一直没有模块(module)体系,无法把大文件有组织地划分成小块,并管理之间地依赖。但是模块化的思想一直存在。因为用Javascript写的代码越来越庞大,而网页也越来越像桌面APP。如此庞杂的代码,如果不进行模块化,就可能引发命名冲突,造成不易复用、维护性高。
1.3 模块化的好处
- 避免命名冲突(减少命名空间污染)
- 更好的分离, 按需加载
- 更高复用性
- 高可维护性
1.4 模块划分
一般,为了清晰明了,一个文件对应一个模块。
1.5 CommonJs模块规范
在Node中的JavaScript还有个很重要的概念:
- 模块作用域
- 使用 require 方法用来加载模块
- 使用 exports 接口对象用来导出模块中的成员
1.5.1 加载require
语法:var 自定义变量名 = require('模块')
两个作用:
- 执行被加载模块中的代码
- 得到被加载模块中的exports导出接口对象
1.5.2 导出exports
- Node 中是模块作用域,默认文件中所有的成员只在当前文件模块有效
- 对于希望可以被其它模块访问的成员,我们就需要把这些公开的成员都挂载到
exports
接口对象中就可以了
导出多个成员(必须在对象中):
exports.a = 123;
exports.b = 'hello';
exports.c = function(){
console.log('ccc');
}
exports.d = {
foo: 'bar'
}
导出单个成员(拿到的是:函数、字符串…):
module.exports = 'hello';
一下情况会覆盖:
module.exports = 'hello';
//后者会覆盖前者
module.exports = function (x,y){
return x+y;
}
也可以这样来导出多个成员:
module.exports = {
add : function (x,y){
return x+y;
},
str : 'hello'
}
1.5.3原理解析:
exports
是 module.exports
的一个引用
也就是说:
console.log(exports === module.exports); // true
exports.foo = 'bar';
//等价于
module.exports.foo = 'bar';
当一个模块需要导出单个成员的时候,直接给exports赋值是不管用的。
错误写法:exports = 'hello' ;
原因:
exports 和 module.exports默认的是指向同一个对象。
给exports 赋值会断开和 module.exports之间的引用关系,此时exports 不再指向module.exports指向的对象。
由于最后返回的还是 module.exports 。所以此时修改exports 并没有修改 module.exports 。
同理 ,给 module.exports 重新赋值也会断开和 exports 之间的引用关系。
例如:
exports.foo = '123'; //管用
exports = {
};
exports.a = 'hello'; //不管用,此时exports不指向module.exports
module.exports.b = '456' ;//管用
//默认在代码的最后有一句:
// return module.exports;
//一定记住 最后 return 的是module.exports
//exports 和 module.exports默认的是指向同一个对象
//所以你给 exports 重新赋值没有用
// 重新赋值会丢失 exports 和 module.exports之间的引用关系
exports
和 module.exports
的区别总结:
- 每个模块中都有一个 module 对象
- module 对象中有一个 exports 对象
- 我们可以把需要导出的成员都挂载到 module.exports 接口对象中
- 也就是:
module.exports.xxx = xxx
的方式 - 但是每次都
module.exports.xxx = xxx
很麻烦,点儿太多了 - 所以Node 为了你方便,同时在每一个模块中都提供了一个成员叫:
exports
exports
等价于module.exports
- 所以对于
module.exports.xxx = xxx
的方式 完全可以写成:exports.xxx = xxx
的方式 - 当一个模块需要导出单个成员的时候,必须使用
module.exports = xxx
的方式 - 不要使用
exports = xxx
的方式 不管用 - 因为每个模块最终向外
return
的是module.exports
- 而
exports
只是module.exports
的一个引用 - 所以即使你为
exports = xxx
重新赋值,也不会影响module.exports
- 但是有一种赋值方式比较特殊:
exports = module.exports
这个用来重新建立引用关系的
module.exports = {
}
exports = module.exports; //重新建立 exports 和 module.exports 的引用关系
exports.foo = 'bar'; //管用
1.5.4 特点:
-
代码运行在模块作用域,不会污染全局
-
加载模块顺序按照词法解析的顺序加载
-
加载模块是同步的
-
单例加载:也就是加载的模块会缓存起来,再次使用时,会直接用运行结果,不会再加载(除非手动清除)
-
加载模块得到的是结果的拷贝