第4章 Node模块化及CommonJS规范
通过前面几个章节的学习, 我们基本掌握了NodeJS编程的基础知识, 但是我们也直观的发现了一个问题,和我们之前学习浏览器编程时JS, 差异还是很大的; 都是JavaScript编程, 为何有这种差异? 前面写过的防Apache服务器的案例中, 使用过内置fs模块, 使用过 moment 模块, 而这些模块都不是我们写的, 都是直接拿过来使用, 那么我们能不能自己写一个模块, 应该怎么写, 有哪些规矩, 如果我们自己写了一个模块, 能不能提供给其他编程人员直接使用, 应该怎么用?
Electron 跨平台的桌面应用框架: https://electronjs.org/
4.1 CommonJS规范的由来
JS 的表现的表现能力取决于宿主环境提供的API, 在web1.0 时代, W3C 组织提供了浏览器的规范支持, 在web2.0 时代, 随着HTML5的发展, 更多的标准API 出现在了浏览器中, 但是, 在后端 JS 中标准的制定纹丝不动 ;
由 Mozilla 工程师Kevin Dangoor于2009年1月提出名为 ServerJS 的规范; 2009年8月,更名为*CommonJS,*以显示 API 的更广泛适用性。
What I’m describing here is not a technical problem. It’s a matter of people getting together and making a decision to step forward and start building up something bigger and cooler together.
我在这里描述的不是一个技术问题。这是一个人们聚在一起,决定向前一步,开始一起建立更大更酷的东西的问题。
–Kevin Dangoor
4.2 CommonJS 的模块规范
CommonJS对模块的定义十分简单,主要分为:
1、模块引用:
使用 require()
方法引入一个模块API ;
2、模块定义:
在模块中使用 exports 对象导出当前模块数据或方法;
在模块中还存在一个module对象,它代表模块自身,module对象有一个exports 属性,用于数据导出;
其实exports 对象就是module.exports 的引用; exports === module.exports
3、模块标识:
其实就是模块的文件名,必须符合小驼峰法命名规则,使用require()
引入时使用 . 或 ..
开头的相对路
径或/
绝对路径,引入时可以不写文件后缀名;
重点注意 : 模块中的方法和变量的作用域仅在模块内部,每个模块具有独立的空间,互不干扰;
CommonJS 构建的模块机制中的引入与导出是我们完全不用考虑变量污染或者替换的问题,相比与命名空间
的机制,是有巨大差距的;
4.3 Node对CommonJS的实现 (Node模块化)
以上代码就是自定义模块的基本规则 这是重点
4.4 模块加载的顺序和规则
在 CommonJS 规范中,使用 require()
加载(引入) 模块时,模块标识必须使用相对路径或绝对路径指明模块位置,但是在node的实现中,我们可以不指明模块路径;如: require('fs')、require('moment')
;
如果没有指明路径,那就是加载核心模块或第三方模块,指明加载路径一般就是加载自定义模块;
不管加载什么模块,都是优先从缓存中加载:
Node 加载模块时,如果这个模块已经被加载过了,则会直接缓存起来,将来再次引用时不会再次加加载这个模块(即:如果一个模块被加载两次,则模块中的代码只会被执行一次)
而核心模块和第三方模块的的加载顺序就是:
先加载核心模块,核心模块的内容都是在安装node时已经编译好的可执行的二进制代码,加载执行的速度,仅次于缓存加载,如果核心模块中没有,则加载第三方模块
第三方模块的加载规则:
- 先在当前文件的模块所属目录去找 node_modules目录
- 如果找到,则去该目录中找 模块名的目录 如 : moment
- 如果找到 moment 目录, 则找该目录中的 package.json文件
- 如果找到 package.json 文件,则找该文件中的 main属性
- 如果找到main 属性,则拿到该属性对应的文件
- 如果找到 moment 目录之后,
- 没有package.json
- 或者有 package.json 没有 main 属性
- 或者有 main 属性,但是指向的路径不存在
- 则 node 会默认去看一下 moment 目录中有没有 index.js --> index.json–> index.node 文件
- 如果找不到index 或者 找不到 moment 或者找不到 node_modules
- 则进入上一级目录找 node_moudles 查找(规则同上)
- 如果上一级还找不到,继续向上,一直到当前文件所属磁盘的根目录
- 如果到磁盘概目录还没有找到,直接报错
4.5 模块化封装案例
思路:
1:服务器功能 --> apache
2:监听请求 --> 路由
3:根据请求处理业务 --> Controller
4:获取数据(处理数据) --> model
5:显示数据 --> view
客户端渲染方式的案例重构
修改 http.js — 服务器模块
var http = require('http');
var router = require('./router');
var server = http.createServer();
router(server);
server.listen(8000,function(){
console.log('欢迎来到node世界')
})
添加自定义模块 router.js – 路由模块
var controller = require('./controller');
module.exports = function(server){
server.on('request',function(req,res){
var urls = req.url;
if(urls == '/'){
// 需要获取html文件中的内容
// 响应给客户端
// 业务层模块的调用
controller.index(function(data){
// 利用回调函数获取数据
res.end(data);
});
}else if(urls == '/getnames'){
// 业务层模块的调用
// 将请求对象及响应对象传入业务层方法,在业务层做http响应处理
controller.getNames(req,res);
}else{
// 响应静态资源
require('fs').readFile('.'+urls,function(err,data){
res.end(data);
})
}
})
}
contrllor.js — 业务模块
var fs = require('fs');
var moment = require('moment');
module.exports = {
index: function (callback) {
// 返回静态页面
fs.readFile('./index.html', 'utf8', function (err, data) {
callback(data);
})
// console.log('index');
},
getNames: function (req, res) {
// console.log('getnamesssss');
fs.readdir('./', 'utf8', function (err, data) {
var filearr = [];
var cont = 0;
for (var i = 0; i < data.length; i++) {
// 2:
(function (i) {
// 3:
fs.stat(data[i], function (err, stats) {
cont++;
// 获取文件名
// 问题:文件名为 undefined
// 原因:在循环结束之后,函数才会被调用,而此时i已经被修改为最后的值
// 解决:在每次循环中形成一个独立的作用域保留i的值,
// 当异步函数调用时,获取到的是独立作用域中的i
filearr[i] = {};
filearr[i].name = data[i];
// 获取文件的其他属性
filearr[i].type = stats.isFile();
filearr[i].size = stats.size;
// filearr[i].mtime = stats.mtime;
filearr[i].mtime = moment(stats.mtime).format("YYYY-MM-DD hh:mm:ss");
// 异步函数调用次数与文件数相等时,服务器作出响应并断开
if (cont == data.length) {
res.end(JSON.stringify(filearr));
}
})
})(i);
}
})
}
}
本文档大量参考相关书籍、文档、博客、手册等资源,最终解释权归 吴明仕 所有;
参考资源相关列表:
https://nodejs.org/zh-cn/ node.js官网
http://nodejs.cn/ node.js中文网
《深入浅出Node.js》 朴灵著 ,人民邮电出版社
https://en.wikipedia.org/wiki/CommonJS 维基百科
《ECMAScript 6 入门》(第三版) 阮一峰著 ,电子工业出版社
《你不知道的JavaScript》(上、中、下卷) [美] Kyle Simpson 著 ,人民邮电出版社
http://www.expressjs.com.cn/ express中文网