跳到主要内容
Deno 2.4 发布,带来 deno bundle、字节/文本导入、OTel 稳定版等更多功能
了解更多

JavaScript 的未来发展

Deno 是一家 JavaScript 公司,我们相信 JavaScript 应该简单、强大且有趣。Deno 致力于通过原生 TypeScript 支持,并借助Web 标准 API 弥合服务端和浏览器端 JavaScript 之间的差距,从而实现 JavaScript 及其工具的现代化。因此,我们非常致力于推动 JavaScript 生态系统的发展,并参与 TC39 等标准委员会,因为我们希望 JavaScript 对所有人来说都更好、更高效。

TC39 第 108 次会议最近推进了 9 项提案,涵盖 4 个阶段,这些阶段代表从粗略想法(第 0 阶段)到完全标准化功能(第 4 阶段)的进程。

以下是每项提案的简要摘要,以及它们对 JavaScript 未来可能意味着什么。

第 4 阶段

第 3 阶段

第 2 阶段

第 1 阶段

推进至第 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 134Deno 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)返回一个介于 minmax 之间的数字。这对于将值保持在一定范围内非常方便。例如:

(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 已进行重大更新! 🚨️

以及更多功能!

立即获取早期访问权限。