一、全局对象

JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。

在浏览器 JavaScript 中,通常 window 是全局对象, 而 Node.js 中的全局对象是 global,所有全局变量(除了 global 本身以外)都是 global 对象的属性。

在 Node.js 我们可以直接访问到 global 的属性,而不需要在应用中包含它。

1.1 全局对象与全局变量

global 最根本的作用是作为全局变量的宿主。按照 ECMAScript 的定义,满足以下条 件的变量是全局变量:

** 在最外层定义的变量 **;
** 全局对象的属性 **
** 隐式定义的变量(未定义直接赋值的变量) **

当你定义一个全局变量时,这个变量同时也会成为全局对象的属性,反之亦然。需要注 意的是,在 Node.js 中你不可能在最外层定义变量,因为所有用户代码都是属于当前模块的, 而模块本身不是最外层上下文。

注意: 最好不要使用 var 定义变量以避免引入全局变量,因为全局变量会污染命名空间,提高代码的耦合风险。

1.2 __filename

__filename 表示当前正在执行的脚本的文件名。它将输出文件所在位置的绝对路径,且和命令行参数所指定的文件名不一定相同。 如果在模块中,返回的值是模块文件的路径。

实例

创建文件 global1.js ,代码如下所示:

1
2
// 输出全局变量 __filename 的值
console.log( __filename );

执行 global1.js 文件,结果如下所示:

E:\workspace\workspace(nodejs)\day03\global1.js

1.3 __dirname

__dirname 表示当前执行脚本所在的目录。
实例

修改文件 global1.js ,代码如下所示:

1
2
// 输出全局变量 __dirname 的值
console.log( __dirname );

执行 global1.js 文件,结果如下所示:

E:\workspace\workspace(nodejs)\day03

1.4 setTimeout(cb, ms)

setTimeout(cb, ms) 全局函数在指定的毫秒(ms)数后执行指定函数(cb)。:setTimeout() 只执行一次指定函数。

返回一个代表定时器的句柄值。
实例

修改文件 global1.js ,代码如下所示:

1
2
3
4
5
function printHello(){
console.log( "Hello, World!");
}
// 两秒后执行以上函数
setTimeout(printHello, 2000);

执行 global1.js 文件,结果如下所示:

Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!

1.5 clearTimeout(t)

clearTimeout( t ) 全局函数用于停止一个之前通过 setTimeout() 创建的定时器。 参数 t 是通过 setTimeout() 函数创建的定时器。
实例

修改文件 global1.js ,代码如下所示:

1
2
3
4
5
6
7
8
function printHello(){
console.log( "Hello, World!");
}
// 两秒后执行以上函数
var t = setTimeout(printHello, 2000);

// 清除定时器
clearTimeout(t);

执行 global1.js 文件,结果没有输出。

1.6 setInterval(cb, ms)

setInterval(cb, ms) 全局函数在指定的毫秒(ms)数后执行指定函数(cb)。

返回一个代表定时器的句柄值。可以使用 clearInterval(t) 函数来清除定时器。

setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。
实例

修改文件 global1.js ,代码如下所示:

1
2
3
4
5
function printHello(){
console.log( "Hello, World!");
}
// 两秒后执行以上函数
setInterval(printHello, 2000);

执行 global1.js 文件,结果如下所示:

Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!

1.7 console

console 用于提供控制台标准输出,它是由 Internet Explorer 的 JScript 引擎提供的调试工具,后来逐渐成为浏览器的实施标准。

Node.js 沿用了这个标准,提供与习惯行为一致的 console 对象,用于向标准输出流(stdout)或标准错误流(stderr)输出字符。

方法描述
console.log([data][, ...])向标准输出流打印字符并以换行符结束。该方法接收若干 个参数,如果只有一个参数,则输出这个参数的字符串形式。如果有多个参数,则 以类似于C 语言 printf() 命令的格式输出。
console.info([data][, ...])该命令的作用是返回信息性消息,这个命令与console.log差别并不大,除了在chrome中只会输出文字外,其余的会显示一个蓝色的惊叹号。
console.error([data][, ...])输出错误消息的。控制台在出现错误时会显示是红色的叉子。
console.warn([data][, ...])输出警告消息。控制台出现有黄色的惊叹号。
console.dir(obj[, options])用来对一个对象进行检查(inspect),并以易于阅读和打印的格式显示。
console.time(label)输出时间,表示计时开始。
console.timeEnd(label)结束时间,表示计时结束。
console.trace(message[, ...])当前执行的代码在堆栈中的调用路径,这个测试函数运行很有帮助,只要给想测试的函数里面加入 console.trace 就行了。
console.assert(value[, message][, ...])用于判断某个表达式或变量是否为真,接收两个参数,第一个参数是表达式,第二个参数是字符串。只有当第一个参数为false,才会输出第二个参数,否则不会有任何结果。

console.log():向标准输出流打印字符并以换行符结束。

console.log 接收若干 个参数,如果只有一个参数,则输出这个参数的字符串形式。如果有多个参数,则 以类似于C 语言 printf() 命令的格式输出。

第一个参数是一个字符串,如果没有 参数,只打印一个换行。

1
2
3
console.log('Hello world');
console.log('byvoid%diovyb');
console.log('byvoid%diovyb', 1991);

运行结果为:

Hello world
byvoid%diovyb
byvoid1991iovyb

console.error():与console.log() 用法相同,只是向标准错误流输出。
console.trace():向标准错误流输出当前的调用栈。

1
console.trace();

运行结果为:

Trace
at Object. (E:\workspace\workspace(nodejs)\day03\consolel.js:5:9)
at Module._compile (internal/modules/cjs/loader.js:1063:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
at Module.load (internal/modules/cjs/loader.js:928:32)
at Function.Module._load (internal/modules/cjs/loader.js:769:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
at internal/main/run_main_module.js:17:47

实例

创建文件 console1.js ,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
console.info("程序开始执行:");

var counter = 10;
console.log("计数: %d", counter);

console.time("获取数据");
//
// 执行一些代码
//
console.timeEnd('获取数据');

console.info("程序执行完毕。")

执行 console1.js 文件,结果如下所示:

程序开始执行:
计数: 10
获取数据: 0.125ms
程序执行完毕。

1.8 process

process 是一个全局变量,即 global 对象的属性。

它用于描述当前Node.js 进程状态的对象,提供了一个与操作系统的简单接口。通常在你写本地命令行程序的时候,少不了要 和它打交道。下面将会介绍 process 对象的一些最常用的成员方法。

事件描述
exit当进程准备退出时触发。
beforeExit当 node 清空事件循环,并且没有其他安排时触发这个事件。通常来说,当没有进程安排时 node 退出,但是 'beforeExit' 的监听器可以异步调用,这样 node 就会继续执行。
uncaughtException当一个异常冒泡回到事件循环,触发这个事件。如果给异常添加了监视器,默认的操作(打印堆栈跟踪信息并退出)就不会发生。
Signal 事件当进程接收到信号时就触发。信号列表详见标准的 POSIX 信号名,如 SIGINT、SIGUSR1 等。

实例

创建文件 process.js ,代码如下所示:

1
2
3
4
5
6
7
8
9
process.on('exit', function(code) {
// 以下代码永远不会执行
setTimeout(function() {
console.log("该代码不会执行");
}, 0);

console.log('退出码为:', code);
});
console.log("程序执行结束");

执行 process.js 文件,代码如下所示:

程序执行结束
退出码为: 0

退出状态码

退出状态码如下所示:

名称描述
Uncaught Fatal Exception有未捕获异常,并且没有被域或 uncaughtException 处理函数处理。
Unused保留
Internal JavaScript Parse ErrorJavaScript的源码启动 Node 进程时引起解析错误。非常罕见,仅会在开发 Node 时才会有。
Internal JavaScript Evaluation FailureJavaScript 的源码启动 Node 进程,评估时返回函数失败。非常罕见,仅会在开发 Node 时才会有。
Fatal ErrorV8 里致命的不可恢复的错误。通常会打印到 stderr ,内容为: FATAL ERROR
Non-function Internal Exception Handler未捕获异常,内部异常处理函数不知为何设置为on-function,并且不能被调用。
Internal Exception Handler Run-Time Failure未捕获的异常, 并且异常处理函数处理时自己抛出了异常。例如,如果 process.on('uncaughtException') 或 domain.on('error') 抛出了异常。
Unused保留
Invalid Argument可能是给了未知的参数,或者给的参数没有值。
Internal JavaScript Run-Time FailureJavaScript的源码启动 Node 进程时抛出错误,非常罕见,仅会在开发 Node 时才会有。
Invalid Debug Argument设置了参数--debug 和/或 --debug-brk,但是选择了错误端口。
Signal Exits如果 Node 接收到致命信号,比如SIGKILL 或 SIGHUP,那么退出代码就是128 加信号代码。这是标准的 Unix 做法,退出信号代码放在高位。

Process 属性

Process 提供了很多有用的属性,便于我们更好的控制系统的交互:

属性描述
stdout标准输出流。
stderr标准错误流。
stdin标准输入流。
argvargv 属性返回一个数组,由命令行执行脚本时的各个参数组成。它的第一个成员总是node,第二个成员是脚本文件名,其余成员是脚本文件的参数。
execPath返回执行当前脚本的 Node 二进制文件的绝对路径。
execArgv返回一个数组,成员是命令行下执行脚本时,在Node可执行文件与脚本文件之间的命令行参数。
env返回一个对象,成员为当前 shell 的环境变量
exitCode进程退出时的代码,如果进程优通过 process.exit() 退出,不需要指定退出码。
versionNode 的版本,比如v0.10.18。
versions一个属性,包含了 node 的版本和依赖.
config一个包含用来编译当前 node 执行文件的 javascript 配置选项的对象。它与运行 ./configure 脚本生成的 "config.gypi" 文件相同。
pid当前进程的进程号。
title进程名,默认值为"node",可以自定义该值。
arch当前 CPU 的架构:'arm'、'ia32' 或者 'x64'。
platform运行程序所在的平台系统 'darwin', 'freebsd', 'linux', 'sunos' 或 'win32'
mainModulerequire.main 的备选方法。不同点,如果主模块在运行时改变,require.main可能会继续返回老的模块。可以认为,这两者引用了同一个模块。

实例

修改文件 process.js ,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 输出到终端
process.stdout.write("Hello World!" + "\n");

// 通过参数读取
process.argv.forEach(function(val, index, array) {
console.log(index + ': ' + val);
});

// 获取执行路径
console.log(process.execPath);

// 平台信息
console.log(process.platform);

执行 process.js 文件,结果如下所示:

Hello World!
0: E:\Program\node-v14.13.1\node.exe
1: E:\workspace\workspace(nodejs)\day03\process.js
E:\Program\node-v14.13.1\node.exe
win32

方法参考手册

Process 提供了很多有用的方法,便于我们更好的控制系统的交互:

方法描述
abort()这将导致 node 触发 abort 事件。会让 node 退出并生成一个核心文件。
chdir(directory)改变当前工作进程的目录,如果操作失败抛出异常。
cwd()返回当前进程的工作目录
exit([code])使用指定的 code 结束进程。如果忽略,将会使用 code 0。
getgid()获取进程的群组标识(参见 getgid(2))。获取到得时群组的数字 id,而不是名字。注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
setgid(id)设置进程的群组标识(参见 setgid(2))。可以接收数字 ID 或者群组名。如果指定了群组名,会阻塞等待解析为数字 ID 。注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
getuid()获取进程的用户标识(参见 getuid(2))。这是数字的用户 id,不是用户名。注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
setuid(id)设置进程的用户标识(参见setuid(2))。接收数字 ID或字符串名字。果指定了群组名,会阻塞等待解析为数字 ID 。注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
getgroups()返回进程的群组 iD 数组。POSIX 系统没有保证一定有,但是 node.js 保证有。注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
setgroups(groups)设置进程的群组 ID。这是授权操作,所以你需要有 root 权限,或者有 CAP_SETGID 能力。注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
initgroups(user, extra_group)读取 /etc/group ,并初始化群组访问列表,使用成员所在的所有群组。这是授权操作,所以你需要有 root 权限,或者有 CAP_SETGID 能力。注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
kill(pid[, signal])发送信号给进程. pid 是进程id,并且 signal 是发送的信号的字符串描述。信号名是字符串,比如 'SIGINT' 或 'SIGHUP'。如果忽略,信号会是 'SIGTERM'。
memoryUsage()返回一个对象,描述了 Node 进程所用的内存状况,单位为字节。
nextTick(callback)一旦当前事件循环结束,调用回调函数。
umask([mask])设置或读取进程文件的掩码。子进程从父进程继承掩码。如果mask 参数有效,返回旧的掩码。否则,返回当前掩码。
uptime()返回 Node 已经运行的秒数。
hrtime()返回当前进程的高分辨时间,形式为 [seconds, nanoseconds]数组。它是相对于过去的任意事件。该值与日期无关,因此不受时钟漂移的影响。主要用途是可以通过精确的时间间隔,来衡量程序的性能。你可以将之前的结果传递给当前的 process.hrtime() ,会返回两者间的时间差,用来基准和测量时间间隔。

实例

修改文件 process.js ,代码如下所示:

1
2
3
4
5
6
7
8
// 输出当前目录
console.log('当前目录: ' + process.cwd());

// 输出当前版本
console.log('当前版本: ' + process.version);

// 输出内存使用情况
console.log(process.memoryUsage());

执行 process.js 文件,结果如下所示:

当前目录: E:\workspace\workspace(nodejs)\day03
当前版本: v14.13.1
{
rss: 19464192,
heapTotal: 4726784,
heapUsed: 2911608,
external: 1031529,
arrayBuffers: 9386
}

二、常用工具

util 是一个Node.js 核心模块,提供常用函数的集合,用于弥补核心 JavaScript 的功能 过于精简的不足。

使用方法如下:

1
const util = require('util');

2.1 util.callbackify

util.callbackify(original) 将 async 异步函数(或者一个返回值为 Promise 的函数)转换成遵循异常优先的回调风格的函数,例如将 (err, value) => ... 回调作为最后一个参数。 在回调函数中,第一个参数为拒绝的原因(如果 Promise 解决,则为 null),第二个参数则是解决的值。

实例

创建文件 util1.js ,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
const util = require('util');

async function fn() {
return 'hello world';
}
const callbackFunction = util.callbackify(fn);

callbackFunction((err, ret) => {
if (err) throw err;
console.log(ret);
});

以上代码输出结果为:

hello world

回调函数是异步执行的,并且有异常堆栈错误追踪。 如果回调函数抛出一个异常,进程会触发一个 'uncaughtException' 异常,如果没有被捕获,进程将会退出。

null 在回调函数中作为一个参数有其特殊的意义,如果回调函数的首个参数为 Promise 拒绝的原因且带有返回值,且值可以转换成布尔值 false,这个值会被封装在 Error 对象里,可以通过属性 reason 获取。

original 为 async 异步函数。该函数返回传统回调函数。

2.2 util.inherits

util.inherits(constructor, superConstructor) 是一个实现对象间原型继承的函数。

JavaScript 的面向对象特性是基于原型的,与常见的基于类的不同。JavaScript 没有提供对象继承的语言级别特性,而是通过原型复制来实现的。

在这里我们只介绍 util.inherits 的用法,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var util = require('util');
function Base() {
this.name = 'base';
this.base = 1991;
this.sayHello = function() {
console.log('Hello ' + this.name);
};
}
Base.prototype.showName = function() {
console.log(this.name);
};
function Sub() {
this.name = 'sub';
}
util.inherits(Sub, Base);
var objBase = new Base();
objBase.showName();
objBase.sayHello();
console.log(objBase);
var objSub = new Sub();
objSub.showName();
//objSub.sayHello();
console.log(objSub);

我们定义了一个基础对象 Base 和一个继承自 Base 的 Sub,Base 有三个在构造函数内定义的属性和一个原型中定义的函数,通过util.inherits 实现继承。运行结果如下:

base
Hello base
Base { name: 'base', base: 1991, sayHello: [Function (anonymous)] }
sub
Sub { name: 'sub' }

注意:Sub 仅仅继承了Base 在原型中定义的函数,而构造函数内部创造的 base 属 性和 sayHello 函数都没有被 Sub 继承。

同时,在原型中定义的属性不会被 console.log 作 为对象的属性输出。如果我们去掉 objSub.sayHello(); 这行的注释,将会看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
base
Hello base
Base { name: 'base', base: 1991, sayHello: [Function (anonymous)] }
sub
E:\workspace\workspace(nodejs)\day03\util1.js:36
objSub.sayHello();
^

TypeError: objSub.sayHello is not a function
at Object.<anonymous> (E:\workspace\workspace(nodejs)\day03\util1.js:36:8)
at Module._compile (internal/modules/cjs/loader.js:1063:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
at Module.load (internal/modules/cjs/loader.js:928:32)
at Function.Module._load (internal/modules/cjs/loader.js:769:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
at internal/main/run_main_module.js:17:47

2.3 util.inspect

util.inspect(object,[showHidden],[depth],[colors]) 是一个将任意对象转换 为字符串的方法,通常用于调试和错误输出。它至少接受一个参数 object,即要转换的对象。

showHidden 是一个可选参数,如果值为 true,将会输出更多隐藏信息。

depth 表示最大递归的层数,如果对象很复杂,你可以指定层数以控制输出信息的多 少。如果不指定depth,默认会递归 2 层,指定为 null 表示将不限递归层数完整遍历对象。 如果 colors 值为 true,输出格式将会以 ANSI 颜色编码,通常用于在终端显示更漂亮 的效果。

特别要指出的是,util.inspect 并不会简单地直接把对象转换为字符串,即使该对 象定义了 toString 方法也不会调用。

1
2
3
4
5
6
7
8
9
10
var util = require('util');
function Person() {
this.name = 'byvoid';
this.toString = function() {
return this.name;
};
}
var obj = new Person();
console.log(util.inspect(obj));
console.log(util.inspect(obj, true));

运行结果是:

1
2
3
4
5
6
7
8
9
10
11
Person { name: 'byvoid', toString: [Function (anonymous)] }
Person {
name: 'byvoid',
toString: <ref *1> [Function (anonymous)] {
[length]: 0,
[name]: '',
[arguments]: null,
[caller]: null,
[prototype]: { [constructor]: [Circular *1] }
}
}

2.4 util.isArray(object)

如果给定的参数 "object" 是一个数组返回 true,否则返回 false。

1
2
3
4
5
6
7
8
var util = require('util');

util.isArray([])
// true
util.isArray(new Array)
// true
util.isArray({})
// false

2.5 util.isRegExp(object)

如果给定的参数 "object" 是一个正则表达式返回true,否则返回false。

1
2
3
4
5
6
7
8
var util = require('util');

util.isRegExp(/some regexp/)
// true
util.isRegExp(new RegExp('another regexp'))
// true
util.isRegExp({})
// false

2.6 util.isDate(object)

如果给定的参数 "object" 是一个日期返回true,否则返回false。

1
2
3
4
5
6
7
8
var util = require('util');

util.isDate(new Date())
// true
util.isDate(Date())
// false (without 'new' returns a String)
util.isDate({})
// false

更多详情可以访问 http://nodejs.org/api/util.html 了解详细内容。

三、文件系统

Node.js 提供一组类似 UNIX(POSIX)标准的文件操作API。 Node 导入文件系统模块(fs)语法如下所示:

1
var fs = require("fs")

3.1 异步和同步

Node.js 文件系统(fs 模块)模块中的方法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。

异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error)。

建议大家使用异步方法,比起同步,异步方法性能更高,速度更快,而且没有阻塞。

实例

创建 input.txt 文件,内容如下:

1
Node.js 提供一组类似 UNIX(POSIX)标准的文件操作API。

创建 file.js 文件, 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var fs = require("fs");

// 异步读取
fs.readFile('input.txt', function (err, data) {
if (err) {
return console.error(err);
}
console.log("异步读取: " + data.toString());
});

// 同步读取
var data = fs.readFileSync('input.txt');
console.log("同步读取: " + data.toString());

console.log("程序执行完毕。");

以上代码执行结果如下:

同步读取: Node.js 提供一组类似 UNIX(POSIX)标准的文件操作API。
程序执行完毕。
异步读取: Node.js 提供一组类似 UNIX(POSIX)标准的文件操作API。

接下来,让我们来具体了解下 Node.js 文件系统的方法。

3.2 打开文件

语法

fs.open(path, flags[, mode], callback)

参数

参数使用说明如下:
path - 文件的路径。
flags - 文件打开的行为。具体值详见下文。
mode - 设置文件模式(权限),文件创建默认权限为 0666(可读,可写)。
callback - 回调函数,带有两个参数如:callback(err, fd)。

flags 参数可以是以下值:

Flag描述
r以读取模式打开文件。如果文件不存在抛出异常。
r+以读写模式打开文件。如果文件不存在抛出异常。
rs以同步的方式读取文件。
rs+以同步的方式读取和写入文件。
w以写入模式打开文件,如果文件不存在则创建。
wx类似 'w',但是如果文件路径存在,则文件写入失败。
w+以读写模式打开文件,如果文件不存在则创建。
wx+类似 'w+', 但是如果文件路径存在,则文件读写失败。
a以追加模式打开文件,如果文件不存在则创建。
ax类似 'a', 但是如果文件路径存在,则文件追加失败。
a+以读取追加模式打开文件,如果文件不存在则创建。
ax+类似 'a+', 但是如果文件路径存在,则文件读取追加失败。

实例

接下来我们创建 file.js 文件,并打开 input.txt 文件进行读写,代码如下所示:

1
2
3
4
5
6
7
8
9
10
var fs = require("fs");

// 异步打开文件
console.log("准备打开文件!");
fs.open('input.txt', 'r+', function(err, fd) {
if (err) {
return console.error(err);
}
console.log("文件打开成功!");
});

以上代码执行结果如下:

准备打开文件!
文件打开成功!

3.3 获取文件信息

语法

fs.stat(path, callback)

参数

参数使用说明如下:
path - 文件路径。
callback - 回调函数,带有两个参数如:(err, stats), stats 是 fs.Stats 对象。

fs.stat(path)执行后,会将stats类的实例返回给其回调函数。可以通过stats类中的提供方法判断文件的相关属性。例如判断是否为文件:

1
2
3
4
5
var fs = require('fs');

fs.stat('E:\\workspace\\workspace(nodejs)\\day03\\input.txt', function (err, stats) {
console.log(stats.isFile()); //true
})

stats类中的方法有:

方法描述
stats.isFile()如果是文件返回 true,否则返回 false。
stats.isDirectory()如果是目录返回 true,否则返回 false。
stats.isBlockDevice()如果是块设备返回 true,否则返回 false。
stats.isCharacterDevice()如果是字符设备返回 true,否则返回 false。
stats.isSymbolicLink()如果是软链接返回 true,否则返回 false。
stats.isFIFO()如果是FIFO,返回true,否则返回 false。FIFO是UNIX中的一种特殊类型的命令管道。
stats.isSocket()如果是 Socket 返回 true,否则返回 false。

实例

接下来我们修改 file.js 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var fs = require("fs");

console.log("准备打开文件!");
fs.stat('input.txt', function (err, stats) {
if (err) {
return console.error(err);
}
console.log(stats);
console.log("读取文件信息成功!");

// 检测文件类型
console.log("是否为文件(isFile) ? " + stats.isFile());
console.log("是否为目录(isDirectory) ? " + stats.isDirectory());
});

以上代码执行结果如下:

准备打开文件!
Stats {
dev: 2093865975,
mode: 33206,
nlink: 1,
uid: 0,
gid: 0,
rdev: 0,
blksize: 4096,
ino: 1688849860584246,
size: 69,
blocks: 0,
atimeMs: 1602752855007.6714,
mtimeMs: 1602752855007.6714,
ctimeMs: 1602752855007.6714,
birthtimeMs: 1602752835675.5398,
atime: 2020-10-15T09:07:35.008Z,
mtime: 2020-10-15T09:07:35.008Z,
ctime: 2020-10-15T09:07:35.008Z,
birthtime: 2020-10-15T09:07:15.676Z
}
读取文件信息成功!
是否为文件(isFile) ? true
是否为目录(isDirectory) ? false

3.4 写入文件

语法

fs.writeFile(file, data[, options], callback)

writeFile 直接打开文件默认是 w 模式,所以如果文件存在,该方法写入的内容会覆盖旧的文件内容。

参数

参数使用说明如下:
file - 文件名或文件描述符。
data - 要写入文件的数据,可以是 String(字符串) 或 Buffer(缓冲) 对象。
options - 该参数是一个对象,包含 {encoding, mode, flag}。默认编码为 utf8, 模式为 0666 , flag 为 'w'
callback - 回调函数,回调函数只包含错误信息参数(err),在写入失败时返回。

实例

接下来我们修改 file.js 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var fs = require("fs");

console.log("准备写入文件");
fs.writeFile('input.txt', '我是通 过fs.writeFile 写入文件的内容', function(err) {
if (err) {
return console.error(err);
}
console.log("数据写入成功!");
console.log("--------我是分割线-------------")
console.log("读取写入的数据!");
fs.readFile('input.txt', function (err, data) {
if (err) {
return console.error(err);
}
console.log("异步读取文件数据: " + data.toString());
});
});

以上代码执行结果如下:

准备写入文件
数据写入成功!
--------我是分割线-------------
读取写入的数据!
异步读取文件数据: 我是通 过fs.writeFile 写入文件的内容

3.5 读取文件

语法

fs.read(fd, buffer, offset, length, position, callback)

该方法使用了文件描述符来读取文件。

参数

参数使用说明如下:
fd - 通过 fs.open() 方法返回的文件描述符。
buffer - 数据写入的缓冲区。
offset - 缓冲区写入的写入偏移量。
length - 要从文件中读取的字节数。
position - 文件读取的起始位置,如果 position 的值为 null,则会从当前文件指针的位置读取。
callback - 回调函数,有三个参数err, bytesRead, buffer,err 为错误信息, bytesRead 表示读取的字节数,buffer 为缓冲区对象。

实例

接下来我们修改 file.js 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var fs = require("fs");
var buf = new Buffer.alloc(1024);

console.log("准备打开已存在的文件!");
fs.open('input.txt', 'r+', function(err, fd) {
if (err) {
return console.error(err);
}
console.log("文件打开成功!");
console.log("准备读取文件:");
fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){
if (err){
console.log(err);
}
console.log(bytes + " 字节被读取");

// 仅输出读取的字节
if(bytes > 0){
console.log(buf.slice(0, bytes).toString());
}
});
});

以上代码执行结果如下:

准备打开已存在的文件!
文件打开成功!
准备读取文件:
47 字节被读取
我是通 过fs.writeFile 写入文件的内容

3.6 关闭文件

语法

fs.close(fd, callback)
该方法为异步模式下关闭文件的语法格式,使用了文件描述符来读取文件。

参数

参数使用说明如下:
fd - 通过 fs.open() 方法返回的文件描述符。
callback - 回调函数,没有参数。

实例

接下来我们修改 file.js 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var fs = require("fs");
var buf = new Buffer.alloc(1024);

console.log("准备打开文件!");
fs.open('input.txt', 'r+', function(err, fd) {
if (err) {
return console.error(err);
}
console.log("文件打开成功!");
console.log("准备读取文件!");
fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){
if (err){
console.log(err);
}

// 仅输出读取的字节
if(bytes > 0){
console.log(buf.slice(0, bytes).toString());
}

// 关闭文件
fs.close(fd, function(err){
if (err){
console.log(err);
}
console.log("文件关闭成功");
});
});
});

以上代码执行结果如下:

准备打开文件!
文件打开成功!
准备读取文件!
我是通 过fs.writeFile 写入文件的内容
文件关闭成功

3.7 截取文件

语法

fs.ftruncate(fd, len, callback)
该方法为异步模式下截取文件的语法格式,使用了文件描述符来读取文件。

参数

参数使用说明如下:
fd - 通过 fs.open() 方法返回的文件描述符。
len - 文件内容截取的长度。
callback - 回调函数,没有参数。

实例

接下来我们修改 file.js 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
var fs = require("fs");
var buf = new Buffer.alloc(1024);

console.log("准备打开文件!");
fs.open('input.txt', 'r+', function(err, fd) {
if (err) {
return console.error(err);
}
console.log("文件打开成功!");
console.log("截取10字节内的文件内容,超出部分将被去除。");

// 截取文件
fs.ftruncate(fd, 10, function(err){
if (err){
console.log(err);
}
console.log("文件截取成功。");
console.log("读取相同的文件");
fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){
if (err){
console.log(err);
}

// 仅输出读取的字节
if(bytes > 0){
console.log(buf.slice(0, bytes).toString());
}

// 关闭文件
fs.close(fd, function(err){
if (err){
console.log(err);
}
console.log("文件关闭成功!");
});
});
});
});

以上代码执行结果如下:

准备打开文件!
文件打开成功!
截取10字节内的文件内容,超出部分将被去除。
文件截取成功。
读取相同的文件
我是通
文件关闭成功!

3.8 删除文件

语法

fs.unlink(path, callback)

参数

参数使用说明如下:

path - 文件路径。
callback - 回调函数,没有参数。

实例

接下来我们修改 file.js 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
var fs = require("fs");

console.log("准备删除文件!");
fs.unlink('input.txt', function(err) {
if (err) {
return console.error(err);
}
console.log("文件删除成功!");
});

以上代码执行结果如下:

准备删除文件!
文件删除成功!

再去查看 input.txt 文件,发现已经不存在了。

3.9 创建目录

语法

fs.mkdir(path[, options], callback)

参数

参数使用说明如下:
path - 文件路径。
options 参数可以是:
recursive - 是否以递归的方式创建目录,默认为 false。
mode - 设置目录权限,默认为 0777。
callback - 回调函数,没有参数。

实例

接下来我们修改 file.js 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
var fs = require("fs");
// tmp 目录必须存在
console.log("创建目录 test");
fs.mkdir("test",function(err){
if (err) {
return console.error(err);
}
console.log("目录创建成功。");
});

以上代码执行结果如下:

创建目录 test
目录创建成功。

可以添加 recursive: true 参数,不管创建的目录 tmp 和 tmp/a 是否存在:

1
2
3
fs.mkdir('tmp/a/apple', { recursive: true }, (err) => {
if (err) throw err;
});

3.10 读取目录

语法

fs.readdir(path, callback)

参数

参数使用说明如下:
path - 文件路径。
callback - 回调函数,回调函数带有两个参数err, files,err 为错误信息,files 为 目录下的文件数组列表。

实例

接下来我们修改 file.js 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
var fs = require("fs");

console.log("查看 tmp 目录");
fs.readdir("tmp/",function(err, files){
if (err) {
return console.error(err);
}
files.forEach( function (file){
console.log( file );
});
});

以上代码执行结果如下:

查看 tmp 目录
a

3.11 删除目录

语法

fs.rmdir(path, callback)

参数

参数使用说明如下:
path - 文件路径。
callback - 回调函数,没有参数。

实例

接下来我们修改 file.js 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var fs = require("fs");
// 执行前创建一个空的 tmp/a 目录
console.log("准备删除目录 tmp/a/apple");
fs.rmdir("tmp/a/apple",function(err){
if (err) {
return console.error(err);
}
console.log("读取 tmp/a/apple 目录");
fs.readdir("tmp/a/apple",function(err, files){
if (err) {
return console.error(err);
}
files.forEach( function (file){
console.log( file );
});
});
});

以上代码执行结果如下:

准备删除目录 tmp/a/apple
读取 tmp/a/apple 目录
[Error: ENOENT: no such file or directory, scandir 'E:\workspace\workspace(nodejs)\day03\tmp\a\apple'] {
errno: -4058,
code: 'ENOENT',
syscall: 'scandir',
path: 'E:\workspace\workspace(nodejs)\day03\tmp\a\apple'
}

3.12 文件模块方法参考手册

以下为 Node.js 文件模块相同的方法列表:

方法描述
fs.rename(oldPath, newPath, callback)异步 rename().回调函数没有参数,但可能抛出异常。
fs.ftruncate(fd, len, callback)异步 ftruncate().回调函数没有参数,但可能抛出异常。
fs.ftruncateSync(fd, len)同步 ftruncate()
fs.truncate(path, len, callback)异步 truncate().回调函数没有参数,但可能抛出异常。
fs.truncateSync(path, len)同步 truncate()
fs.chown(path, uid, gid, callback)异步 chown().回调函数没有参数,但可能抛出异常。
fs.chownSync(path, uid, gid)同步 chown()
fs.fchown(fd, uid, gid, callback)异步 fchown().回调函数没有参数,但可能抛出异常。
fs.fchownSync(fd, uid, gid)同步 fchown()
fs.lchown(path, uid, gid, callback)异步 lchown().回调函数没有参数,但可能抛出异常。
fs.lchownSync(path, uid, gid)同步 lchown()
fs.chmod(path, mode, callback)异步 chmod().回调函数没有参数,但可能抛出异常。
fs.chmodSync(path, mode)同步 chmod().
fs.fchmod(fd, mode, callback)异步 fchmod().回调函数没有参数,但可能抛出异常。
fs.fchmodSync(fd, mode)同步 fchmod().
fs.lchmod(path, mode, callback)异步 lchmod().回调函数没有参数,但可能抛出异常。Only available on Mac OS X.
fs.lchmodSync(path, mode)同步 lchmod().
fs.stat(path, callback)异步 stat(). 回调函数有两个参数 err, stats,stats 是 fs.Stats 对象。
fs.lstat(path, callback)异步 lstat(). 回调函数有两个参数 err, stats,stats 是 fs.Stats 对象。
fs.fstat(fd, callback)异步 fstat(). 回调函数有两个参数 err, stats,stats 是 fs.Stats 对象。
fs.statSync(path)同步 stat(). 返回 fs.Stats 的实例。
fs.lstatSync(path)同步 lstat(). 返回 fs.Stats 的实例。
fs.fstatSync(fd)同步 fstat(). 返回 fs.Stats 的实例。
fs.link(srcpath, dstpath, callback)异步 link().回调函数没有参数,但可能抛出异常。
fs.linkSync(srcpath, dstpath)同步 link().
fs.symlink(srcpath, dstpath[, type], callback)异步 symlink().回调函数没有参数,但可能抛出异常。 type 参数可以设置为 'dir', 'file', 或 'junction' (默认为 'file') 。
fs.symlinkSync(srcpath, dstpath[, type])同步 symlink().
fs.readlink(path, callback)异步 readlink(). 回调函数有两个参数 err, linkString。
fs.realpath(path[, cache], callback)异步 realpath(). 回调函数有两个参数 err, resolvedPath。
fs.realpathSync(path[, cache])同步 realpath()。返回绝对路径。
fs.unlink(path, callback)异步 unlink().回调函数没有参数,但可能抛出异常。
fs.unlinkSync(path)同步 unlink().
fs.rmdir(path, callback)异步 rmdir().回调函数没有参数,但可能抛出异常。
fs.rmdirSync(path)同步 rmdir().
fs.mkdir(path[, mode], callback)S异步 mkdir(2).回调函数没有参数,但可能抛出异常。 访问权限默认为 0777。
fs.mkdirSync(path[, mode])同步 mkdir().
fs.readdir(path, callback)异步 readdir(3). 读取目录的内容。
fs.readdirSync(path)同步 readdir().返回文件数组列表。
fs.close(fd, callback)异步 close().回调函数没有参数,但可能抛出异常。
fs.closeSync(fd)同步 close().
fs.open(path, flags[, mode], callback)异步打开文件。
fs.openSync(path, flags[, mode])同步 version of fs.open().
fs.utimes(path, atime, mtime, callback)
fs.utimesSync(path, atime, mtime)修改文件时间戳,文件通过指定的文件路径。
fs.futimes(fd, atime, mtime, callback)
fs.futimesSync(fd, atime, mtime)修改文件时间戳,通过文件描述符指定。
fs.fsync(fd, callback)异步 fsync.回调函数没有参数,但可能抛出异常。
fs.fsyncSync(fd)同步 fsync.
fs.write(fd, buffer, offset, length[, position], callback)将缓冲区内容写入到通过文件描述符指定的文件。
fs.write(fd, data[, position[, encoding]], callback)通过文件描述符 fd 写入文件内容。
fs.writeSync(fd, buffer, offset, length[, position])同步版的 fs.write()。
fs.writeSync(fd, data[, position[, encoding]])同步版的 fs.write().
fs.read(fd, buffer, offset, length, position, callback)通过文件描述符 fd 读取文件内容。
fs.readSync(fd, buffer, offset, length, position)同步版的 fs.read.
fs.readFile(filename[, options], callback)异步读取文件内容。
fs.readFileSync(filename[, options])
fs.writeFile(filename, data[, options], callback)异步写入文件内容。
fs.writeFileSync(filename, data[, options])同步版的 fs.writeFile。
fs.appendFile(filename, data[, options], callback)异步追加文件内容。
fs.appendFileSync(filename, data[, options])The 同步 version of fs.appendFile.
fs.watchFile(filename[, options], listener)查看文件的修改。
fs.unwatchFile(filename[, listener])停止查看 filename 的修改。
fs.watch(filename[, options][, listener])查看 filename 的修改,filename 可以是文件或目录。返回 fs.FSWatcher 对象。
fs.exists(path, callback)检测给定的路径是否存在。
fs.existsSync(path)同步版的 fs.exists.
fs.access(path[, mode], callback)测试指定路径用户权限。
fs.accessSync(path[, mode])同步版的 fs.access。
fs.createReadStream(path[, options])返回ReadStream 对象。
fs.createWriteStream(path[, options])返回 WriteStream 对象。
fs.symlink(srcpath, dstpath[, type], callback)异步 symlink().回调函数没有参数,但可能抛出异常。

四、GET/POST请求

在很多场景中,我们的服务器都需要跟用户的浏览器打交道,如表单提交。

表单提交到服务器一般都使用 GET/POST 请求。

本章节我们将为大家介绍 Node.js GET/POST请求。

4.1 获取GET请求内容

由于GET请求直接被嵌入在路径中,URL是完整的请求路径,包括了?后面的部分,因此你可以手动解析后面的内容作为GET请求的参数。

node.js 中 url 模块中的 parse 函数提供了这个功能。

实例

创建 httpRequest.js 文件, 代码如下:

1
2
3
4
5
6
7
8
var http = require('http');
var url = require('url');
var util = require('util');

http.createServer(function(req, res){
res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
res.end(util.inspect(url.parse(req.url, true)));
}).listen(3000);

在浏览器中访问 http://localhost:3000/user?name=node学习&url=www.wno704.com 然后查看返回结果:

获取 URL 的参数

我们可以使用 url.parse 方法来解析 URL 中的参数,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var http = require('http');
var url = require('url');
var util = require('util');

http.createServer(function(req, res){
res.writeHead(200, {'Content-Type': 'text/plain'});

// 解析 url 参数
var params = url.parse(req.url, true).query;
res.write("网站名:" + params.name);
res.write("\n");
res.write("网站 URL:" + params.url);
res.end();

}).listen(3000);

在浏览器中访问 http://localhost:3000/user?name=node学习&url=www.wno704.com 然后查看返回结果:

4.2 获取 POST 请求内容

POST 请求的内容全部的都在请求体中,http.ServerRequest 并没有一个属性内容为请求体,原因是等待请求体传输可能是一件耗时的工作。

比如上传文件,而很多时候我们可能并不需要理会请求体的内容,恶意的POST请求会大大消耗服务器的资源,所以 node.js 默认是不会解析请求体的,当你需要的时候,需要手动来做。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var http = require('http');
var querystring = require('querystring');
var util = require('util');

http.createServer(function(req, res){
// 定义了一个post变量,用于暂存请求体的信息
var post = '';

// 通过req的data事件监听函数,每当接受到请求体的数据,就累加到post变量中
req.on('data', function(chunk){
post += chunk;
});

// 在end事件触发后,通过querystring.parse将post解析为真正的POST请求格式,然后向客户端返回。
req.on('end', function(){
post = querystring.parse(post);
res.end(util.inspect(post));
});
}).listen(3000);

以下实例表单通过 POST 提交并输出数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var http = require('http');
var querystring = require('querystring');

var postHTML =
'<html><head><meta charset="utf-8"><title>Node.js 实例</title></head>' +
'<body>' +
'<form method="post">' +
'网站名: <input name="name"><br>' +
'网站 URL: <input name="url"><br>' +
'<input type="submit">' +
'</form>' +
'</body></html>';

http.createServer(function (req, res) {
var body = "";
req.on('data', function (chunk) {
body += chunk;
});
req.on('end', function () {
// 解析参数
body = querystring.parse(body);
// 设置响应头部信息及编码
res.writeHead(200, {'Content-Type': 'text/html; charset=utf8'});

if(body.name && body.url) { // 输出提交的数据
res.write("网站名:" + body.name);
res.write("<br>");
res.write("网站 URL:" + body.url);
} else { // 输出表单
res.write(postHTML);
}
res.end();
});
}).listen(3000);

执行结果 Gif 演示:

改造版代码

我们新建 from.html,内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
<meta charset="utf-8">
<title>Node.js 实例</title>
</head>
<body>
<form method="post">
网站名: <input name="name">
<br>
网站 URL: <input name="url">
<br>
<input type="submit"></form>
</body>
</html>

修改httpRequest.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var http = require('http');
var fs = require("fs");

var querystring = require('querystring');
var buf = new Buffer.alloc(1024);

var postHTML = '';

fs.open('from.html', 'r+', function(err, fd) {
if (err) {
return console.error(err);
}
fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){
if (err){
console.log(err);
}
// 仅输出读取的字节
if(bytes > 0){
postHTML = buf.slice(0, bytes).toString()
}
});
});

http.createServer(function (req, res) {
var body = "";
req.on('data', function (chunk) {
body += chunk;
});
req.on('end', function () {
// 解析参数
body = querystring.parse(body);
// 设置响应头部信息及编码
res.writeHead(200, {'Content-Type': 'text/html; charset=utf8'});

if(body.name && body.url) { // 输出提交的数据
res.write("网站名:" + body.name);
res.write("<br>");
res.write("网站 URL:" + body.url);
} else { // 输出表单
res.write(postHTML);
}
res.end();
});
}).listen(3000);

测试效果和上面一样

五、Web 模块

5.1 什么是 Web 服务器?

Web服务器一般指网站服务器,是指驻留于因特网上某种类型计算机的程序,Web服务器的基本功能就是提供Web信息浏览服务。它只需支持HTTP协议、HTML文档格式及URL,与客户端的网络浏览器配合。

大多数 web 服务器都支持服务端的脚本语言(php、python、ruby)等,并通过脚本语言从数据库获取数据,将结果返回给客户端浏览器。

目前最主流的三个Web服务器是Apache、Nginx、IIS。

5.2 Web 应用架构

Client - 客户端,一般指浏览器,浏览器可以通过 HTTP 协议向服务器请求数据。
Server - 服务端,一般指 Web 服务器,可以接收客户端请求,并向客户端发送响应数据。
Business - 业务层, 通过 Web 服务器处理应用程序,如与数据库交互,逻辑运算,调用外部程序等。
Data - 数据层,一般由数据库组成。

5.3 使用 Node 创建 Web 服务器

Node.js 提供了 http 模块,http 模块主要用于搭建 HTTP 服务端和客户端,使用 HTTP 服务器或客户端功能必须调用 http 模块,代码如下:

1
var http = require('http');

以下是演示一个最基本的 HTTP 服务器架构(使用 8080 端口),创建 server.js 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

var http = require('http');
var fs = require('fs');
var url = require('url');


// 创建服务器
http.createServer( function (request, response) {
// 解析请求,包括文件名
var pathname = url.parse(request.url).pathname;

// 输出请求的文件名
console.log("Request for " + pathname + " received.");

// 从文件系统中读取请求的文件内容
fs.readFile(pathname.substr(1), function (err, data) {
if (err) {
console.log(err);
// HTTP 状态码: 404 : NOT FOUND
// Content Type: text/html
response.writeHead(404, {'Content-Type': 'text/html'});
}else{
// HTTP 状态码: 200 : OK
// Content Type: text/html
response.writeHead(200, {'Content-Type': 'text/html'});

// 响应文件内容
response.write(data.toString());
}
// 发送响应数据
response.end();
});
}).listen(8080);

// 控制台会输出以下信息
console.log('Server running at http://127.0.0.1:8080/');

接下来我们在该目录下创建一个 index.html 文件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<h1>我的第一个标题</h1>
<p>我的第一个段落。</p>
</body>
</html>

执行 web.js 文件:

Server running at http://127.0.0.1:8080/

接着我们在浏览器中打开地址: http://127.0.0.1:8080/index.html ,显示如下图所示:

web.js 的控制台输出信息如下:

Server running at http://127.0.0.1:8080/
Request for /index.html received.
Request for /favicon.ico received.
[Error: ENOENT: no such file or directory, open 'E:\workspace\workspace(nodejs)\day03\favicon.ico'] {
errno: -4058,
code: 'ENOENT',
syscall: 'open',
path: 'E:\workspace\workspace(nodejs)\day03\favicon.ico'
}

5.4 使用 Node 创建 Web 客户端

Node 创建 Web 客户端需要引入 http 模块,创建 client.js 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var options = {
host: 'localhost',
port: '8080',
path: '/index.html'
};

// 处理响应的回调函数
var callback = function(response){
// 不断更新数据
var body = '';
response.on('data', function(data) {
body += data;
});

response.on('end', function() {
// 数据接收完成
console.log(body);
});
}
// 向服务端发送请求
var req = http.request(options, callback);
req.end();

新开一个终端,执行 client.js 文件,输出结果如下:

我的第一个标题

我的第一个段落。

web.js 的控制台输出信息如下:

Request for /index.html received.

六、工具模块

在 Node.js 模块库中有很多好用的模块。接下来我们为大家介绍几种常用模块的使用:

模块名描述
OS 模块提供基本的系统操作函数。
Path 模块提供了处理和转换文件路径的工具。
Net 模块用于底层的网络通信。提供了服务端和客户端的的操作。
DNS 模块用于解析域名。
Domain 模块简化异步代码的异常处理,可以捕捉处理try catch无法捕捉的。

6.1 OS 模块

Node.js os 模块提供了一些基本的系统操作函数。我们可以通过以下方式引入该模块:
var os = require("os");

方法

方法描述
os.tmpdir()返回操作系统的默认临时文件夹。
os.endianness()返回 CPU 的字节序,可能的是 "BE" 或 "LE"。
os.hostname()返回操作系统的主机名。
os.type()返回操作系统名
os.platform()返回编译时的操作系统名
os.arch()返回操作系统 CPU 架构,可能的值有 "x64"、"arm" 和 "ia32"。
os.release()返回操作系统的发行版本。
os.uptime()返回操作系统运行的时间,以秒为单位。
os.loadavg()返回一个包含 1、5、15 分钟平均负载的数组。
os.totalmem()返回系统内存总量,单位为字节。
os.freemem()返回操作系统空闲内存量,单位是字节。
os.cpus()返回一个对象数组,包含所安装的每个 CPU/内核的信息:型号、速度(单位 MHz)、时间(一个包含 user、nice、sys、idle 和 irq 所使用 CPU/内核毫秒数的对象)。
os.networkInterfaces()获得网络接口列表。

属性

属性描述
os.EOL定义了操作系统的行尾符的常量。

实例

创建 os.js 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var os = require("os");

// CPU 的字节序
console.log('endianness : ' + os.endianness());

// 操作系统名
console.log('type : ' + os.type());

// 操作系统名
console.log('platform : ' + os.platform());

// 系统内存总量
console.log('total memory : ' + os.totalmem() + " bytes.");

// 操作系统空闲内存量
console.log('free memory : ' + os.freemem() + " bytes.");

代码执行结果如下:

endianness : LE
type : Windows_NT
platform : win32
total memory : 17047015424 bytes.
free memory : 9508794368 bytes.

6.2 Path 模块

Node.js path 模块提供了一些用于处理文件路径的小工具,我们可以通过以下方式引入该模块:
var path = require("path")

方法

方法描述
path.normalize(p)规范化路径,注意'..' 和 '.'。
path.join([path1][, path2][, ...])用于连接路径。该方法的主要用途在于,会正确使用当前系统的路径分隔符,Unix系统是"/",Windows系统是""。
path.resolve([from ...], to)将 to 参数解析为绝对路径,给定的路径的序列是从右往左被处理的,后面每个 path 被依次解析,直到构造完成一个绝对路径。 例如,给定的路径片段的序列为:/foo、/bar、baz,则调用 path.resolve('/foo', '/bar', 'baz') 会返回 /bar/baz。
path.isAbsolute(path)判断参数 path 是否是绝对路径。
path.relative(from, to)用于将绝对路径转为相对路径,返回从 from 到 to 的相对路径(基于当前工作目录)。在 Linux 上:path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb'); 在 Windows 上:path.relative('C:\orandea\test\aaa', 'C:\orandea\impl\bbb');
path.dirname(p)返回路径中代表文件夹的部分,同 Unix 的dirname 命令类似。
path.basename(p[, ext])返回路径中的最后一部分。同 Unix 命令 bashname 类似。
path.extname(p)返回路径中文件的后缀名,即路径中最后一个'.'之后的部分。如果一个路径中并不包含'.'或该路径只包含一个'.' 且这个'.'为路径的第一个字符,则此命令返回空字符串。
path.parse(pathString)返回路径字符串的对象。
path.format(pathObject)从对象中返回路径字符串,和 path.parse 相反。

属性

属性描述
path.sep平台的文件路径分隔符,'\' 或 '/'。
path.delimiter平台的分隔符, ; or ':'.
path.posix提供上述 path 的方法,不过总是以 posix 兼容的方式交互。
path.win32提供上述 path 的方法,不过总是以 win32 兼容的方式交互。

实例

创建 path.js 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
var path = require("path");

// 格式化路径
console.log('normalization : ' + path.normalize('test/test1//2slashes/1slash/tab/..'));

// 连接路径
console.log('joint path : ' + path.join('test', 'test1', '2slashes/1slash', 'tab', '..'));

// 转换为绝对路径
console.log('resolve : ' + path.resolve('main.js'));

// 路径中文件的后缀名
console.log('ext name : ' + path.extname('main.js'));

代码执行结果如下:

normalization : test\test1\2slashes\1slash
joint path : test\test1\2slashes\1slash
resolve : E:\workspace\workspace(nodejs)\day03\main.js
ext name : .js

6.3 Net 模块

Node.js Net 模块提供了一些用于底层的网络通信的小工具,包含了创建服务器/客户端的方法,我们可以通过以下方式引入该模块:
var net = require("net")

方法

方法描述
net.createServer([options][, connectionListener])创建一个 TCP 服务器。参数 connectionListener 自动给 'connection' 事件创建监听器。
net.connect(options[, connectionListener])返回一个新的 'net.Socket',并连接到指定的地址和端口。当 socket 建立的时候,将会触发 'connect' 事件。
net.createConnection(options[, connectionListener])创建一个到端口 port 和 主机 host的 TCP 连接。 host 默认为 'localhost'。
net.connect(port[, host][, connectListener])创建一个端口为 port 和主机为 host的 TCP 连接 。host 默认为 'localhost'。参数 connectListener 将会作为监听器添加到 'connect' 事件。返回 'net.Socket'。
net.createConnection(port[, host][, connectListener])创建一个端口为 port 和主机为 host的 TCP 连接 。host 默认为 'localhost'。参数 connectListener 将会作为监听器添加到 'connect' 事件。返回 'net.Socket'。
net.connect(path[, connectListener])创建连接到 path 的 unix socket 。参数 connectListener 将会作为监听器添加到 'connect' 事件上。返回 'net.Socket'。
net.createConnection(path[, connectListener])创建连接到 path 的 unix socket 。参数 connectListener 将会作为监听器添加到 'connect' 事件。返回 'net.Socket'。
net.isIP(input)检测输入的是否为 IP 地址。 IPV4 返回 4, IPV6 返回 6,其他情况返回 0。
net.isIPv4(input)如果输入的地址为 IPV4, 返回 true,否则返回 false。
net.isIPv6(input)如果输入的地址为 IPV6, 返回 true,否则返回 false。

net.Server

net.Server通常用于创建一个 TCP 或本地服务器。

方法
方法描述
server.listen(port[, host][, backlog][, callback])监听指定端口 port 和 主机 host ac连接。 默认情况下 host 接受任何 IPv4 地址(INADDR_ANY)的直接连接。端口 port 为 0 时,则会分配一个随机端口。
server.listen(path[, callback])通过指定 path 的连接,启动一个本地 socket 服务器。
server.listen(handle[, callback])通过指定句柄连接。
server.listen(options[, callback])options 的属性:端口 port, 主机 host, 和 backlog, 以及可选参数 callback 函数, 他们在一起调用server.listen(port, [host], [backlog], [callback])。还有,参数 path 可以用来指定 UNIX socket。
server.close([callback])服务器停止接收新的连接,保持现有连接。这是异步函数,当所有连接结束的时候服务器会关闭,并会触发 'close' 事件。
server.address()操作系统返回绑定的地址,协议族名和服务器端口。
server.unref()如果这是事件系统中唯一一个活动的服务器,调用 unref 将允许程序退出。
server.ref()与 unref 相反,如果这是唯一的服务器,在之前被 unref 了的服务器上调用 ref 将不会让程序退出(默认行为)。如果服务器已经被 ref,则再次调用 ref 并不会产生影响。
server.getConnections(callback)异步获取服务器当前活跃连接的数量。当 socket 发送给子进程后才有效;回调函数有 2 个参数 err 和 count。
net.Server 事件
事件描述
listening当服务器调用 server.listen 绑定后会触发。
connection当新连接创建后会被触发。socket 是 net.Socket实例。
close服务器关闭时会触发。注意,如果存在连接,这个事件不会被触发直到所有的连接关闭。
error发生错误时触发。'close' 事件将被下列事件直接调用。

net.Socket

net.Socket 对象是 TCP 或 UNIX Socket 的抽象。net.Socket 实例实现了一个双工流接口。 他们可以在用户创建客户端(使用 connect())时使用, 或者由 Node 创建它们,并通过 connection 服务器事件传递给用户。
事件

net.Socket 事件
事件描述
lookup在解析域名后,但在连接前,触发这个事件。对 UNIX sokcet 不适用。
connect成功建立 socket 连接时触发。
data当接收到数据时触发。
end当 socket 另一端发送 FIN 包时,触发该事件。
timeout当 socket 空闲超时时触发,仅是表明 socket 已经空闲。用户必须手动关闭连接。
drain当写缓存为空得时候触发。可用来控制上传。
error错误发生时触发。
close当 socket 完全关闭时触发。参数 had_error 是布尔值,它表示是否因为传输错误导致 socket 关闭。
属性

net.Socket 提供了很多有用的属性,便于控制 socket 交互:

属性描述
socket.bufferSize该属性显示了要写入缓冲区的字节数。
socket.remoteAddress远程的 IP 地址字符串,例如:'74.125.127.100' or '2001:4860:a005::68'。
socket.remoteFamily远程IP协议族字符串,比如 'IPv4' or 'IPv6'。
socket.remotePort远程端口,数字表示,例如:80 or 21。
socket.localAddress网络连接绑定的本地接口 远程客户端正在连接的本地 IP 地址,字符串表示。例如,如果你在监听'0.0.0.0'而客户端连接在'192.168.1.1',这个值就会是 '192.168.1.1'。
socket.localPort本地端口地址,数字表示。例如:80 or 21。
socket.bytesRead接收到得字节数。
socket.bytesWritten发送的字节数。
方法
方法描述
new net.Socket([options])构造一个新的 socket 对象。
socket.connect(port[, host][, connectListener])指定端口 port 和 主机 host,创建 socket 连接 。参数 host 默认为 localhost。通常情况不需要使用 net.createConnection 打开 socket。只有你实现了自己的 socket 时才会用到。
socket.connect(path[, connectListener])打开指定路径的 unix socket。通常情况不需要使用 net.createConnection 打开 socket。只有你实现了自己的 socket 时才会用到。
socket.setEncoding([encoding])设置编码
socket.write(data[, encoding][, callback])在 socket 上发送数据。第二个参数指定了字符串的编码,默认是 UTF8 编码。
socket.end([data][, encoding])半关闭 socket。例如,它发送一个 FIN 包。可能服务器仍在发送数据。
socket.destroy()确保没有 I/O 活动在这个套接字上。只有在错误发生情况下才需要。(处理错误等等)。
socket.pause()暂停读取数据。就是说,不会再触发 data 事件。对于控制上传非常有用。
socket.resume()调用 pause() 后想恢复读取数据。
socket.setTimeout(timeout[, callback])socket 闲置时间超过 timeout 毫秒后 ,将 socket 设置为超时。
socket.setNoDelay([noDelay])禁用纳格(Nagle)算法。默认情况下 TCP 连接使用纳格算法,在发送前他们会缓冲数据。将 noDelay 设置为 true 将会在调用 socket.write() 时立即发送数据。noDelay 默认值为 true。
socket.setKeepAlive([enable][, initialDelay])禁用/启用长连接功能,并在发送第一个在闲置 socket 上的长连接 probe 之前,可选地设定初始延时。默认为 false。 设定 initialDelay (毫秒),来设定收到的最后一个数据包和第一个长连接probe之间的延时。将 initialDelay 设为0,将会保留默认(或者之前)的值。默认值为0.
socket.address()操作系统返回绑定的地址,协议族名和服务器端口。返回的对象有 3 个属性,比如{ port: 12346, family: 'IPv4', address: '127.0.0.1' }。
socket.unref()如果这是事件系统中唯一一个活动的服务器,调用 unref 将允许程序退出。如果服务器已被 unref,则再次调用 unref 并不会产生影响。
socket.ref()与 unref 相反,如果这是唯一的服务器,在之前被 unref 了的服务器上调用 ref 将不会让程序退出(默认行为)。如果服务器已经被 ref,则再次调用 ref 并不会产生影响。

实例

创建 netserver.js 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
var net = require('net');
var server = net.createServer(function(connection) {
console.log('client connected');
connection.on('end', function() {
console.log('客户端关闭连接');
});
connection.write('Hello World!\r\n');
connection.pipe(connection);
});
server.listen(8080, function() {
console.log('server is listening');
});

执行以上服务端代码:

server is listening

新开一个窗口,创建 netclient.js 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
var net = require('net');
var client = net.connect({port: 8080}, function() {
console.log('连接到服务器!');
});
client.on('data', function(data) {
console.log(data.toString());
client.end();
});
client.on('end', function() {
console.log('断开与服务器的连接');
});

执行以上客户端的代码:

1
2
3
4
连接到服务器!
Hello World!

断开与服务器的连接

Gif 实例演示

6.4 DNS 模块

Node.js DNS 模块用于解析域名。引入 DNS 模块语法格式如下:
var dns = require("dns")

方法

方法描述
dns.lookup(hostname[, options], callback)将域名(比如 'runoob.com')解析为第一条找到的记录 A (IPV4)或 AAAA(IPV6)。参数 options可以是一个对象或整数。如果没有提供 options,IP v4 和 v6 地址都可以。如果 options 是整数,则必须是 4 或 6。
dns.lookupService(address, port, callback)使用 getnameinfo 解析传入的地址和端口为域名和服务。
dns.resolve(hostname[, rrtype], callback)将一个域名(如 'runoob.com')解析为一个 rrtype 指定记录类型的数组。
dns.resolve4(hostname, callback)和 dns.resolve() 类似, 仅能查询 IPv4 (A 记录)。 addresses IPv4 地址数组 (比如,['74.125.79.104', '74.125.79.105', '74.125.79.106'])。
dns.resolve6(hostname, callback)和 dns.resolve4() 类似, 仅能查询 IPv6( AAAA 查询)
dns.resolveMx(hostname, callback)和 dns.resolve() 类似, 仅能查询邮件交换(MX 记录)。
dns.resolveTxt(hostname, callback)和 dns.resolve() 类似, 仅能进行文本查询 (TXT 记录)。 addresses 是 2-d 文本记录数组。(比如,[ ['v=spf1 ip4:0.0.0.0 ', '~all' ] ])。 每个子数组包含一条记录的 TXT 块。根据使用情况可以连接在一起,也可单独使用。
dns.resolveSrv(hostname, callback)和 dns.resolve() 类似, 仅能进行服务记录查询 (SRV 记录)。 addresses 是 hostname可用的 SRV 记录数组。 SRV 记录属性有优先级(priority),权重(weight), 端口(port), 和名字(name) (比如,[{'priority': 10, 'weight': 5, 'port': 21223, 'name': 'service.example.com'}, ...])。
dns.resolveSoa(hostname, callback)和 dns.resolve() 类似, 仅能查询权威记录(SOA 记录)。
dns.resolveNs(hostname, callback)和 dns.resolve() 类似, 仅能进行域名服务器记录查询(NS 记录)。 addresses 是域名服务器记录数组(hostname 可以使用) (比如, ['ns1.example.com', 'ns2.example.com'])。
dns.resolveCname(hostname, callback)和 dns.resolve() 类似, 仅能进行别名记录查询 (CNAME记录)。addresses 是对 hostname 可用的别名记录数组 (比如,, ['bar.example.com'])。
dns.reverse(ip, callback)反向解析 IP 地址,指向该 IP 地址的域名数组。
dns.getServers()返回一个用于当前解析的 IP 地址数组的字符串。
dns.setServers(servers)指定一组 IP 地址作为解析服务器。

rrtypes

以下列出了 dns.resolve() 方法中有效的 rrtypes值:

'A' IPV4 地址, 默认
'AAAA' IPV6 地址
'MX' 邮件交换记录
'TXT' text 记录
'SRV' SRV 记录
'PTR' 用来反向 IP 查找
'NS' 域名服务器记录
'CNAME' 别名记录
'SOA' 授权记录的初始值

错误码

每次 DNS 查询都可能返回以下错误码:

dns.NODATA: 无数据响应。
dns.FORMERR: 查询格式错误。
dns.SERVFAIL: 常规失败。
dns.NOTFOUND: 没有找到域名。
dns.NOTIMP: 未实现请求的操作。
dns.REFUSED: 拒绝查询。
dns.BADQUERY: 查询格式错误。
dns.BADNAME: 域名格式错误。
dns.BADFAMILY: 地址协议不支持。
dns.BADRESP: 回复格式错误。
dns.CONNREFUSED: 无法连接到 DNS 服务器。
dns.TIMEOUT: 连接 DNS 服务器超时。
dns.EOF: 文件末端。
dns.FILE: 读文件错误。
dns.NOMEM: 内存溢出。
dns.DESTRUCTION: 通道被摧毁。
dns.BADSTR: 字符串格式错误。
dns.BADFLAGS: 非法标识符。
dns.NONAME: 所给主机不是数字。
dns.BADHINTS: 非法HINTS标识符。
dns.NOTINITIALIZED: c c-ares 库尚未初始化。
dns.LOADIPHLPAPI: 加载 iphlpapi.dll 出错。
dns.ADDRGETNETWORKPARAMS: 无法找到 GetNetworkParams 函数。
dns.CANCELLED: 取消 DNS 查询。

实例

创建 dns.js 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
var dns = require('dns');

dns.lookup('www.github.com', function onLookup(err, address, family) {
console.log('ip 地址:', address);
dns.reverse(address, function (err, hostnames) {
if (err) {
console.log(err.stack);
}

console.log('反向解析 ' + address + ': ' + JSON.stringify(hostnames));
});
});

执行以上代码,结果如下所示:

ip 地址: 13.250.177.223
反向解析 13.250.177.223: ["ec2-13-250-177-223.ap-southeast-1.compute.amazonaws.com"]

6.5 Domain 模块

Node.js Domain(域) 简化异步代码的异常处理,可以捕捉处理try catch无法捕捉的异常。引入 Domain 模块 语法格式如下:
var domain = require("domain")

domain模块,把处理多个不同的IO的操作作为一个组。注册事件和回调到domain,当发生一个错误事件或抛出一个错误时,domain对象会被通知,不会丢失上下文环境,也不导致程序错误立即退出,与process.on('uncaughtException')不同。

Domain 模块可分为隐式绑定和显式绑定:

隐式绑定: 把在domain上下文中定义的变量,自动绑定到domain对象
显式绑定: 把不是在domain上下文中定义的变量,以代码的方式绑定到domain对象

方法

方法描述
domain.run(function)在域的上下文运行提供的函数,隐式的绑定了所有的事件分发器,计时器和底层请求。
domain.add(emitter)显式的增加事件
domain.remove(emitter)删除事件。
domain.bind(callback)返回的函数是一个对于所提供的回调函数的包装函数。当调用这个返回的函数时,所有被抛出的错误都会被导向到这个域的 error 事件。
domain.intercept(callback)和 domain.bind(callback) 类似。除了捕捉被抛出的错误外,它还会拦截 Error 对象作为参数传递到这个函数。
domain.enter()进入一个异步调用的上下文,绑定到domain。
domain.exit()退出当前的domain,切换到不同的链的异步调用的上下文中。对应domain.enter()。
domain.dispose()释放一个domain对象,让node进程回收这部分资源。
domain.create()返回一个domain对象。

属性

属性描述
domain.members已加入domain对象的域定时器和事件发射器的数组。

实例

创建 domain.js 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
var EventEmitter = require("events").EventEmitter;
var domain = require("domain");

var emitter1 = new EventEmitter();

// 创建域
var domain1 = domain.create();

domain1.on('error', function(err){
console.log("domain1 处理这个错误 ("+err.message+")");
});

// 显式绑定
domain1.add(emitter1);

emitter1.on('error',function(err){
console.log("监听器处理此错误 ("+err.message+")");
});

emitter1.emit('error',new Error('通过监听器来处理'));

emitter1.removeAllListeners('error');

emitter1.emit('error',new Error('通过 domain1 处理'));

var domain2 = domain.create();

domain2.on('error', function(err){
console.log("domain2 处理这个错误 ("+err.message+")");
});

// 隐式绑定
domain2.run(function(){
var emitter2 = new EventEmitter();
emitter2.emit('error',new Error('通过 domain2 处理'));
});


domain1.remove(emitter1);
emitter1.emit('error', new Error('转换为异常,系统将崩溃!'));

执行以上代码,结果如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
监听器处理此错误 (通过监听器来处理)
domain1 处理这个错误 (通过 domain1 处理)
domain2 处理这个错误 (通过 domain2 处理)
events.js:291
throw er; // Unhandled 'error' event
^

Error: 转换为异常,系统将崩溃!
at Object.<anonymous> (E:\workspace\workspace(nodejs)\day03\domain.js:40:24)
at Module._compile (internal/modules/cjs/loader.js:1063:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
at Module.load (internal/modules/cjs/loader.js:928:32)
at Function.Module._load (internal/modules/cjs/loader.js:769:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
at internal/main/run_main_module.js:17:47
Emitted 'error' event at:
at Object.<anonymous> (E:\workspace\workspace(nodejs)\day03\domain.js:40:10)
at Module._compile (internal/modules/cjs/loader.js:1063:30)
[... lines matching original stack trace ...]
at internal/main/run_main_module.js:17:47