JavaScript 的未来发展
Deno 是一家 JavaScript 公司,我们相信 JavaScript 应该简单、强大且有趣。Deno 致力于通过原生 TypeScript 支持,并借助Web 标准 API 弥合服务端和浏览器端 JavaScript 之间的差距,从而实现 JavaScript 及其工具的现代化。因此,我们非常致力于推动 JavaScript 生态系统的发展,并参与 TC39 等标准委员会,因为我们希望 JavaScript 对所有人来说都更好、更高效。
TC39 第 108 次会议最近推进了 9 项提案,涵盖 4 个阶段,这些阶段代表从粗略想法(第 0 阶段)到完全标准化功能(第 4 阶段)的进程。
以下是每项提案的简要摘要,以及它们对 JavaScript 未来可能意味着什么。
推进至第 4 阶段
using
)
显式资源管理(新的 using
声明(及其异步变体 await using
)为资源添加了确定性清理功能,灵感来源于 C# 和 Python 等语言。对象可以定义 [Symbol.dispose]()
(或 [Symbol.asyncDispose]()
),该方法会在 using
块结束时自动调用。例如:
class FileHandle {
constructor(name) {
this.name = name; /* open file... */
}
[Symbol.dispose]() {
console.log(`${this.name} closed`); /* close file */
}
}
function readFile() {
{
using file = new FileHandle("data.txt");
// read from file...
}
// file.[Symbol.dispose]() was called here automatically
}
readFile(); // logs "data.txt closed"
这确保了即使块内发生异常,也能进行清理(例如关闭文件或流),使资源管理更简单、更安全。
此功能已在 Chrome 134、Firefox 134 和 Deno v2.3 中受支持。
在 Deno 中,您已经可以使用 using
关键字来管理文件句柄(Deno.File
)、网络套接字(Deno.Conn
)等资源。例如,在这里我们在对 HTTP 服务器发出请求后自动停止它:
using server = Deno.serve({ port: 8000 }, () => {
return new Response("Hello, world!");
});
const response = await fetch("http://localhost:8000");
console.log(await response.text()); // "Hello, world!"
// The server is automatically closed here because of the `using` keyword
Deno 团队也有兴趣使用 using
关键字来简化异步上下文传播。事实上,Deno 对 异步上下文传播(第 2 阶段)的支持已经使我们能够做到诸如 console.log
的自动检测以包含 HTTP 信息。然而,目前每次您想要创建一个新的 span 来跟踪某些工作时,都需要创建一个新函数并运行它。这很麻烦:
async function doWork() {
const parent = tracer.startSpan("doWork");
return parent.run(async () => {
console.log("doing some work...");
return true;
});
}
Deno 团队正在提议可处置的 AsyncContext.Variable
,这将允许您使用 using
关键字来简化此代码:
async function doWork() {
using parent = tracer.startActiveSpan("doWork");
console.log("doing some work...");
return true;
}
看到了吗?样板代码少了很多!
Array.fromAsync
Array.fromAsync
类似于 Array.from
,但它适用于异步可迭代对象,返回一个解析为结果数组的 Promise。它也支持映射函数和 thisArg
,就像 Array.from
一样。
例如,给定一个值的异步生成器,您可以这样写:
async function* generate() {
yield await Promise.resolve(1);
yield await Promise.resolve(2);
}
const nums = await Array.fromAsync(generate()); // [1, 2]
这里 Array.fromAsync(generate())
返回一个 Promise,一旦所有生成的值都准备就绪,它就会解析为 [1, 2]
。这使得常见的异步集合模式变得更简单、更易读。
Array.fromAsync
在所有浏览器以及 Deno v1.38 和 Node v22 中都可用。
Error.isError
Error.isError(value)
是一个新的内置函数,用于可靠地检测错误对象。如果 value
是任何类型的 Error(包括跨领域或子类化的错误),它返回 true
,否则返回 false
。例如:
Error.isError(new TypeError("oops")); // true
Error.isError({ name: "TypeError", message: "oops" }); // false
虽然这几乎不需要,但如果没有此功能,编写某些代码(例如一些 polyfill)可能会很困难。
Error.isError
在所有浏览器中都受支持,以及在 Deno v2.2 中也受支持。
第 3 阶段
ArrayBuffer
不可变 不可变 ArrayBuffer
现已进入第 3 阶段,引入了 transferToImmutable()
和 sliceToImmutable()
方法。在一个缓冲区上调用 transferToImmutable()
会将其数据移动到一个新的、不可更改的缓冲区中,并分离原始缓冲区。例如:
let buf = new ArrayBuffer(100);
let imm = buf.transferToImmutable();
// buf is now detached (byteLength 0), and imm is a new immutable ArrayBuffer of length 100
console.log(buf.byteLength, imm.byteLength); // 0, 100
// attempting to modify imm will throw a TypeError
imm[0] = 1; // TypeError: Cannot modify an immutable ArrayBuffer
类似地,sliceToImmutable(start, end)
创建一个子范围的不可变副本。不可变缓冲区*不能被分离或修改*,这使得共享二进制数据(例如跨线程或 Worker)更安全、更高效。
我们计划在 Deno 中利用这一点来优化各种接受字节数组作为输入的 API,例如 new Response()
或 Deno.writeFile()
。这将使我们能够避免不必要的复制,并提高处理二进制数据时的性能。
第 2 阶段
Random.Seeded
当前的伪随机数生成器方法(例如 Math.random()
)是自动播种的,这使得它在不同运行或领域之间不可重现。但是,有时您会需要一组可重现的随机值。
此提案提出了一种新的 SeededPRNG
类,通过允许您设置种子来提供可重现的随机性。您可以创建一个 Random.Seeded(seedValue)
对象,并使用其 .random()
方法代替 Math.random()
。例如:
const prng = new Random.Seeded(42);
for (let i = 0; i < 3; i++) {
console.log(prng.random());
// prints the same sequence on every run given seed 42
}
这在游戏或模拟中很有用,因为可重复性很重要。您还可以从现有 PRNG
派生新种子或克隆状态,以应对复杂场景。
Number.prototype.clamp
Number.prototype.clamp(min, max)
函数(最初是 Math.clamp
)返回一个介于 min
和 max
之间的数字。这对于将值保持在一定范围内非常方便。例如:
(5).clamp(0, 10); // 5
(-5).clamp(0, 10); // 0 (floored at 0)
(15).clamp(0, 10); // 10 (capped at 10)
如果 min > max
,它会抛出 RangeError
。这避免了像 Math.min(Math.max(x, min), max)
这样冗长的模式,并明确了意图。
第 1 阶段
保留尾随零
Intl.NumberFormat
中的新格式选项将允许保留或删除格式化数字中的尾随零,这对于表示人类可读的十进制值(例如货币)很有用。
trailingZeroDisplay: "auto"
设置(默认)根据指定的精度保留零;"stripIfInteger"
在数字为整数时删除它们。例如:
// Keep two decimal places (auto-preserve zeros):
new Intl.NumberFormat("en", {
minimumFractionDigits: 2,
trailingZeroDisplay: "auto",
})
.format(1.5); // "1.50"
// Strip zeros if unnecessary:
new Intl.NumberFormat("en", {
minimumFractionDigits: 0,
trailingZeroDisplay: "stripIfInteger",
})
.format(2); // "2" (not "2.0")
这使开发人员能够对数字格式(例如货币或定点输出)进行更精细的控制,而无需临时的字符串操作。
比较
比较提案旨在标准化 JavaScript 如何生成人类可读的值表示——类似于 Node.js 中的 util.inspect
或测试运行器打印 diff 的方式。
目标是为测试框架和控制台工具提供一种一致的方式来生成差异——特别是在运行时和领域之间。
随机函数
此提案引入了一个新的 Random
命名空间,其中包含便利方法,以避免随机性中常见的陷阱。Random
命名空间中的方法不仅可以生成随机数值,还可以接受和返回集合。
以下是一些示例:
// Random integer between -5 and 5
Random.int(-5, 5); // -1
// Random Number between 0 and 10
Random.number(0, 10); // 8
// Random number between 0, 5 with steps of 0.1
Random.number(0, 5, 0.1); // 1.1
// Randomly select n items from an array.
const name = Random.take(["Alice", "Bob", "Carol"], 2); // ['Alice', 'Bob']
// With replacement.
Random.take(["Alice", "Bob", "Carol"], 2, { replace: true }); // ['Alice', 'Alice']
// With weights.
Random.take(["Alice", "Bob", "Carol"], 2, { weights: [1, 1, 5] }); // ['Alice', 'Bob']
// Select one random item.
Random.sample(["Alice", "Bob", "Carol"]); // 'Bob'
// Mutate and return a "shuffled" Array in-place.
Random.shuffle([1, 2, 3, 4]); // [4,2,1,3]
// Return a new shuffled array.
const shuffled = Random.toShuffled([1, 2, 3, 4]);
目标是使与随机性相关的代码更安全、更简洁,减少差一错误并提高一致性。
下一步
TC39 将继续发展和改进 JavaScript,以满足现代开发人员的需求。Deno 致力于 Web 标准,并积极参与这些讨论,以使用 JavaScript,这直接简化了您在 Deno 中编写 JavaScript 的方式(请参阅 异步上下文传播 和我们内置的 OpenTelemetry API)。下一次 TC39 会议将进一步讨论这些提案,计划于 9 月下旬举行。
🚨️ Deno Deploy 已进行重大更新! 🚨️
以及更多功能!