模块化
2009年美国程序员Ryan Dahl创建了Node.js项目,将JavaScript语言用于服务端编程,这标志着JavaScript模块化编程正式诞生。
模块化是将系统分割为独立的功能模块以实现按需加载,每个模块都是独立的,良好设计的模块会尽量与外部代码撇清关系以便于独立对其进行修改和维护,另外模块可重复使用从而解决冗余代码的问题。模块化主要解决两个问题分别是命名冲突和文件依赖。
在没有模块的时候一般会使用立即执行函数和闭包来解决命名冲突的问题,立即执行函数内部拥有自己独立的作用域,但外部只能访问自己暴露的成员而不能读取内部的私有成员。
//立即执行函数
let module = (function(){
let privateVar = "private";//私有变量
let privateFun = function(){};//私有函数
//对外暴露的成员
return {publicVar:"public", publicFun:function()};
})();
使用模块可以暴露所有模块内的成员,模块的内部状态可被外部改写。
let module = {
varname:"varname",
fun:function(){
console.log(this.varname);
}
};
CommonJS
JavaScript模块化规范分为四种,分别是CommonJS、AMD、CMD、ED6的模块系统。
CommonJS分为两种CommonJS1和CommonJS2,区别在于CommonJS1只能通过exports.xxx = xxx
的方式导出,CommonJS2在CommonJS1的基础上加入了module.exports = xxx
的导出方式。这里的CommonJS指的是CommonJS2.
Node.js应用是由模块组成,并采用了CommonJS模块规范,即每个文件是一个模块,拥有自己独立的作用域、变量、方法等,且对其他模块不可见。Node.js是CommonJS规范的实现,不适合客户端,因此ES推出了module
功能。
- CommonJS规范加载模块是同步的,当加载完毕后才可以执行后续流程,由于Node.js主要用于服务器编程,模块一般都是存在于本地磁盘,同步加载较快。
- CommonJS模块中所有代码都运行在模块的作用域下,因此不会污染全局作用域。
- CommonJS模块可以被多次加载但只会在第一次加载时运行一次,运行结果会被缓存起来。后续再加载将直接从缓存中读取,如果想让模块再次运行就必须先清除缓存。
- CommonJS模块的加载顺序是按照在代码中出现的顺序
- CommonJS的缺点在于不能并行加载模块,而且会阻塞浏览器加载。另外代码无法直接运行在浏览器环境下,必须通过工具转换成为标准的ES5才行。
CommonJS模块
CommonJS模块分为模块定义exports
、模块标识module
、模块引用require
三部分
-
module
对象代表模块本身 -
exports
对象用于导出当前模块的方法或变量 -
require
方法用于引入外部模块
CommonJS的核心思想是通过require
方法同步加载依赖的模块,使用module.exports
导出需要暴露的接口。
module
CommonJS规范规定每个模块内使用module
变量代表当前模块,module
变量是一个对象并具有exports
属性,用于对外提供接口。
$ vim module.ms
console.log(module);
$ node module.js
Module {
id: '.',
exports: {},
parent: null,
filename: 'D:\\nodejs\\workspace\\test.js',
loaded: false,
children: [],
paths:
[ 'D:\\nodejs\\workspace\\node_modules',
'D:\\nodejs\\node_modules',
'D:\\node_modules' ] }
属性 | 描述 |
---|---|
id | 模块标识符,通常是带绝对路径的模块文件名称。 |
filename | 模块的文件名,带有绝对路径。 |
loaded | 模块是否已经完成加载 |
parent | 调用该模块的父级模块 |
children | 模块所使用的其它模块 |
exports | 模块对外输出的值 |
//定义模块
$ vim module.js
module.exports = {
varname:"varname",
fn:function(){
console.log(varname);
}
}
CommonJS的module
对象的exports
属性表示当前模块对外输出的接口,当在其他文件中使用require
加载该模块时,实际是读取module.exports
变量。
//加载模块
$ vim test.js
const module = require("./module");
module.fn();//varname
module.exports
属性表示当前模块对外输出的接口,若其他文件使用require
加载该模块,实际是在读取module.exports
对象。
exports
Node.js为每个模块都提供了一个exports
变量(对象)并指向module.exports
属性,相当于每个模块的头部都存在:
//CommonJS隐式的做了这个赋值操作
var exports = module.exports;
$ vim module.js
console.log(exports);
$ node module.js
{}
这样做的好处在于对外输出模块接口时,可以向exports
对象中添加方法以暴露出去。
$ vim module.js
exports.varname = "hello world";
exports.fn = function(){
console.log(exports.varname);
};
console.log(module.exports);
$ node module.js
{ varname: 'hello world', fn: [Function] }
当模块对外输出时,可以在exports
上添加方法,但不能直接将exports
变量指向一个值,这样做相当于切断了exports
和module.exports
之间的关系。
-
module.exports
可以单独返回一个数据类型,而exports
只能返回一个对象。 -
exports
对象最终是通过module.exports
传递并执行,确切地说exports
是给module.exports
添加属性和方法。 -
exports
是module.exports
的引用,module.exports
是exports
的具体实现。
如果要改变module.export
但还想使用exports.xxx
的方式暴露接口,就只能自己来写exports = module.exports
。
// 常见用法
exports = module.exports = something;
require
require
方法用于同步加载读取并执行指定的JavaScript文件,返回模块的exports
对象,若未发现目标模块则报错。
封装
例如:导出对象实例
$ vim /config/mysql.json
{
"development":{
"host":"127.0.0.1",
"port":3306,
"user": "root",
"password": "root",
"database": "pomelo",
"charset": "utf8mb4",
"connectionLimit": 10
},
"production":{
"host":"127.0.0.1",
"port":3306,
"user": "root",
"password": "root",
"database": "pomelo",
"charset": "utf8mb4",
"connectionLimit": 10
}
}
$ vim /utils/mysql.js
//创建连接池
let Module = function(){
const env = process.env.NODE_ENV || "development";
const config = require("../config/mysql")[env];
const mysql = require("mysql2");
this.pool = mysql.createPool(config);
};
Module.prototype.query = function (sql, values){
return new Promise((resolve, reject)=>{
this.pool.getConnection((error, connection)=>{
if(error){
reject(error);
}else{
if(values){
connection.query(sql, values, (err, result)=>{
if(err){
reject(err);
}else{
if(result.length === 1){
result = result[0];
}
resolve(result);
}
});
}else{
connection.query(sql, (err, result)=>{
if(err){
reject(err);
}else{
if(result.length === 1){
result = result[0];
}
resolve(result);
}
});
}
connection.release();
}
});
});
};
module.exports = new Module();
//进程退出时自动关闭连接池
process.on("exit", async (code)=>{
try{
await this.pool.end();//TypeError: Cannot read property 'end' of undefined
}catch(e){
console.error(e);
}
});
process.on('unhandledRejection', error => {
console.error('unhandledRejection', error);
process.exit(1) // To exit with a 'failure' code
});
Express中使用
$ vim route.js
const router = require("express").Router();
const mysql = require("../utils/mysql");
router.get('/index',async (req, res, next)=>{
let json = {};
json.title = "default index page";
json.message = "hello world";
let sql = "SELECT * FROM game_user WHERE 1=1";
let result = await mysql.query(sql);
console.log(result);
res.render("index/index.html", json);
});
module.exports = router;