1.模块组织方式
node.js中可以按照模块来组织代码,主要借助于在模块中用module.exports 和 exports 导出你想要暴露给引用模块者使用的属性或者方法;
而引用者则通过 const moduleA = require(某个模块名或者路径)的方式来引用模块。 代码如下:
// module.a.js
module.exports.functionA = () => {
console.log('module A')
}
// module.b.js
module.exports.functionB = () => {
console.log('module B')
}
// app.js
const moduleA = require('./module.a');
const moduleB = require('./module.b');
moduleA.functionA();
moduleB.functionB();
// 输出
$ node app.js
module A
module B
在同层目录下创建如上文件,并且在git bash内执行 node app.js 即可. 这里面有几个问题需要注意:
1. exports 是对 module.exports 的全局别名引用
如果你用 exports = someOther 会导致破坏了引用关系
// module.a.js
exports = {};
exports.functionA = () => {
console.log('module A')
}
// module.exports = exports;
大家可以修改模块A代码如上,测试输出,可以采用最后一行重新恢复他们的引用关系
2. require 是以同步的方式引入代码
所以一般是在应用开始执行代码的时候引入所需模块
3. require 的参数分如下情况
3.1 路径(文件)
如果指定了文件名后缀,则分为.js或者.json 这2种文件格式都支持
3.2 模块名字
如果只写了模块的名字,那么会按照如下规则去寻找:
3.3 路径(文件夹)
如果是个目录路径,则按照如下规则去找:
4.被引入的模块是会被缓存起来的
我们可以修改上面的js
// module.a.js
const moduleC = require('./module.c');
moduleC.config.a = 'a';
exports = {};
exports.functionA = () => {
console.log('module A')
}
module.exports = exports;
// module.b.js
const moduleC = require('./module.c');
module.exports.functionB = () => {
console.log('module B')
console.log('moduleC ', moduleC)
}
const config = {
}
module.exports.config = config;
// 输出
$ node app.js
module A
module B
moduleC { config: { a: 'a' } }
从上面看到,确实同一个进程再次require同一个模块使用的是缓存中的模块,这样子我们可以在主应用js中导出一个公共app模块,里面包含了一些全局配置文件,就比较方便使用了。
5.如何处理循环引用
引用官方文档的例子:
// a.js
console.log('a starting');
exports.done = false;
const b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');
// b.js
console.log('b starting');
exports.done = false;
const a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');
// index.js
console.log('main starting');
const a = require('./a.js');
const b = require('./b.js');
console.log('in main, a.done = %j, b.done = %j', a.done, b.done);
// 输出
/*
$ node index.js
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done = true, b.done = true
*/
从例子我们可以看到,main执行的是引用A再引用B,我们先看A,A中也引入了B,这时候我们再看B的执行,B中又引入了A,如果这么require下去会导致无限循环,所以node对循环引用采取的方案是:main -> A -> B -> A 在这个时候,我们强制完成B的require,它此时所依赖的A作为一个半成品强制完成require给B使用,然后保证B的顺利引入,之后再完成A的引入,再执行main剩下的逻辑。所以也导致了我们看到的输出结果。
2.异步编程
2.1 事件发射器实现的聊天小程序
引用原文的例子
const events = require('events');
const net = require('net');
const channel = new events.EventEmitter();
// 所有连接的client都存储在这里
channel.clients = {};
// 广播事件触发时候 每个client的自动执行脚本
channel.subscriptions = {};
channel.on('join', function(id, client) {
// 提示当前在线人数
const welcome = `Welcome!
Guests online: ${this.listeners('broadcast').length}
`;
client.write(`${welcome}\n`);
this.clients[id] = client;
// 脚本逻辑 向自己输出发送者的消息
this.subscriptions[id] = (senderId, message) => {
if (id !== senderId) {
this.clients[id].write(message);
}
};
// 在频道的广播事件上面 绑定所有的client脚本 注意 有多少个client就有多少个脚本 然后一触发广播事件 所有的事件都会触发 即向每个client发送这条消息 达到广播的效果(除发送者外)
this.on('broadcast', this.subscriptions[id]);
})
// 离开事件
channel.on('leave', function(id) {
channel.removeListener('broadcast', this.subscriptions[id]);
channel.emit('broadcast', id, `${id} has left the chatroom.\n`);
})
// 关闭服务
channel.on('shutdown', function(id) {
channel.emit('broadcast', '', `The server has shut down.\n`);
channel.removeAllListeners('broadcast');
})
const server = net.createServer(client => {
const id = `${client.remoteAddress}:${client.remotePort}`;
channel.emit('join', id, client);
client.on('data', data => {
data = data.toString();
channel.emit('broadcast', id, data);
})
client.on('close', () => {
channel.emit('leave', id);
})
});
server.listen(8888);
// window下打开cmd窗口输入 telnet 127.0.0.1 8888 多开几个 模拟多用户