likes
comments
collection
share

和Node.js的Buffer说拜拜

作者站长头像
站长
· 阅读数 9

声明:(1)此文为译文,原文见 Goodbye, Node.js Buffer (2)翻译不准处的后面加上了英文原文词汇或进行了不影响理解的删减(3)翻译不对的地方请不吝赐教,留言更正(4)为了使文章更具有可读性,译者适当地增加了标题和划分了段落。

Buffer存在的缺欠

Buffer类型从一开始就是Node.js中处理二进制数据的基石。然而,现在我们有了 Uint8Array,这是一个原生的 JavaScript 类型并且可以跨平台工作。而 Buffer 是 Uint8Array 的一个实例,它引入了许多在其他 JavaScript 环境中不可用的方法。因此,那些使用了特定于Buffer的方法(Buffer-specific methods)的相关代码需要进行填充(polyfilling),这阻碍了许多有价值的包和浏览器兼容。

Buffer 还带了一些额外的问题。例如:

第一, Buffer的slice()方法,创建了一个链接到原始缓冲区的可变片段(segment ),会导致一些不可预测的行为,然而 Uint8Array的slice()方法则创建了一个不可变的副本。问题不在于 Buffer的slice()方法的行为,而在于Buffer是Uint8Array的子类,它完全改变了继承自Uint8Array的方法的行为。可以使用Unit8Array的subarray()方法或者Buffer的subarray()方法代替Buffer的slice()方法。

第二,Buffer通过全局变量暴露隐私信息,这是一个潜在的安全风险。

我们是时候和Buffer说拜拜了!

我的升级计划

我打算将我所有的包从使用 Buffer 改为Uint8Array。如果您是 JavaScript 包的维护者,我鼓励您也这样做。Buffer可能永远不会被删除,甚至可能永远不会被弃用,但至少社区可以慢慢远离它,我希望 Node.js 团队至少开始不鼓励使用 Buffer。

如何进行升级

首先,熟悉 Uint8Array 和 Buffer 之间微妙的不兼容性。我已经开发了 uint8array-extras 包来使转换更加容易。

如果你的代码使用了 Buffer 并且没有使用任何特定于 Buffer 的方法( Buffer-specific methods),那么只需将文档和数据类型更新为 Uint8Array 即可。

将输入类型从 Buffer 更改为 Uint8Array 是非破坏性更改,因为 Buffer 是 Uint8Array 的实例。

将返回类型从 Buffer 更改为 Uint8Array 是一个重大更改,因为返回值的消费者可能使用特定于Buffer的方法(Buffer-specific methods)。

如果你确实需要将 Uint8Array 转换为 Buffer,可以使用 Buffer.from (uint8Array)(拷贝数据)或者 Buffer.from(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength)(不拷贝数据)。然而还有更好的方法吗?

一些主要的修改步骤是:

  • 删除所有的import {Buffer} from 'node:buffer' 导入语句
  • 删除所有的Buffer全局变量
  • 停止使用特定于Buffer的方法
  • 替换 Buffer的读/写方法,例如用DataView替换Buffer的readInt32BE()方法

解答一些问题

1.Buffer 为什么一开始会存在?

在 Uint8Array 存在之前很久就已经创建了 Buffer。

2.如何使用 Uint8Array 进行与 Base64之间的转换?

你现在可以使用我的 uint8array-extras 包,它很可能最终会在 原生JavaScript 中得到支持。

3.如何处理返回Buffer的 Node.js API,比如 fs 的方法?

因为 Buffer 是 Uint8Array 的一个子类,所以您可以将它看作 Uint8Array。只要确保不使用. slice ()(它在行为上有所不同)或任何特定于 Buffer 的方法( Buffer-specific methods)。

例子

Javascript代码

+ import {stringToBase64} from 'uint8array-extras';

- Buffer.from(string).toString('base64');
+ stringToBase64(string);
+ import {uint8ArrayToHex} from 'uint8array-extras';

- buffer.toString('hex');
+ uint8ArrayToHex(uint8Array);
const bytes = getBytes();

+ const view = new DataView(bytes.buffer);

- const value = bytes.readInt32BE(1);
+ const value = view.getInt32(1);
import crypto from 'node:crypto';
- import {Buffer} from 'node:buffer';
+ import {isUint8Array} from 'uint8array-extras';

 export default function hash(data) {
	- if (!(typeof data === 'string' || Buffer.isBuffer(data))) {
	+ if (!(typeof data === 'string' || isUint8Array(data))) {
 		throw new TypeError('Incorrect type.');
 	}

 	return crypto.createHash('md5').update(data).digest('hex');
 }

大多数 Node.js API 也接受 Uint8Array,因此不需要额外的工作。

TypeScript代码

- import {Buffer} from 'node:buffer';

- export function getSize(input: string | Buffer): number { … }
+ export function getSize(input: string | Uint8Array): number { … }

强制使用Unit8Array

我建议使用 lint工具(linting)强制使用 Uint8Array 而不是 Buffer,将如下内容增加到你的ESLint配置当中:

{
	'no-restricted-globals': [
		'error',
		{
			name: 'Buffer',
			message: 'Use Uint8Array instead.'
		}
	],
	'no-restricted-imports': [
		'error',
		{
			name: 'buffer',
			message: 'Use Uint8Array instead.'
		},
		{
			name: 'node:buffer',
			message: 'Use Uint8Array instead.'
		}
	]
}

如果你使用的是TypeScript则使用如下内容:

{
	'@typescript-eslint/ban-types': [
		'error',
		{
			types: {
				Buffer: {
					message: 'Use Uint8Array instead.',
					suggest: [
						'Uint8Array'
					]
				}
			}
		}
	]
}