Nodejs开发进阶9-REPL本文讨论了何为REPL,包括基本概念、工作原理和流程,以及它如何帮助开发者进行编程语言的
本章节,我们来讨论一下nodejs中的REPL,一个对学习和开发非常有帮助的工具。
概述
从广泛的定义而言,REPL(Read读取, Evaluate评估, Print打印, Loop循环)是一种实时的程序开发、探究、评估和使用模式和环境。
nodejs内置提供了REPL的实现,特别是它的使用非常方便。我们知道,在操作系统命令行环境中,通过输入“node”命令可以执行node程序,如果把js文件名作为参数,就可以让nodejs执行这个js文件(输入“.”,可以执行当前目录中的index.js文件作为默认文件);而如果不带任何参数,就会进入nodejs的REPL环境:
yanjh@WK-YANJH-AMD:~$ node
Welcome to Node.js v14.16.0.
Type ".help" for more information.
> .help
.break Sometimes you get stuck, this gets you out
.clear Alias for .break
.editor Enter editor mode
.exit Exit the REPL
.help Print this help message
.load Load JS from a file into the REPL session
.save Save all evaluated commands in this REPL session to a file
Press Ctrl+C to abort current expression, Ctrl+D to exit the REPL
这是一个交互式环境,类似操作系统的命令行环境,就是用户输入命令或者代码,就可以立刻执行。为了和操作系统环境分开,这里使用提示符“>”。
这里我们输入.help,就可以查看帮助信息,这里就是可用的命令。因为REPL也可以执行和输入代码,所以使用点号前缀的表示这是一个REPL指令,和普通的js代码是区分开来的。比如我们再输入.exit,就可以退出REPL环境,回到操作系统命令行。当然,如果我们输入js代码,将会执行相应的js程序。
应用场景
大概并体验了解了REPL是什么之后,我们来思考一下这个东西设计的目的和应用的场景是什么。其实非常简单:
方便,高效
使用REPL非常方便,不需要编辑器,不需要执行器,不需要打开、编辑、保存文件,也不需要启动、执行命令,直接的编写和执行代码,正确或者错误立刻知晓。而且这个过程轻量,轻松,愉快。这些特性和体验,在开发者整个的构思-编码-验证的工作链条和循环中,可以提供极高的效率。所以,笔者建议开发者积极尝试REPL的使用,找到自己适合的方式,这些方式可能包括:
- 学习js语法
哪怕是对有经验的开发者而言,也不可能熟悉和掌握所有的js语法、对象特性和方法,就需要一种高效的查阅和验证机制。当然查询技术文档也是一个方法,但毕竟所有的代码都必须能够在真实环境中运行,RPEL就提供了这一一个很好的PlayGround,来帮助开发者方便快速的熟悉这些特性。显然,这个特性,对于初学者就更加方便有效了就像儿童的游乐场一样(下图),可以先不在正式的环境中操作代码,也可以来熟悉这些语法和功能。
- 快速代码和方法验证
REPL适合来验证单一的代码片段和逻辑,不太适合于太复杂和冗长的代码。恰好,js语言的简洁高效也适合于这个特点。后面我们有实例可以看到这一点。
- 语法提示
在使用REPL环境编写代码时,对于一些方法和类,REPL可以根据上下文进行语法的提示补全,让开发者可以快速的了解相关的对象特性和语法。
- 查看类定义
很多开发者可能没有注意到,在REPL中,可以快速的查看类定义,省去了很多查阅技术文档的时间。比如http这个类:
> http
{
_connectionListener: [Function: connectionListener],
METHODS: [
'ACL', 'BIND', 'CHECKOUT',
'CONNECT', 'COPY', 'DELETE',
'GET', 'HEAD', 'LINK',
'LOCK', 'M-SEARCH', 'MERGE',
'MKACTIVITY', 'MKCALENDAR', 'MKCOL',
'MOVE', 'NOTIFY', 'OPTIONS',
'PATCH', 'POST', 'PRI',
'PROPFIND', 'PROPPATCH', 'PURGE',
'PUT', 'REBIND', 'REPORT',
'SEARCH', 'SOURCE', 'SUBSCRIBE',
'TRACE', 'UNBIND', 'UNLINK',
'UNLOCK', 'UNSUBSCRIBE'
],
STATUS_CODES: {
'100': 'Continue',
'101': 'Switching Protocols',
'102': 'Processing',
'103': 'Early Hints',
'200': 'OK',
'201': 'Created',
'202': 'Accepted',
'203': 'Non-Authoritative Information',
'204': 'No Content',
'205': 'Reset Content',
'206': 'Partial Content',
'207': 'Multi-Status',
'208': 'Already Reported',
'226': 'IM Used',
'300': 'Multiple Choices',
'301': 'Moved Permanently',
'302': 'Found',
'303': 'See Other',
'304': 'Not Modified',
'305': 'Use Proxy',
'307': 'Temporary Redirect',
'308': 'Permanent Redirect',
'400': 'Bad Request',
'401': 'Unauthorized',
'402': 'Payment Required',
'403': 'Forbidden',
'404': 'Not Found',
'405': 'Method Not Allowed',
'406': 'Not Acceptable',
'407': 'Proxy Authentication Required',
'408': 'Request Timeout',
'409': 'Conflict',
'410': 'Gone',
'411': 'Length Required',
'412': 'Precondition Failed',
'413': 'Payload Too Large',
'414': 'URI Too Long',
'415': 'Unsupported Media Type',
'416': 'Range Not Satisfiable',
'417': 'Expectation Failed',
'418': "I'm a Teapot",
'421': 'Misdirected Request',
'422': 'Unprocessable Entity',
'423': 'Locked',
'424': 'Failed Dependency',
'425': 'Too Early',
'426': 'Upgrade Required',
'428': 'Precondition Required',
'429': 'Too Many Requests',
'431': 'Request Header Fields Too Large',
'451': 'Unavailable For Legal Reasons',
'500': 'Internal Server Error',
'501': 'Not Implemented',
'502': 'Bad Gateway',
'503': 'Service Unavailable',
'504': 'Gateway Timeout',
'505': 'HTTP Version Not Supported',
'506': 'Variant Also Negotiates',
'507': 'Insufficient Storage',
'508': 'Loop Detected',
'509': 'Bandwidth Limit Exceeded',
'510': 'Not Extended',
'511': 'Network Authentication Required'
},
Agent: [Function: Agent] { defaultMaxSockets: Infinity },
ClientRequest: [Function: ClientRequest],
IncomingMessage: [Function: IncomingMessage],
OutgoingMessage: [Function: OutgoingMessage],
Server: [Function: Server],
ServerResponse: [Function: ServerResponse],
createServer: [Function: createServer],
validateHeaderName: [Function: hidden],
validateHeaderValue: [Function: hidden],
get: [Function: get],
request: [Function: request],
maxHeaderSize: [Getter],
globalAgent: [Getter/Setter]
}
>
在理解了这些使用场景后,我们来看看它是如何工作的。
工作流程
它的工作原理和流程,就是那四个字母: R-E-P-L。
- Read(读取)
和普通的命令行交互环境不同。用户可以直接在REPL内输入代码。代码输入完成后,可以使用回车结束这一轮的代码输入,REPL程序就会进入下一个执行阶段-求值。
如果我们输入的代码不是一行的,可以使用ctrl+c另起一行,这时代码会换行显示,但不会真正的执行。也可以使用.editor命令,进入编辑器模式,这时候的换行就是文本换行,而非开始执行命令(ctrl+d结束编辑并执行)。
- Evaluate(求值)
这里的求值,其实就是加载和运行读取阶段得代码。REPL的执行命令分隔符是“回车”。也就是说,当你输入回车的时候,求值过程就开始启动和执行。
有些代码是可以有计算结果的,这时会有一个结果值。但有些代码可能只是一些操作,比如赋值,它并没有明确的返回值。这时REPL会认为它的结果是undefined。
- Print(打印)
打印,就是输出处理结果了。需要注意,REPL永远都输出在一次循环中的最后的那个计算结果。而且REPL的操作是实时的,它会在你输入的时候,就试图输出计算的结果,提供了很好的体验。前面提到,如果最后的步骤不是一个计算,比如是一个赋值,它也会输出一个"undefined",可能是因为这个操作没有返回一个明确可定义的结果吧。
- Loop (循环)
这个在说明,REPL是一个交互循环的过程,在一个流程结束后,就可以进入下一个流程,无需其他操作,可以一直进行下去。REPL还有session的概念,虽然有很多loop,但它们还是在一个运行环境中的,就是那些赋值和定义,都还有效,在后面也可以使用。如果我们要清除这个环境,也可以使用.clear命令。
这几个步骤和流程看起来复杂,其实真正操作起来是很简单的,就是输入命令和指令,就可以执行查看结果,和操作系统的命令行接口的逻辑,是一样的。还是建议读者实际操作体验,可以更直观深入的理解这一点。
指令和快捷键
nodejs默认REPL环境的指令包括:
- .help: 显示帮助信息(指令列表)
- .break: 在输入多行表达式时,中断输入(同Ctrl+C)
- .clear: 重置 REPL 上下文
- .exit: 关闭当前I/O流,退出REPL环境
- .save: 保存当前 REPL 会话到文件
- .load: 从文件加载并恢复REPL会话
- .editor: 进入编辑器模式(Ctrl+D结束编辑,Ctrl+C中断编辑)
除指令之外,可能在操作中,需要使用一些快捷键:
- Ctrl+C: 中断指令,同.break
- Ctrl+D: 退出指令,同.exit
- Tab: 在空行时可以查看全局和局部变量,以及可用类(下图)
实操
这里笔者例举了一些REPL操作的实例,希望可以帮助读者更好的理解它的应用:
// 1 理解Buffer和base64编码
> Buffer.from("China中国")
<Buffer 43 68 69 6e 61 e4 b8 ad e5 9b bd>
> Buffer.from("China中国").toString("base64")
'Q2hpbmHkuK3lm70=
// 2 理解Array的数据类型
> typeof []
'object'
> typeof ""
'string'
> Array.isArray("");
// 3 crypto sha1Hmac
> crypto.createHmac("SHA1","somekey").update("content").digest("hex")
'f0e59fee8f991dc67274c51f231e416d6ab79627'
// 4 Reduce用法测试
> Buffer.from("what's up").reduce((c,v,i)=>c+v,0)
851
> Buffer.from("what's up").reduce((c,v,i)=>c+v.toString(10).padStart("0",3),"")
'119104971163911532117112'
// 5 js解构方法学习
> let {id,name} = {id: "1234", name:"John Yan"}
undefined
> id
'1234'
> name
'John Yan'
// 6 ID卡编码转换
> parseInt(parseInt(0616988734).toString(16).match(/.{2}/g).reverse().join(""),16).toString().slice(-10);
'1048626724'
几个要点和有趣的地方:
- js的链式语法操作,特别适合于编写快速验证代码
- REPL并不非常严格遵循JS语法,比如可以直接声明和变量
- 很多全局变量、库和方法,在REPL中是可以直接使用的,如crypto
- 规划良好的话,console不是必须的,REPL会输出结果和内容,但如果考虑明确或多个内容的话,也可以使用console
- 如果要运行一个已存在的js代码块,可以使用.load指令
- 一些实验性代码需要保存的话,可以使用.save指令记录当前的状态和设置
- 迄今为止,笔者尚未遇到需要.break的场合,所以这部分内容不知如何阐述
IDE中的REPL
REPL并非nodejs独有的概念或者技术,比如很多IDE环境中,就集成或者可以安装REPL,来方便开发者的工作。在笔者使用的VSCode这个集成开发环境中,就可以通过插件的方式,来安装使用REPL。
可以在VSCode插件面板中搜索REPL,就可以看到Node.js REPL这个插件,安装完成后,可以通过快捷键Ctrl+Shift+P启动插件程序菜单,搜索并启动REPL就可以开始使用了。
这个命令会启动一个编辑器来操作REPL(下图),可以看到,比node的REPL更方便的是,它同时可以保存和显示多个命令代码状态,更方便代码验证和设计。
node REPL模块
nodejs中,专门有一个REPL模块,可以对REPL的相关功能进行编程。也就是说,你完全可以利用这个模块,编写自己的REPL实现和应用(也许VSCode中的REPL插件就是这样实现的?)。关于这方面的内容,笔者也没有机会深入了解和应用,这里只是简单的和读者分享一下这方面相关的信息。
但从下面的示例代码,也可以让我们简单的了解一下REPL模块:
const repl = require('node:repl');
const replServer = repl.start({ prompt: '> ' });
replServer.defineCommand('sayhello', {
help: 'Say hello',
action(name) {
this.clearBufferedCommand();
console.log(`Hello, ${name}!`);
this.displayPrompt();
},
});
replServer.defineCommand('saybye', function saybye() {
console.log('Goodbye!');
this.close();
});
从这段代码,我们大致可以获得几个相关信息:
- REPL可以以js程序的形式运行
- REPL的工作模式,基本上是客户端/服务器模式,每个REPL,都是一个replServer实例
- 在新REPL实例中,可以自行定义提示符和命令
其实REPL模块还提供了很多功能,如果需要了解详情,可以查阅nodejs官方的技术文档。
小结
本文讨论了何为REPL,包括基本概念、工作原理和流程,以及它如何帮助开发者进行编程语言的学习和开发的辅助工作。并通过一些简单的示例,来说明其实际使用方式和特点。
转载自:https://juejin.cn/post/7308562434449342527