《Node学习指南》一2.3 多行以及更复杂的JavaScript

    xiaoxiao2024-05-25  125

    本节书摘来自异步社区《Node学习指南》一书中的第2章,第2.3节,作者【美】Shelley Powers,更多章节内容可以访问云栖社区“异步社区”公众号查看

    2.3 多行以及更复杂的JavaScript

    Node学习指南你可以像写文件一样在REPL中输入JavaScript,包括导入module的require语句。以下代码显示了如何使用Query String(qs)module:

    $ node > qs = require('querystring'); { unescapeBuffer: [Function],  unescape: [Function],  escape: [Function],  encode: [Function],  stringify: [Function],  decode: [Function],  parse: [Function] } > val = qs.parse('file=main&file=secondary&test=one').file; [ 'main', 'secondary' ]

    由于没有使用var关键字,表达式的结果被直接输出,在本例中是querystring对象的接口。预期之外的收获是用这种方式不仅可以访问对象,同时还可以了解更多关于对象的可用接口。但是,如果不想看到可能出现的长文本输出,请使用var关键字:

    > var qs = require('querystring');

    可以用qs变量访问querystring对象的任一方法。

    为了兼容外部模块,REPL可以处理多行表达式,提供了可以嵌套使用的文本标识符,跟在大括号{}之后:

    > var test = function (x, y) { ... var val = x * y; ... return val; ... }; undefined > test(3,4); 12

    REPL提供重复的点“.”符号跟在开放的大括号后面表示输入命令未完成,该符号同样可以用于不闭合的小括号:

    > test(4, ... 5); 20

    层级间的递进需要更多的点符号。这在交互式环境中是必须的,否则会在输入过程中迷失了自己当前所在的位置:

    > var test = function (x, y) { ... var test2 = function (x, y) { ..... return x * y; ..... } ... return test2(x,y); ...} undefined > test(3,4); 12 >

    以下代码是一个完整的Node应用程序,可以在REPL中输入或者复制粘贴并运行:

    > var http = require('http'); undefined > http.createServer(function (req, res) { ... ...   // content header ...   res.writeHead(200, {'Content-Type': 'text/plain'}); ... ...  res.end("Hello person\n"); ... }).listen(8124); { connections: 0,   allowHalfOpen: true,   _handle:   { writeQueueSize: 0,    onconnection: [Function: onconnection],    socket: [Circular] },   _events:   { request: [Function],    connection: [Function: connectionListener] },   httpAllowHalfOpen: false } > undefined > console.log('Server running at http://127.0.0.1:8124/ '); Server running at http://127.0.0.1:8124/ Undefined

    可以通过浏览器访问该应用,这与用Node运行程序文件没有差别。而且,从REPL返回的response如上述粗体文本所示。

    事实上,REPL最实用之处在于快捷查看对象。例如,Node核心对象global在Node.js官网上文档很少。为了更好地了解该对象,在REPL中将global对象传递给console.log方法,如下:

    > console.log(global)

    下面这行代码具有相同的结果:

    > gl = global;

    这里不再复制REPL中的运行结果。global对象接口非常多,你可以安装后自己尝试。这个练习的关键在于告知一种可以在任何时候简单快捷地查看对象接口的方法,不必去死记硬背需要调用什么方法、什么属性是可用的。

    提示:更多关于global对象请见第3章。在REPL中可以使用上下箭头遍历之前输入的命令。这样可以很方便地查看之前的操作,也可以一定程度上提供编辑历史命令的能力。

    阅读REPL中的如下代码:

    > var myFruit = function(fruitArray,pickOne) { ... return fruitArray[pickOne - 1]; ... } undefined > fruit = ['apples','oranges','limes','cherries']; [ 'apples',  'oranges',  'limes',  'cherries' ] > myFruit(fruit,2); 'oranges' > myFruit(fruit,0); undefined > var myFruit = function(fruitArray,pickOne) { ... if (pickOne <= 0) return 'invalid number'; ...return fruitArray[pickOne - 1];  ... }; undefined > myFruit(fruit,0); 'invalid number' > myFruit(fruit,1); 'apples'

    在以上输入中没有体现出来的是,每次在修改方法检查输入值时,首先向上查找之前的命令找到函数定义,回车重新运行该方法。每添加新的语句,再用方向键重复上述操作直到完成该函数。同时也使用向上方向键重复函数调用,输出undefined。

    看起来似乎多做了很多工作只是为了避免重复输入,但是如果使用正则表达式,如以下例子:

    > var ssRe = /^\d{3}-\d{2}-\d{4}$/; undefined > ssRe.test('555-55-5555'); true > var decRe = /^\s*(\+|-)?((\d+(\.\d+)?)|(\.\d+))\s*$/; undefined > decRe.test(56.5); true

    则在写出一个正确的正则表达式之前通常要反复修改好几次(我并不擅长正则表达式)。用REPL做正则表达式的测试非常便捷,可以避免重复输入很长的正则表达式的痛苦。

    幸运的是在REPL中只需要使用方向键就可以找到创建正则表达式的命令,修改,回车然后继续测试。

    除了方向键,还可以使用Tab键自动补全。例如,在命令行中输入va,按Tab键,REPL会自动补全为var。Tab也可以用于自动补全任意的全局或者局部变量。表2-1列出了一些REPL中的按键功能。

    如果你担心花很多时间在REPL中编程但是结束时却没有什么可以保存下来的文件,不用担心,.save命令可以保存当前的上下文输入。这一命令和其他REPL的命令会在下一节中讲到。

    2.3.1 REPL命令

    REPL提供了一些常用命令的接口。在前一节中提到了.save命令,该命令将当前语境中的输入保存在文件中。除非特意创建了一个新的语境或者使用.clear命令,该文件会包含在当前REPL中所有的输入:

    > .save ./dir/session/save.js

    保存下来的只有你自己直接输入的文本,就像在文本编辑器中直接输入的一样。

    以下是一个完整的REPL命令以及功能列表:

    .break

    如果多行输入发生混乱不知道当前位置时,使用.break会重新开始。不过会丢失之前输入的多行内容。

    .clear

    重置语境并清空所有表达式。该命令可以使你重头再来。

    .exit

    退出REPL。

    .help

    显示所有可用的REPL命令。

    .save

    将当前REPL会话保存至文件。

    .load

    将文件加载到当前会话(.load/path/to/file.js)。

    如果使用REPL作为开发应用程序的编辑器,以下提示或许会有帮助:

    经常使用.save命令保存当前工作。尽管当前命令可以在历史记录中查找,但是重建代码依然是个很痛苦的过程。

    提到关于命令的记录,接下来就会涉及如何定制自己的REPL。

    2.3.2 REPL和rlwrap

    Node.js官网上关于REPL的文档提到过设置环境变量,所以可以在REPL中使用rlwrap。那么,rlwrap是什么?又为什么要在REPL中使用rlwrap呢?

    rlwrap将GNU readline库的功能添加至命令行,增加键盘输入的灵活性。它监听键盘输入并提供更多的功能,比如增强行编辑以及提供命令历史浏览功能。

    需要安装rlwrap和readline以便在REPL中使用这一功能,大部分Unix系统提供了简单的包安装。比如,在我的Ubuntu系统中,安装rlwrap只需要一行命令:

    apt-get install rlwrap

    Mac用户可以使用自己的安装工具安装该程序。Windows用户需要使用Unix环境模拟器,比如Cygwin。

    下面是一个简单的示例,如何在REPL中使用rlwrap将REPL的提示改为紫色:

    env NODE_NO_READLINE=1 rlwrap -ppurple node

    如果希望REPL的提示符一直是紫色的,可以在bashrc文件中添加别名(alias):

    alias node="env NODE_NO_READLINE=1 rlwrap -ppurple node"

    同时改变提示符和颜色,命令如下:

    env NODE_NO_READLINE=1 rlwrap -ppurple -S "::>" node

    现在提示符变为以下符号并且是紫色的:

    ::>

    rlwrap组件的特殊之处在于它在多个REPL窗口中浏览命令历史的功能。REPL默认只能浏览当前REPL会话的命令历史,但是使用rlwrap,在关闭当前会话下一次重新进入REPL时,不仅可以浏览当前会话的历史命令,并且可以浏览之前会话的命令历史(以及其他命令行输入)。在下面例子中,显示的命令行不是手动输入的,而是通过方向键从命令历史中找出来的:

    # env NODE_NO_READLINE=1 rlwrap -ppurple -S "::>" node ::>e = ['a','b']; [ 'a', 'b' ] ::>3 > 2 > 1; false

    即使rlwrap如此强大,但是每次输入无返回值的表达式时依然得到undefined。然而,这一现象是可以改变的,这就是下一节中将要讨论的功能——创建自定义的REPL。

    2.3.3 定制REPL

    Node提供了定制REPL的功能。为了实现该功能,首先需要引入REPL模块(repl):

    var repl = require("repl");

    通过在repl对象上调用start方法创建新的REPL。调用该方法的语法是:

    repl.start([prompt], [stream], [eval], [useGlobal], [ignoreUndefined]);

    所有参数都是可选择的。如果不传入参数,将会使用各参数的默认值,参数列表如下:

    prompt

    Default is>.默认值为>。

    stream

    Default is process.stdin.默认值为process.stdin。

    eval

    Default is the async wrapper for eval.eval的默认值为async。

    useGlobal

    默认值为false,新建一个语境而不是使用全局对象。

    ignoreUndefined

    默认值为false。不要忽略undefined的返回值。

    当我开始慢慢厌倦REPL在无返回值的表达式输出undefined时,我决定创建自己的REPL。事实上只需要两行代码就可以实现(不包括注释):

    repl = require("repl"); //设置ignoreUndefined为true,启动REPL repl.start("node via stdin> ", null, null, null, true);

    在Node中运行repl.js文件:

    node repl.js

    然后就可以像使用REPL内建版本一样使用自定义的REPL,除了自定义的提示符以及不会在变量赋值之后再看到讨厌的undefined之外,依然可以看到除了undefined之外的其他输出。

    node via stdin> var ct = 0; node via stdin> ct++; 0 node via stdin> console.log(ct); 1 node via stdin> ++ct; 2 node via stdin> console.log(ct); 2

    在代码中我希望除了提示符和ignoreUndefined以外都使用默认值。设置其他参数为null会使Node使用该参数的默认值。

    可以用自定义的REPL替换eval方法。唯一的要求是有具体的格式:

    function eval(cmd, callback) {   callback(null, result); }

    stream选项比较有意思。可以运行多个版本的REPL,从标准输入(默认)或者socket中获取输入内容。Node.js网站提供的REPL文档中给出了REPL监听TCP socket的例子,代码与下面这个例子类似:

    var repl = require("repl"),    net = require("net"); //设置ignoreUndefined为true,启动REPL repl.start("node via stdin> ", null, null, null, true); net.createServer(function (socket) {  repl.start("node via TCP socket> ", socket); }).listen(8124);

    在Node中运行应用程序的时候会看到标准输入提示符。还可以通过TCP进入REPL。我使用PuTTY作为Telnet客户端来登录支持TCP版本的REPL,某种程度上来说是可行的。我必须先运行.clear清理样式,但在尝试使用下划线表示上一行命令的时候,Node无法解析该符号,如图2-1所示。

    同样我也尝试过Windows 7 Telnet客户端,结果更糟糕。只有在Linux Telnet客户端使用时没有任何问题。

    你可能预计到问题在于Telnet客户端的设置。然而,我并没有深究这一问题。因为从不安全的Telnet Socket运行REPL并不是我计划要做的事情,也并不推荐这一存在安全隐患的行为。就像在客户端代码中使用eval()一样,并没有破坏或者泄露客户发给你需要运行的内容,但是结果比这样更糟糕。

    可以用UNIX socket通信运行REPL,比如GNU Netcat:

    nc -U /tmp/node-repl-sock

    可以像使用stdin一样输入命令。但是需要了解的是,如果使用TCP或者UNIX socket,任何console.log命令都会在server端打印输出,而不是客户端。

    console.log(someVariable);//在server端输出

    我想到一个很有用的应用程序,创建一个REPL程序,可以预加载模块。示例2-1的应用中,在REPL启动之后,http、os和util模块被加载并赋值给当前语境的对应属性。

    示例2-1 创建可以预加载模块的自定义REPL

    var repl = require('repl'); var context = repl.start(">>", null, null, null, true).context; // 预加载模块 context.http = require('http'); context.util = require('util'); context.os = require('os'); 用Node运行该程序,显示REPL的提示符,可以访问之前加载的那些模块: >>os.hostname(); 'einstein' >>util.log('message'); 5 Feb 11:33:15 - message >>

    如果希望像Linux中的可执行程序一样运行REPL程序,将下行代码加入应用程序开头:

    #!/usr/local/bin/node 修改文件权限为可执行并运行: # chmod u+x replcontext.js # ./replcontext.js  >> 相关资源:敏捷开发V1.0.pptx
    最新回复(0)