《Node.js实战》(第二版) 第二章读后感

297 阅读1分钟

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 多开几个 模拟多用户