Skip to content
  • Node 单线程,远离多线程死锁,状态同步等问题。

  • 利用异步io,让单线程远离阻塞,以更好的充分利用cpu。需要强调,这里得单线程仅仅是JS执行在单线程罢了。在node中,无论是*nix还是Windows平台,内部完成io任务的另有线程池。

  • Node的循环机制,启动时又一个死循环,每执行一次循环体称为Tick。每次循环处理事件。如果事件存在回调则处理回调。接着处理下一个事件。

  • 在Node中,事件来源有网络请求,文件io等。

事件循环时典型的生产者/消费者模型,异步io,网络请求是生产者,源源不断等为node提供不同的事件,这次事件被传递导对应的观察者那里,事件循环则从观察者那里取出事件并处理

  • Node8起新增了 util.promisify() 方法,可以快捷的把原来的异步回调方法改成返回 Promise 实例。

举例1

const util = require('util');
const fs = require('fs');
const readFileAsync = util.promisify(fs.readFile);
fileResult = await readFileAsync(sourcePathFile);

举例2

/**
* 执行 shell 返回 Promise
*/
async function execShell(scriptPath) {
  const execFile = require('util').promisify(require('child_process').execFile);
  return await execFile('sh', [scriptPath]);
}

  • module.exports 与 exports 的区别 先看下面的例子
**test.js**

var a = {name: 1}; 
var b = a;

console.log(a); 
console.log(b);

b.name = 2; 
console.log(a); 
console.log(b);

var b = {name: 3}; 
console.log(a); 
console.log(b);

运行 test.js 结果为:

{ name: 1 } 
{ name: 1 } 
{ name: 2 } 
{ name: 2 } 
{ name: 2 } 
{ name: 3 }

解释:a 是一个对象,b 是对 a 的引用,即 a 和 b 指向同一块内存,所以前两个输出一样。当对 b 作修改时,即 a 和 b 指向同一块内存地址的内容发生了改变,所以 a 也会体现出来,所以第三四个输出一样。当 b 被覆盖时,b 指向了一块新的内存,a 还是指向原来的内存,所以最后两个输出不一样。

同理 exports 是 module.exports 的引用。 当 module.exports 属性被一个新的对象完全替代时,也会重新赋值 exports 如果你觉得用不好可以只使用module.exports

Event Loop

event loop是一个执行模型,在不同的地方有不同的实现。浏览器和NodeJS基于不同的技术实现了各自的 Event Loop。 可以简单理解为不断执行的死循环 浏览器的Event Loop是在 html5 的规范中明确定义。 NodeJS的Event Loop是基于libuv实现的。可以参考 Node 的官方文档以及 libuv 的官方文档。 libuv已经对Event Loop做出了实现,而HTML5规范中只是定义了浏览器中Event Loop的模型,具体的实现留给了浏览器厂商。

Events

Events 是 Node.js 中一个非常重要的 core 模块, 在 node 中有许多重要的 core API 都是依赖其建立的. 比如 Stream 是基于 Events 实现的, 而 fs, net, http 等模块都依赖 Stream, 所以 Events 模块的重要性可见一斑。

通过继承 EventEmitter 来使得一个类具有 node 提供的基本的 event 方法, 这样的对象可以称作 emitter,而触发(emit)事件的 cb 则称作 listener。与前端 DOM 树上的事件并不相同, emitter 的触发不存在冒泡, 逐层捕获等事件行为, 也没有处理事件传递的方法。

Node.js 中 Eventemitter 的 emit 是同步的。

例1:

const EventEmitter = require('events');
let emitter = new EventEmitter();

emitter.on('myEvent', () => {
  console.log('1');
});
emitter.on('myEvent', () => {
  console.log('2');
});
emitter.emit('myEvent');

执行结果是 1, 2

例2: 会发生死循环

const EventEmitter = require('events');
let emitter = new EventEmitter();

emitter.on('myEvent', () => {
  console.log('hi');
  emitter.emit('myEvent');
});

// 只出现一次
console.log("1")

emitter.emit('myEvent');

// 永远不会发生
console.log("down")

例3 在使用node的mongoose模块中,项目中有如下代码: 如何实现的呢?

const mongoose = require('mongoose');
// MongoDB connect
function mongoDBConnect() {
  mongoose.connect(`${config.mongo.url}${config.mongo.database}`);
  return mongoose.connection;
}

mongoDBConnect()
  .on('error', console.error.bind(console, 'connection error:'))
  .on('disconnected', () => console.log('mongodb disconnected'))
  .once('open', () => console.log('mongodb connection successful'));

翻了 源码 最关键的一行是让Connection继承自EventEmitter。 Connection.prototype.__proto__ = EventEmitter.prototype;

const EventEmitter = require('events').EventEmitter;

// connectionState start
const STATES = Object.create(null);

const disconnected = 'disconnected';
const connected = 'connected';
const connecting = 'connecting';

STATES[0] = disconnected;
STATES[1] = connected;
STATES[2] = connecting;

STATES[disconnected] = 0;
STATES[connected] = 1;
STATES[connecting] = 2;
// connectionState end


function Connection() {
	this.states = STATES;
	this._readyState = STATES.disconnected;
}

// 这行非常关键,继承 EventEmitter
Connection.prototype.__proto__ = EventEmitter.prototype;

Object.defineProperty(Connection.prototype, 'readyState', {
  get: function() {
    return this._readyState;
  },
  set: function(val) {
    if (!(val in STATES)) {
      throw new Error('Invalid connection state: ' + val);
    }

    if (this._readyState !== val) {
      this._readyState = val;

      this.emit(STATES[val]);
    }
  }
});

Connection.prototype.onOpen = function() {
  this.readyState = STATES.connected;
  this.emit('open');
};

let conn = new Connection();

conn.on('connected', () => {
	console.log("1");
});

conn.on('open', () => {
	console.log("open!!");
});


conn.readyState = 1

conn.readyState = 2

conn.onOpen();

面试相关

https://elemefe.github.io/node-interview/#/sections/zh-cn/