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

简介:

本节书摘来自异步社区《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中的按键功能。


fc13444152655ca8e8b2a58f1d86af19ae7cd650

如果你担心花很多时间在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所示。


308f0f474a17d0006a4776a35ed183100cb46659

同样我也尝试过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
 >>
相关文章
|
3月前
|
JSON JavaScript 前端开发
JS服务端技术—Node.js知识点
本篇文章是我开始系统学习Node.js的一些笔记。如果文中阐述不全或不对的,多多交流。
62 0
JS服务端技术—Node.js知识点
|
10天前
|
JavaScript 前端开发 持续交付
【专栏】构建现代Web应用:Vue.js与Node.js的完美结合
【4月更文挑战第27天】本文探讨了Vue.js和Node.js如何结合构建现代Web应用。Vue.js作为轻量级前端框架,以其简洁易懂、组件化开发、双向数据绑定和虚拟DOM等特点受到青睐;而Node.js是高性能后端平台,具备事件驱动、非阻塞I/O、丰富生态系统和跨平台优势。两者结合实现前后端分离,高效通信,并支持热更新、持续集成、跨平台和多端适配,为开发高性能、易维护的Web应用提供强有力的支持。
|
29天前
报错/ ./node_modules/axios/lib/platform/index.js Module parse failed: Unexpected token (5:2)怎么解决?
报错/ ./node_modules/axios/lib/platform/index.js Module parse failed: Unexpected token (5:2)怎么解决?
|
7天前
|
JavaScript 前端开发 开发工具
【JavaScript 技术专栏】Node.js 基础与实战
【4月更文挑战第30天】本文介绍了Node.js的基础及应用,包括事件驱动的非阻塞I/O、单线程模型和模块系统。内容涵盖Node.js的安装配置、核心模块(如http、fs、path)及实战应用,如Web服务器、文件操作和实时通信。文章还讨论了Node.js的优劣势、与其他技术的结合,并通过案例分析展示项目实施流程。总结来说,Node.js是高效后端开发工具,适合构建高并发应用,其广阔的应用前景值得开发者探索。
|
7天前
|
JavaScript API 开发者
深入了解Node.js的文件系统:Node.js文件系统API的使用与探索
【4月更文挑战第30天】本文深入探讨了Node.js的文件系统API,介绍了如何引入`fs`模块进行文件操作。内容包括异步读取和写入文件、删除文件、创建目录以及使用文件流进行高效操作。此外,还提到了文件系统的监视功能,帮助开发者全面掌握在Node.js中处理文件和目录的方法。
|
13天前
|
Web App开发 JavaScript 前端开发
js开发:请解释什么是Node.js,以及它的应用场景。
Node.js是基于V8引擎的JavaScript运行时,用于服务器端编程。以其事件驱动、非阻塞I/O模型著称,适用于高并发和实时应用。常见用途包括:构建Web服务器、实时应用(如聊天)、API服务、微服务、工具和命令行应用,以及搭配Electron开发桌面软件。
20 1
|
25天前
|
小程序 开发工具 开发者
【微信小程序】微信开发者工具 引用 vant-weapp时“miniprogram/node_modules/@babel/runtime/index.js: 未找到npm包入口文件” 解决办法
【微信小程序】微信开发者工具 引用 vant-weapp时“miniprogram/node_modules/@babel/runtime/index.js: 未找到npm包入口文件” 解决办法
22 1
|
2月前
Module build failed (from ./node_modules/eslint-loader/index.js)
Module build failed (from ./node_modules/eslint-loader/index.js)
89 0
Module build failed (from ./node_modules/eslint-loader/index.js)
|
2月前
|
JavaScript 前端开发 Serverless
函数计算新功能— 支持 Node.js 18 、Node.js 20 运行时
从2024年2月起,函数计算正式发布 Node.js 18 运行时和 Nodejs.20 运行时,函数计算2.0和函数计算3.0都支持新的运行时,目前新运行时处在公测状态,欢迎大家来体验。
475 0
|
3月前
|
Web App开发 JavaScript 前端开发
构建现代Web应用:Vue.js与Node.js的完美结合
在当今快速发展的Web技术领域,选择合适的技术栈对于开发高效、响应迅速的现代Web应用至关重要。本文深入探讨了Vue.js和Node.js结合使用的优势,以及如何利用这两种技术构建一个完整的前后端分离的Web应用。不同于传统的摘要,我们将通过一个实际的项目示例,展示从搭建项目架构到实现具体功能的整个过程,着重介绍了Vue.js在构建用户友好的界面方面的能力,以及Node.js在处理服务器端逻辑和数据库交互中的高效性。通过本文,读者不仅能够理解Vue.js与Node.js各自的特点,还能学习到如何将这两种技术融合应用,以提升Web应用的开发效率和用户体验。