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

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
 >>
时间: 2024-08-17 13:12:57

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

《Node学习指南》一1.3 异步函数及Node事件循环

1.3 异步函数及Node事件循环 Node学习指南 Node的基本设计原则是将应用程序放置在单线程(或单进程)中执行,同时异步处理所有事件. 考虑下典型的Web服务器(如Apache)是如何工作的.Apache可以采用两种不同的方式处理传入的请求:一种方式是将传入的每个请求分配到独立的进程中直至请求被处理完毕:另一种方式则是为每一个请求生成单独的处理线程. 第一种方式(也称为prefork multiprocessing model,或prefork MPM)可以根据Apache配置文件中指定

《Node学习指南》一导读

前 言 Node学习指南 非同寻常的JavaScript 目前正是学习Node的好时机. Node相关的技术依然年轻充满生机,经常出现有趣的变化和改动.同时,这项技术也达到了一定的成熟度,可以确保你在学习Node上花费的时间是值得的:即使在Windows上安装也非常简单:从成百上千的可用模块中涌现出了最佳组合模块:对于产品环境来说这种结构足够健壮. 当使用Node时需要记得两个要点.第一,Node是基于JavaScript的,与你之前用于客户端开发的JavaScript多少有些类似.当然,你也可

《Node学习指南》一1.2 开始Node开发

1.2 开始Node开发 Node学习指南现在你已经安装了Node,是时候开始编写第一个Node应用程序了. 1.2.1 Hello, World in Node 为了测试新的开发环境.语言或者工具,第一个写出来的程序往往是"Hello,World".我们同样也将使用Node创建一个"Hello,World"程序,它仅仅简单的向访问它的用户输出问候语. 示例1-1包含了使用Node创建Hello,World程序需要的全部文本代码. 示例1-1 Node版Hello,

《Node学习指南》一第2章 Node与REPL 2.1 REPL:先睹为快和未定义的表达式

第2章 Node与REPL Node学习指南尝试使用Node编写自定义的模块或者应用程序时,并不需要每次运行写好的JavaScript文件来测试代码功能.Node有一个交互式组件称为REPL(read-eval-print-loop,读取求值列印循环),这将是本章的主题. REPL(发音为"repple")支持简化的Emacs风格行编辑和一小部分基本命令.在REPL中输入任何内容都与用Node运行JavaScript编写的文件具有相同的处理方式.事实上,可以使用REPL编写整个应用程序

《Node学习指南》一2.2 REPL的优势:更好地理解表层之下的JavaScript

2.2 REPL的优势:更好地理解表层之下的JavaScript Node学习指南下例是一个REPL的典型示范: > 3 > 2 > 1; false 这段代码很好地解释了REPL的工作原理.一眼看上去会认为期望的输出值为true,因为3大于2,2大于1.但是在JavaScript中,表达式是从左到右计算的,每个表达式的返回值作为下一个表达式的一部分进行计算. 以下REPL中的语句可以帮助你更好地理解前端代码: > 3 > 2 > 1; false > 3 >

《Node学习指南》一2.4 不可预计的意外—记得经常保存

2.4 不可预计的意外-记得经常保存 Node学习指南Node的REPL是一个便捷的交互式工具,可以使开发任务变得简单点.REPL不仅可以在引入文件之前对JavaScript进行测试,并且可以边编写边测试直到完成时保存代码内容. REPL另一个有用的特性是可以创建自定义的REPL,减少无用的undefined输出,预加载模块以及修改提示符或者eval方法等. 我强烈推荐在REPL中使用rlwrap,可以跨session浏览历史命令.这一特性可以节省大量的时间.话说回来,我们之中谁不喜欢更多更强大

《写给PHP开发者的Node.js学习指南》一2.2 预定义的PHP变量

2.2 预定义的PHP变量 写给PHP开发者的Node.js学习指南 当一个支持PHP的Web服务器执行一个PHP页面时,它并不是仅提供一个未处理的对某个页面的HTTP request,然后执行这个页面.如果它这样做的话,那么每一个PHP页面都需要大量额外的代码来解析原始的HTTP request并且把这些值用更方便的方式存储起来.相反,PHP引擎解码原始的HTTP请求,并将数据填充到一堆众所周知的PHP全局变量中.这些全局变量被正确填充才能保证PHP页面正常工作. 由于我们采用的基本方法是将P

《写给PHP开发者的Node.js学习指南》一1.3 Eclipse PDT

1.3 Eclipse PDT 写给PHP开发者的Node.js学习指南学会如何分析堆栈追踪信息是PHP到Node.js成功转换的一个重要技能.堆栈追踪是查找代码错误的一个诊断工具,就像医生用X光来检查病人一样.从某种观点来看,PHP到Node.js的转换可以类似于一个复杂的外科手术.你要对PHP和Node.js代码进行手术.像手术过程一样,转换需要技巧和耐性,但是一个好的环境也有很大帮助.就像手术室中使用X光一样,堆栈追踪就是转换过程中开发环境中的工具.下一步,我们会讨论集成开发环境,为转换过

《写给PHP开发者的Node.js学习指南》一第 2 章 简单的Node.js框架2.1 HTTP服务器

第 2 章 简单的Node.js框架 写给PHP开发者的Node.js学习指南 在之前的章节,我介绍了一个用于PHP到Node.js转换的开发环境,以及如何使用它进行转换.在本章,我们将开始使用这个开发环境并进行实际的转换. 2.1 HTTP服务器 写给PHP开发者的Node.js学习指南 在PHP中,一个PHP文件代表一个HTML页面.一个Web服务器,比如Apache,当请求一个PHP页面时,Web服务器会运行PHP.但是在Node.js里,Node.js的main文件代表了整个服务器.No