Nodejs中,有一个系统模块,名为Util。笔者理解,这就是一个工具类,有时也称为Utility或者Helper,是一些常用的,方便使用的,帮助开发的功能和方法集合。
由于它的分类,本来就比较杂,很多内容在普通的开发过程中可能用到的机会也不是特别大,这里笔者尝试对其进行分类,并列举几个比较常用或者特别的项目。
类和定义
util是一个nodejs的内置类,它的完整类定义如下:
shell
[root@john-eosdev xdata3]# node
Welcome to Node.js v21.7.2.
Type ".help" for more information.
> util
{
_errnoException: [Function: _errnoException],
_exceptionWithHostPort: [Function: _exceptionWithHostPort],
_extend: [Function: _extend],
callbackify: [Function: callbackify],
debug: [Function: debuglog],
debuglog: [Function: debuglog],
deprecate: [Function: deprecate],
format: [Function: format],
styleText: [Function: styleText],
formatWithOptions: [Function: formatWithOptions],
getSystemErrorMap: [Function: getSystemErrorMap],
getSystemErrorName: [Function: getSystemErrorName],
inherits: [Function: inherits],
inspect: [Function: inspect] {
custom: Symbol(nodejs.util.inspect.custom),
colors: [Object: null prototype] {
reset: [Array],
bold: [Array],
dim: [Array],
italic: [Array],
underline: [Array],
blink: [Array],
inverse: [Array],
hidden: [Array],
strikethrough: [Array],
doubleunderline: [Array],
black: [Array],
red: [Array],
green: [Array],
yellow: [Array],
blue: [Array],
magenta: [Array],
cyan: [Array],
white: [Array],
bgBlack: [Array],
bgRed: [Array],
bgGreen: [Array],
bgYellow: [Array],
bgBlue: [Array],
bgMagenta: [Array],
bgCyan: [Array],
bgWhite: [Array],
framed: [Array],
overlined: [Array],
gray: [Array],
redBright: [Array],
greenBright: [Array],
yellowBright: [Array],
blueBright: [Array],
magentaBright: [Array],
cyanBright: [Array],
whiteBright: [Array],
bgGray: [Array],
bgRedBright: [Array],
bgGreenBright: [Array],
bgYellowBright: [Array],
bgBlueBright: [Array],
bgMagentaBright: [Array],
bgCyanBright: [Array],
bgWhiteBright: [Array]
},
styles: [Object: null prototype] {
special: 'cyan',
number: 'yellow',
bigint: 'yellow',
boolean: 'yellow',
undefined: 'grey',
null: 'bold',
string: 'green',
symbol: 'green',
date: 'magenta',
regexp: 'red',
module: 'underline'
},
replDefaults: [Getter/Setter]
},
isArray: [Function: isArray],
isBoolean: [Function: isBoolean],
isBuffer: [Function: isBuffer],
isDeepStrictEqual: [Function: isDeepStrictEqual],
isNull: [Function: isNull],
isNullOrUndefined: [Function: isNullOrUndefined],
isNumber: [Function: isNumber],
isString: [Function: isString],
isSymbol: [Function: isSymbol],
isUndefined: [Function: isUndefined],
isRegExp: [Function: isRegExp],
isObject: [Function: isObject],
isDate: [Function: isDate],
isError: [Function: isError],
isFunction: [Function: isFunction],
isPrimitive: [Function: isPrimitive],
log: [Function: log],
promisify: [Function: promisify] { custom: Symbol(nodejs.util.promisify.custom) },
stripVTControlCharacters: [Function: stripVTControlCharacters],
toUSVString: [Function: toUSVString],
transferableAbortSignal: [Getter],
transferableAbortController: [Getter],
aborted: [Getter],
types: {
isExternal: [Function: isExternal],
isDate: [Function: isDate],
isArgumentsObject: [Function: isArgumentsObject],
isBigIntObject: [Function: isBigIntObject],
isBooleanObject: [Function: isBooleanObject],
isNumberObject: [Function: isNumberObject],
isStringObject: [Function: isStringObject],
isSymbolObject: [Function: isSymbolObject],
isNativeError: [Function: isNativeError],
isRegExp: [Function: isRegExp],
isAsyncFunction: [Function: isAsyncFunction],
isGeneratorFunction: [Function: isGeneratorFunction],
isGeneratorObject: [Function: isGeneratorObject],
isPromise: [Function: isPromise],
isMap: [Function: isMap],
isSet: [Function: isSet],
isMapIterator: [Function: isMapIterator],
isSetIterator: [Function: isSetIterator],
isWeakMap: [Function: isWeakMap],
isWeakSet: [Function: isWeakSet],
isArrayBuffer: [Function: isArrayBuffer],
isDataView: [Function: isDataView],
isSharedArrayBuffer: [Function: isSharedArrayBuffer],
isProxy: [Function: isProxy],
isModuleNamespaceObject: [Function: isModuleNamespaceObject],
isAnyArrayBuffer: [Function: isAnyArrayBuffer],
isBoxedPrimitive: [Function: isBoxedPrimitive],
isArrayBufferView: [Function: isView],
isTypedArray: [Function: isTypedArray],
isUint8Array: [Function: isUint8Array],
isUint8ClampedArray: [Function: isUint8ClampedArray],
isUint16Array: [Function: isUint16Array],
isUint32Array: [Function: isUint32Array],
isInt8Array: [Function: isInt8Array],
isInt16Array: [Function: isInt16Array],
isInt32Array: [Function: isInt32Array],
isFloat32Array: [Function: isFloat32Array],
isFloat64Array: [Function: isFloat64Array],
isBigInt64Array: [Function: isBigInt64Array],
isBigUint64Array: [Function: isBigUint64Array],
isKeyObject: [Function: value],
isCryptoKey: [Function: value]
},
parseEnv: [Function: parseEnv],
parseArgs: [Getter/Setter],
TextDecoder: [Getter/Setter],
TextEncoder: [Getter/Setter],
MIMEType: [Getter/Setter],
MIMEParams: [Getter/Setter]
}
>
虽然,util是一个nodejs的内置模块,但不是全局变量,需要直接引用,然后就可以使用了。
const util = require('node:util');
isDeepStrictEqual
这是一个比较有用的功能,用于判断两个对象的内容和值是完全相等的。如果没有这个方法,自己来实现,需要考虑到很多的因素,也不一定能够做得很好。
类型判断
使用util进行类型判断,是一个比较常见的需求和场景。从其类定义来看,可以直接进行的类型判断包括:
Array数组、Boolean布尔值、Buffer缓冲区、Null空、Undefined未定义、NullOrUndefined空或未定义、Number数字、String字符串、Symbol符号、RegExp正则、Object对象、Date日期、Error错误、Function方法、Primitive基础类型等等。
虽然用的比较少,但笔者看到,这些方法似乎比JS的typeof或者instanceof好像要细致和优雅,但它们并不是标准的JS方法,这样就在前端受到了一些限制,有机会希望可以尝试使用。
在更新版本的nodejs中,这一系列的类型判断函数已经被标记为"过期"。推荐的处理方式是使用util.types类的对应的判断方法,或者类型本身的判断方式,如Array.isArray,Buffer.isBuffer等等。除了上述的类型判断之外,它还提供了更细粒度的类型检查和判断,我们在其类定义里面也可以看到。读者有兴趣可以研读相关的技术文档。
Debug 调试
util.debuglog方法,用于创建一个调试方法,它可以基于环境变量的设置,条件化的输出调试内容到stderr接口。下面的示例代码方便我们进行理解:
js
// NODE_DEBUG = foo
const util = require('node:util');
const debuglog = util.debuglog('foo');
debuglog('hello from foo [%d]', 123);
// 输出
FOO 3245: hello from foo [123]
这段代码说明:
- 可以用debuglog方法,来创建一个日志方法
- 参数是一个环境变量值
- 当NODE_DEBUG环境变量设置为该值是,debuglog方法就可以执行了
- 如果没有这个环境变量,则定义的debuglog不会实际执行,也不会输出内容
- 如果有输出信息,则会包括环境变量和当前进程编号
还有一个util.debug方法,其实就是debuglog方法的别名。使用util.debug,开发者可以有条件的控制调试信息的输出。
Format 格式化
这个方法类似于C语言中的printf函数。它的输入包括一个模板字符串和一系列数值,输出是格式化后的字符串。format是同步方法,通常用作调试信息的显示和输出。
下面是其用法的简单的参考代码:
js
util.format('%s:%s', 'foo');
// Returns: 'foo:%s'
util.format('%s:%s', 'foo', 'bar', 'baz');
// Returns: 'foo:bar baz'
这里我们可以看到一些要点:
- format方法的第一个参数,通常是带有类型变量占位符的字符串
- format后续参数,将会按照类型定义,替换格式字符串中的占位符
format格式模板可选项目包括:
- %s 字符串
- %d 数字
- %i 整数
- %f 浮点数
- %j JSON
- %o 对象
- %c CSS
- %o 对象
- %% 百分号转义
util还有一个相关的formatWithOptions方法,可以通过提供更多的选项,来丰富模板字符串的操作。
Inspect 检查器
我们在开发和调试的过程中,经常遇到需要打印对象内容的场景。默认的console.log方法可能不能够满足我们的需求,因为它可能会将内部的对象打印称为"[object]"这种方式,或者,它会忽略输出一些内容,这显然不是我们需要的。
这时,我们可以将console.log和util.inspect方法结合起来使用,它可以用于输出完整的JS对象的内容。下面是一个简单的示例:
js
const { inspect } = require('node:util');
const obj = {};
obj.a = [obj];
obj.b = {};
obj.b.inner = obj.b;
obj.b.obj = obj;
console.log(inspect(obj));
// <ref *1> {
// a: [ [Circular *1] ],
// b: <ref *2> { inner: [Circular *2], obj: [Circular *1] }
// }
这只是一个非常简单的示例和场景。实际上util的inspect方法功能非常强大,有很多额外的选项,比如可以自定义输出(自定义inspect方法),配置输出内容的格式包括颜色,选择是否显示非可枚举对象,显示深度,控制显示内容的大型长度,排序等等,有兴趣的读者可以自行查阅相关技术文档,获取更详细的信息。
MIMEType
在当前的版本中,这是一个实验性的特性。MIMEType就是对mime类型的支持。下面的例子让我们能够很容易的理解这一点:
js
const { MIMEType } = require('node:util');
const myMIME = new MIMEType('text/javascript');
console.log(myMIME.type);
// Prints: text
myMIME.type = 'application';
console.log(myMIME.type);
// Prints: application
console.log(String(myMIME));
// Prints: application/javascript
有趣的是,不知道为什么nodejs社区会将这个功能设计在util模块中,笔者个人觉得,将mimetype设计到http模块中,或者作为一个独立的模块,可能更会更合适一点吧。
parseArgs/parseEnv 解析参数和环境变量
parseArgs可以用于解析process.argv中的内容,可以通过选项参数来控制内容和转换的规则,最后输出一个参数对象。如下面的示例:
js
const { parseArgs } = require('node:util');
const args = ['-f', '--bar', 'b'];
const options = {
foo: {
type: 'boolean',
short: 'f',
},
bar: {
type: 'string',
},
};
const {
values,
positionals,
} = parseArgs({ args, options });
console.log(values, positionals);
// Prints: [Object: null prototype] { foo: true, bar: 'b' } []
和parseArg的诉求和原理类似,parseEnv可以将.env文件中的内容,转换为一个对象。
Promisify Promise化
此方法可以将一个异步回调函数,转换称为一个Promise对象,然后使用Promise的方法来执行。比如下面这个简单的示例:
js
const util = require('node:util');
const fs = require('node:fs');
const stat = util.promisify(fs.stat);
stat('.').then((stats) => {
// Do something with `stats`
}).catch((error) => {
// Handle the error.
});
// async/await 调用方式
async function callStat() {
const stats = await stat('.');
console.log(`This directory is owned by ${stats.uid}`);
}
callStat();
直接使用util.promisify,来对某个异步回调函数进行promise化,其实有一个条件,就是它的callback方法的参数,必须是(error,data)这种形式的,如果不是这种类型,可能需要使用util.promisify.custom来完全自定义方法的promise化,这种情况称为Custom Promisified Functions,自定义Promise函数。
Encoder/Decoder 文本编解码器
这个没有什么特别的,应该就是WHATWH Encoding标准的 text Encoder/Decoder API的实现。和全局变量TexeEncoder/Decoder好像没什么差异。
Log 日志
可以向标准stdout输出带有时间戳的调试信息。
js
> util.log('Timestamped message.');
18 Apr 11:32:29 - Timestamped message.
deprecate 弃用
我们在开发工作中,经常会遇到某个功能版本被标记成为"弃用"的情况。这是一种在软件开发行业在系统演进过程中,尽量保证这个过程平稳过渡的管理和控制机制。它的基础原理是,通过一种一致的约定和规范,可以选择将一个功能、API或者函数标记成为"弃用",这时并不意味着它会被立刻删除或者停用,但它会在被使用时开发者和用户都会收到一些提示信息;这样在一个过渡周期当中,开发者就可以优先选择使用新的特性或者替代技术方案;最终经过一段时间的发展,当源头开发者看到老版本的使用降低到某个程度之后,就可以真正考虑将老特性从系统中移除了。
关于弃用机制,开发者应该理解以下要点:
- 弃用(deprecated)不等于立即删除
不用过于惊慌,它依然可以在当前甚至相当长的一段时间内可用,但应当在新系统或者更新时考虑改进和替代的技术方案。
- 向后兼容性
弃用通常是为了保持向后兼容性,给开发人员一个过渡期去适应变更,并修改代码来适应这个弃用,这样可以防止意外破坏现有系统。
- 删除计划和版本
弃用是对未来移除该功能特性的一个预告和警告。它向开发人员发出信号,某些遗留功能在未来版本中计划被删除。一个良好的弃用方案还可能包括弃用的版本规划,即表明在那些版本可能实施真正的移除操作。
- 替代方案
作为原始代码的开发者,如果考虑将特性标记成为"弃用"时,应当提供相关的升级或替代方案。
- 编译器/IDE警告
作为一致的约定,大多数编译器和IDE会显示警告信息提醒开发者,正在使用的代码元素已被标记为deprecated,需要注意更新。此外一些弃用信息也会出现在程序启动、调试或者运行过程中,对开发者和用户发出提示。
nodejs的util模块,就提供了弃用信息注入的方法。我们可以通过示例代码来简单了解一下:
js
const util = require('node:util');
const fn1 = util.deprecate(someFunction, someMessage, 'DEP0001');
const fn2 = util.deprecate(someOtherFunction, someOtherMessage, 'DEP0001');
fn1(); // Emits a deprecation warning with code DEP0001
fn2(); // Does not emit a deprecation warning because it has the same code
这些弃用信息在开发的过程中,可能确实对于开发者是有用的,但在生产环境中,可能会带来一些困扰。这时可以通过一些启动选择,可选不显示这些信息。如 --no-deprecation 或者 --no-warnings等。
其他杂项
util的其他杂项包括:
- getSystemErrorName(err) 获取错误的系统名称
- getSystemErrorMap() 获取相关所有系统错误列表
- callBackify(original) 将一个同步调用方法,转换称为异步回调的模式
- styleText() 将文本风格化
- stripVTControlCharacters(str) 剥离逃逸码
- transferableAbortController/transferableAbortSignal 撤销控制相关