背景
目前在开发ohh-cli脚手架,当中有用到了inquirer来实现命令行的交互;在好奇心的驱动下,想去研究下它的执行和原理。
知识点
readline/events/stream/ansi-escapes/rxjs
目标
掌握命令行交互的实现,并实现一个可交互的列表
一、readline的实现方法和原理
1. readlie介绍
node的内置库,主要是管理输入流;监听键盘上所有按钮的操作;
node官网的描述:https://nodejs.org/dist/lates...
The node:readline module provides an interface for reading data from a Readable stream (such as process.stdin) one line at a time.
翻译:readline模块提供了一个接口,用于每次一行从可读流(如process.stdin)读取数据。
2.readlie的使用
readlie.js
const readline = require ('readline');
const rl = readline.createInterface({
input: process.stdin, // ( process.stdin:系统输入流 );
output: process.stdout // ( process.stdout: 系统输出流 );
})
// 交互命令
rl.question('your name:', answer => {
console.log(answer);
rl.close(); // readline 不会自动关闭,需要调用命令关闭
})
node执行js
your name:ohh
ohh
readline 是根据传入的输入流信息,逐行读取,按回车后,认为输入信息已经结束;再将输入流的信息传入到输出流output中,进行展示;
3.readline源码重点阅读
3.1 readlie的整个进程的准备工作
(1) 强制将函数转化为构造函数
functuon createInterface(input, output, completer, terminal) {
return Interface(input, output, completer, terminal);
}
function Interface(input, output, completer, terminal) {
// Instanceof判断一个对象的正确类型,内部机制是通过原型链来判断的,测试一个对象在其原型链中是否存在一个构造函数的prototype属性。
if(!(this instanceof Interface)) { // false 就强制转化
return new Interface(input, output, completer, terminal)
}
}
(2) readlie如何去做事件监听的
利用Generator
emitKeypressEvents(input, this);
// 'input' uaually refers to stdin
input.on('keypress', onkeypress);
input.on('end', ontermend);
// Current line
this.line = '';
this._setRawMode(true);
this.terminal = true;
// Cursor position on the lie.
this.cursor = 0;
this.history = [];
this.histryIndex = -1;
.....
input.resume();
调用emitKeypressEvents(input, this);
function emitKeypressEvents(stream, iface) {
if(stream[KEYPRESS_DECVODER])return;
if(StringDecoder === undefined)
StringDecoder = require('string_decoder).StrinngDecoder;
stream[KEYPRESS_DECVODER] = new StringDecoder('utf8');
>1 stream[ESCAPE_DECODER] = emitKeys(stream);
stream[ESCAPE_DECODER].next();
const escapeCodeTimeout = () => stream[ESCAPE_DECODER].next('');
let timeoutId;
function onData(b) {....};
function onNewListener(event) {
if (event === 'keypress') {
stream.on('data', onData);
stream.removeListener('newListener', onNewListener);
}
}
if ( stream.listenerCount('keypress') > 0 ) {
stream.on('data', onData);
} else {
>2 stream.on('newListener', onNewListener);
}
}
调用emitKeys,是一个Generator函数,所以返回的
stream[ESCAPE_DECODER]
是一个Generator函数;
emitKeys 方法
funnction* emitKyes (stream) {
while(true) {
let ch = yied;
let s =ch;
let escaped = false;
const key = {name: undefiend...};
if(ch === kEscape) {
escaped = true;
s += (ch = yield);
}
.......
}
}
第一步执行
stream[ESCAPE_DECODER].next();
执行,执行到都一个yied以前进行返回while(true) { let ch =
第二步执行
stream.on('newListener', onNewListener);
stream 是input stream(输入流);增加一个newListener事件,就会跳出emitKeypressEvents
这个方法;第三步执行
去执行input.on('keypress', onkeypress);
event在执行.on方法的时候,会判断当前监听的这个对象, 这个input是否存在newListener这个事件,如果存在这个事件,就会在on这个过程中,emit newListener 这个事件; 所以会直接监听都这个方法onNewListene;
onkeypress方法
// 重写on方法
Readable.prototype.on = function(en, fn) {
const res = Stream.prototype.on.call(this, ev, fn);
const state = this._readableState;
.....
}
const res = Stream.prototype.on.call(this, ev, fn);
进入 Stream.prototypeEventEmitter.prototype.addListener = function addListener(type, listener) { return _addListener(this, type, listener,false); }
进入 _addListener 方法
checkListent(listener); events = target._events; .... // events 指是输入流 if(events.newListener === undefined) { //emit newListener事件 target.emit('newListener', type, listener.listener ? listener.listener: listener); }
newListener事件 捕获就就直接执行
emitKeypressEvents中的 onNewListener 方法
function onNewListener(event) {
if (event === 'keypress') {
stream.on('data', onData);
stener', onNewListener);
}
}
第四步执行
stream.on('data', onData);
输入流监听它的输入,这个方法会让当前的进程处于hold on的状态;等待用户去输入信息。
第五步执行
stream.removeListener('newListener', onNewListener);
监听的方法给移除了。
第六步执行
this._setRawMode(true);
iput.setRawMode(true)
//true逐字监听; false 是逐行监听;
Inerface.prototype._setRawMode = functiion(mode) {const wasInRawMode = this.input.isRaw; if (typeof this.input.setRawMode === 'function') { this.input.setRawMode(mode); } return wasInRawMode;
}
第七步执行
input.resume
把输入流从终止状态恢复到监听状态
整个进程的准备工作就完成了,等待用户输入
3.1 readlie的 用户输入信息后,回调流程
接收到用户输入的信息就会emit一个data事件;
function onData(b) {
....
stream[ESCAPE_DECODER].next(r[i]); // r[i] 就是输入的 “哦"
}
emitKeys 方法
funnction* emitKyes (stream) {
while(true) {
let ch = yied;
let s = ch; // s = 哦
let escaped = false;
const key = {
squence: null,
name: undefined,
ctrl: false,
meta: false,
shift: false
};
if(ch === kEscape) {
escaped = true;
s += (ch = yield);
}else if(判断是否是“\n”、 "\t"、空格 等等) {
.......
} else if (/^[0-9A-Za-z]$/.test(ch)) {
key.name = ch.toLowerCase();
key.shift = /^[A-Z]$/.test(ch);
key.meta = escaped;
}
.......
stream.emit('keypress', escaped? undefined: s, key);
}
}
输入显示出来调用的方法:
this.output.write(stringToWrite);
回显出输入的内容后,回进入Generator函数中再次被yied出去;
再次输入信息,还是会一样的进入循环;
如果输入回车:
再次进入 onData方法中,
function onData(b) {
....
stream[ESCAPE_DECODER].next(r[i]); // r[i] 就是输入的 "\r" 换行符
}
emitKeys 方法
funnction* emitKyes (stream) {
while(true) {
let ch = yied;
let s = ch; // s = 哦
let escaped = false;
const key = {
squence: null,
name: undefined,
ctrl: false,
meta: false,
shift: false
};
if(ch === kEscape) {
escaped = true;
s += (ch = yield);
}else if(ch === '\r'){
key.name = 'return';
} else if (大小写数字和字母的判断) {
key.name = ch.toLowerCase();
key.shift = /^[A-Z]$/.test(ch);
key.meta = escaped;
}
.......
stream.emit('keypress', escaped? undefined: s, key);
}
}
进入到 clearLine 中的,执行 this._wirteToOutput('\r\n')
让光标去执行换行,新的this.line置空;
rl.close();
close方法调用了this.pause();这个方法执行,this.input,pause();将输入流关闭;
Interface.prototype.pause = function () {
if(this.paused) return;
this.input.pause();
this.paused = true;
this.emit('pause');
return this;
};
this._setRawMode(false);
设置为false;
程序就结束了。