Bun

S3 对象存储

生产服务器通常将文件读取、上传和写入到 S3 兼容的对象存储服务,而不是本地文件系统。从历史上看,这意味着你在开发中使用的本地文件系统 API 无法在生产中使用。当你使用 Bun 时,情况有所不同。

Bun 的 S3 API 速度快

Bun's S3 API is fast
左:Bun v1.1.44。右:Node.js v23.6.0

Bun 提供了快速的本地绑定,用于与 S3 兼容的对象存储服务进行交互。Bun 的 S3 API 设计简单,感觉类似于 fetch 的 `Response` 和 `Blob` API(类似于 Bun 的本地文件系统 API)。

import { s3, write, S3Client } from "bun";

// Bun.s3 reads environment variables for credentials
// file() returns a lazy reference to a file on S3
const metadata = s3.file("123.json");

// Download from S3 as JSON
const data = await metadata.json();

// Upload to S3
await write(metadata, JSON.stringify({ name: "John", age: 30 }));

// Presign a URL (synchronous - no network request needed)
const url = metadata.presign({
  acl: "public-read",
  expiresIn: 60 * 60 * 24, // 1 day
});

// Delete the file
await metadata.delete();

S3 是事实上的互联网文件系统标准。Bun 的 S3 API 适用于 S3 兼容的存储服务,例如

  • AWS S3
  • Cloudflare R2
  • DigitalOcean Spaces
  • MinIO
  • Backblaze B2
  • ...以及任何其他 S3 兼容的存储服务

基本用法

与 Bun 的 S3 API 交互有几种方法。

Bun.S3Client & Bun.s3

Bun.s3 等同于 new Bun.S3Client(),它依赖于环境变量来获取凭据。

要显式设置凭据,请将它们传递给 Bun.S3Client 构造函数。

import { S3Client } from "bun";

const client = new S3Client({
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // sessionToken: "..."
  // acl: "public-read",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
  // endpoint: "https://<region>.digitaloceanspaces.com", // DigitalOcean Spaces
  // endpoint: "https://:9000", // MinIO
});

// Bun.s3 is a global singleton that is equivalent to `new Bun.S3Client()`

处理 S3 文件

S3Client 中的 file 方法返回 S3 上文件的惰性引用

// A lazy reference to a file on S3
const s3file: S3File = client.file("123.json");

Bun.file(path) 类似,S3Clientfile 方法是同步的。它在调用依赖于网络请求的方法之前不会进行任何网络请求。

从 S3 读取文件

如果你使用过 fetch API,你就会熟悉 ResponseBlob API。S3File 继承自 Blob。适用于 Blob 的方法也适用于 S3File

// Read an S3File as text
const text = await s3file.text();

// Read an S3File as JSON
const json = await s3file.json();

// Read an S3File as an ArrayBuffer
const buffer = await s3file.arrayBuffer();

// Get only the first 1024 bytes
const partial = await s3file.slice(0, 1024).text();

// Stream the file
const stream = s3file.stream();
for await (const chunk of stream) {
  console.log(chunk);
}

内存优化

text()json()bytes()arrayBuffer() 这样的方法,在可能的情况下,会避免在内存中复制字符串或字节。

如果文本是 ASCII 编码,Bun 会直接将字符串传输到 JavaScriptCore(引擎),无需转码,也无需在内存中复制字符串。当你使用 .bytes().arrayBuffer() 时,它也会避免在内存中复制字节。

这些辅助方法不仅简化了 API,还提高了其速度。

向 S3 写入和上传文件

写入 S3 同样简单。

// Write a string (replacing the file)
await s3file.write("Hello World!");

// Write a Buffer (replacing the file)
await s3file.write(Buffer.from("Hello World!"));

// Write a Response (replacing the file)
await s3file.write(new Response("Hello World!"));

// Write with content type
await s3file.write(JSON.stringify({ name: "John", age: 30 }), {
  type: "application/json",
});

// Write using a writer (streaming)
const writer = s3file.writer({ type: "application/json" });
writer.write("Hello");
writer.write(" World!");
await writer.end();

// Write using Bun.write
await Bun.write(s3file, "Hello World!");

处理大型文件(流)

Bun 会自动处理大型文件的分段上传,并提供流式传输功能。适用于本地文件的 API 也适用于 S3 文件。

// Write a large file
const bigFile = Buffer.alloc(10 * 1024 * 1024); // 10MB
const writer = s3file.writer({
  // Automatically retry on network errors up to 3 times
  retry: 3,

  // Queue up to 10 requests at a time
  queueSize: 10,

  // Upload in 5 MB chunks
  partSize: 5 * 1024 * 1024,
});
for (let i = 0; i < 10; i++) {
  writer.write(bigFile);
  await writer.flush();
}
await writer.end();

预签名 URL

当你的生产服务需要让用户将文件上传到你的服务器时,通常让用户直接上传到 S3 比你的服务器充当中介更可靠。

为了方便这一点,你可以为 S3 文件预签名 URL。这会生成一个带有签名的 URL,允许用户安全地将该特定文件上传到 S3,而不会暴露你的凭据或授予他们对你的桶的不必要访问权限。

默认行为是生成一个 24 小时后过期的 GET URL。Bun 会尝试从文件扩展名推断内容类型。如果无法推断,它将默认为 application/octet-stream

import { s3 } from "bun";

// Generate a presigned URL that expires in 24 hours (default)
const download = s3.presign("my-file.txt"); // GET, text/plain, expires in 24 hours

const upload = s3.presign("my-file", {
  expiresIn: 3600, // 1 hour
  method: "PUT",
  type: "application/json", // No extension for inferring, so we can specify the content type to be JSON
});

// You can call .presign() if on a file reference, but avoid doing so
// unless you already have a reference (to avoid memory usage).
const myFile = s3.file("my-file.txt");
const presignedFile = myFile.presign({
  expiresIn: 3600, // 1 hour
});

设置 ACL

要在预签名 URL 上设置 ACL(访问控制列表),请传递 acl 选项

const url = s3file.presign({
  acl: "public-read",
  expiresIn: 3600,
});

您可以传递以下任何 ACL

ACL说明
"public-read"该对象可由公众读取。
"private"该对象仅可由存储桶所有者读取。
"public-read-write"该对象可由公众读取和写入。
"authenticated-read"该对象可由存储桶所有者和经过身份验证的用户读取。
"aws-exec-read"该对象可由发出请求的 AWS 账户读取。
"bucket-owner-read"该对象可由存储桶所有者读取。
"bucket-owner-full-control"该对象可由存储桶所有者读取和写入。
"log-delivery-write"该对象可由用于日志传输的 AWS 服务写入。

过期 URL

要设置预签名 URL 的过期时间,请传递 expiresIn 选项。

const url = s3file.presign({
  // Seconds
  expiresIn: 3600, // 1 hour

  // access control list
  acl: "public-read",

  // HTTP method
  method: "PUT",
});

method

要设置预签名 URL 的 HTTP 方法,请传递 method 选项。

const url = s3file.presign({
  method: "PUT",
  // method: "DELETE",
  // method: "GET",
  // method: "HEAD",
  // method: "POST",
  // method: "PUT",
});

new Response(S3File)

要快速将用户重定向到 S3 文件的预签名 URL,请将 S3File 实例作为主体传递给 Response 对象。

const response = new Response(s3file);
console.log(response);

这会自动将用户重定向到 S3 文件的预签名 URL,从而节省您下载文件到服务器并将其发送回用户所消耗的内存、时间和带宽。

Response (0 KB) {
  ok: false,
  url: "",
  status: 302,
  statusText: "",
  headers: Headers {
    "location": "https://<account-id>.r2.cloudflarestorage.com/...",
  },
  redirected: true,
  bodyUsed: false
}

S3 兼容服务的支持

Bun 的 S3 实现与任何 S3 兼容的存储服务均可配合使用。只需指定相应的端点即可

将 Bun 的 S3Client 与 AWS S3 结合使用

AWS S3 是默认设置。您还可以为 AWS S3 传递 region 选项而不是 endpoint 选项。

import { S3Client } from "bun";

// AWS S3
const s3 = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // region: "us-east-1",
});

将 Bun 的 S3Client 与 Google Cloud Storage 结合使用

要将 Bun 的 S3 客户端与 Google Cloud Storage 结合使用,请在 S3Client 构造函数中将 endpoint 设置为 "https://storage.googleapis.com"

import { S3Client } from "bun";

// Google Cloud Storage
const gcs = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",
  endpoint: "https://storage.googleapis.com",
});

将 Bun 的 S3Client 与 Cloudflare R2 结合使用

要将 Bun 的 S3 客户端与 Cloudflare R2 结合使用,请在 S3Client 构造函数中将 endpoint 设置为 R2 端点。R2 端点包含您的帐户 ID。

import { S3Client } from "bun";

// CloudFlare R2
const r2 = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",
  endpoint: "https://<account-id>.r2.cloudflarestorage.com",
});

将 Bun 的 S3Client 与 DigitalOcean Spaces 结合使用

要将 Bun 的 S3 客户端与 DigitalOcean Spaces 结合使用,请在 S3Client 构造函数中将 endpoint 设置为 DigitalOcean Spaces 端点。

import { S3Client } from "bun";

const spaces = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",
  // region: "nyc3",
  endpoint: "https://<region>.digitaloceanspaces.com",
});

将 Bun 的 S3Client 与 MinIO 结合使用

要将 Bun 的 S3 客户端与 MinIO 结合使用,请在 S3Client 构造函数中将 endpoint 设置为 MinIO 运行的 URL。

import { S3Client } from "bun";

const minio = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",

  // Make sure to use the correct endpoint URL
  // It might not be localhost in production!
  endpoint: "https://:9000",
});

将 Bun 的 S3Client 与 Supabase 结合使用

要将 Bun 的 S3 客户端与 Supabase 结合使用,请在 S3Client 构造函数中将 endpoint 设置为 Supabase 端点。Supabase 端点包含您的账户 ID 和 /storage/v1/s3 路径。请确保在 Supabase 控制面板的 https://supabase.com/dashboard/project/<account-id>/settings/storage 中启用“通过 S3 协议连接”,并设置同一部分中指示的区域。

import { S3Client } from "bun";

const supabase = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",
  region: "us-west-1",
  endpoint: "https://<account-id>.supabase.co/storage/v1/s3/storage",
});

将 Bun 的 S3Client 与 S3 虚拟主机式端点结合使用

使用 S3 虚拟主机式端点时,您需要将 virtualHostedStyle 选项设置为 true,并且如果没有提供端点,Bun 将使用区域和桶来推断 AWS S3 的端点;如果未提供区域,则将使用 us-east-1。如果您提供了端点,则无需提供桶名称。

import { S3Client } from "bun";

// AWS S3 endpoint inferred from region and bucket
const s3 = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",
  virtualHostedStyle: true,
  // endpoint: "https://my-bucket.s3.us-east-1.amazonaws.com",
  // region: "us-east-1",
});

// AWS S3
const s3WithEndpoint = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  endpoint: "https://<bucket-name>.s3.<region>.amazonaws.com",
  virtualHostedStyle: true,
});

// Cloudflare R2
const r2WithEndpoint = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  endpoint: "https://<bucket-name>.<account-id>.r2.cloudflarestorage.com",
  virtualHostedStyle: true,
});

凭据

凭据是使用 S3 最困难的部分之一,我们已尽力使其尽可能简单。默认情况下,Bun 从以下环境变量读取凭据。

选项名称环境变量
accessKeyIdS3_ACCESS_KEY_ID
secretAccessKeyS3_SECRET_ACCESS_KEY
regionS3_REGION
endpointS3_ENDPOINT
bucketS3_BUCKET
sessionTokenS3_SESSION_TOKEN

如果未设置 S3_* 环境变量,Bun 还会检查每个上述选项的 AWS_* 环境变量。

选项名称备用环境变量
accessKeyIdAWS_ACCESS_KEY_ID
secretAccessKeyAWS_SECRET_ACCESS_KEY
regionAWS_REGION
endpointAWS_ENDPOINT
bucketAWS_BUCKET
sessionTokenAWS_SESSION_TOKEN

这些环境变量在初始化时从 .env 文件或进程环境(不使用 process.env)中读取。

您传递给 `s3.file(credentials)`、`new Bun.S3Client(credentials)` 或任何接受凭据的方法的选项将覆盖这些默认值。因此,如果,例如,您对不同的存储桶使用相同的凭据,您可以在 `.env` 文件中设置一次凭据,然后将 `bucket: "my-bucket"` 传递给 `s3.file()` 函数,而无需再次指定所有凭据。

S3Client 对象

当您不使用环境变量或使用多个存储桶时,可以创建一个 S3Client 对象来显式设置凭据。

import { S3Client } from "bun";

const client = new S3Client({
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // sessionToken: "..."
  endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
  // endpoint: "https://:9000", // MinIO
});

// Write using a Response
await file.write(new Response("Hello World!"));

// Presign a URL
const url = file.presign({
  expiresIn: 60 * 60 * 24, // 1 day
  acl: "public-read",
});

// Delete the file
await file.delete();

S3Client.prototype.write

要上传或写入文件到 S3,请在 S3Client 实例上调用 write

const client = new Bun.S3Client({
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  endpoint: "https://s3.us-east-1.amazonaws.com",
  bucket: "my-bucket",
});
await client.write("my-file.txt", "Hello World!");
await client.write("my-file.txt", new Response("Hello World!"));

// equivalent to
// await client.file("my-file.txt").write("Hello World!");

S3Client.prototype.delete

要从 S3 删除文件,请在 S3Client 实例上调用 delete

const client = new Bun.S3Client({
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
});

await client.delete("my-file.txt");
// equivalent to
// await client.file("my-file.txt").delete();

S3Client.prototype.exists

要检查文件是否存在于 S3 中,请在 S3Client 实例上调用 exists

const client = new Bun.S3Client({
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
});

const exists = await client.exists("my-file.txt");
// equivalent to
// const exists = await client.file("my-file.txt").exists();

S3File

S3File 实例是通过调用 S3Client 实例方法或 s3.file() 函数创建的。与 Bun.file() 类似,S3File 实例是惰性的。它们不一定在创建时就指向存在的事物。这就是为什么所有不涉及网络请求的方法都是完全同步的。

interface S3File extends Blob {
  slice(start: number, end?: number): S3File;
  exists(): Promise<boolean>;
  unlink(): Promise<void>;
  presign(options: S3Options): string;
  text(): Promise<string>;
  json(): Promise<any>;
  bytes(): Promise<Uint8Array>;
  arrayBuffer(): Promise<ArrayBuffer>;
  stream(options: S3Options): ReadableStream;
  write(
    data:
      | string
      | Uint8Array
      | ArrayBuffer
      | Blob
      | ReadableStream
      | Response
      | Request,
    options?: BlobPropertyBag,
  ): Promise<number>;

  exists(options?: S3Options): Promise<boolean>;
  unlink(options?: S3Options): Promise<void>;
  delete(options?: S3Options): Promise<void>;
  presign(options?: S3Options): string;

  stat(options?: S3Options): Promise<S3Stat>;
  /**
   * Size is not synchronously available because it requires a network request.
   *
   * @deprecated Use `stat()` instead.
   */
  size: NaN;

  // ... more omitted for brevity
}

Bun.file() 类似,S3File 扩展了 Blob,因此所有适用于 Blob 的方法也适用于 S3File。用于从本地文件读取数据的 API 也可用于从 S3 读取数据。

方法输出
await s3File.text()string
await s3File.bytes()Uint8Array
await s3File.json()JSON
await s3File.stream()ReadableStream
await s3File.arrayBuffer()ArrayBuffer

这意味着将 S3File 实例与 fetch()Response 和其他接受 Blob 实例的 Web API 一起使用时,它们可以正常工作。

使用 slice 进行部分读取

要读取文件的部分范围,可以使用 slice 方法。

const partial = s3file.slice(0, 1024);

// Read the partial range as a Uint8Array
const bytes = await partial.bytes();

// Read the partial range as a string
const text = await partial.text();

在内部,这通过使用 HTTP `Range` 头来请求您想要的字节。此 `slice` 方法与 `Blob.prototype.slice` 相同。

从 S3 删除文件

要从 S3 删除文件,可以使用 delete 方法。

await s3file.delete();
// await s3File.unlink();

deleteunlink 相同。

错误代码

当 Bun 的 S3 API 抛出错误时,它将具有一个 code 属性,该属性与以下值之一匹配

  • ERR_S3_MISSING_CREDENTIALS
  • ERR_S3_INVALID_METHOD
  • ERR_S3_INVALID_PATH
  • ERR_S3_INVALID_ENDPOINT
  • ERR_S3_INVALID_SIGNATURE
  • ERR_S3_INVALID_SESSION_TOKEN

当 S3 对象存储服务返回错误(即不是 Bun)时,它将是 S3Error 实例(一个名称为 "S3Error"Error 实例)。

S3Client 静态方法

S3Client 类提供了几个用于与 S3 交互的静态方法。

S3Client.write(静态)

要将数据直接写入存储桶中的路径,可以使用 S3Client.write 静态方法。

import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};

// Write string
await S3Client.write("my-file.txt", "Hello World");

// Write JSON with type
await S3Client.write("data.json", JSON.stringify({ hello: "world" }), {
  ...credentials,
  type: "application/json",
});

// Write from fetch
const res = await fetch("https://example.com/data");
await S3Client.write("data.bin", res, credentials);

// Write with ACL
await S3Client.write("public.html", html, {
  ...credentials,
  acl: "public-read",
  type: "text/html",
});

这等同于调用 new S3Client(credentials).write("my-file.txt", "Hello World")

S3Client.presign(静态)

要为 S3 文件生成预签名 URL,可以使用 S3Client.presign 静态方法。

import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};

const url = S3Client.presign("my-file.txt", {
  ...credentials,
  expiresIn: 3600,
});

这等同于调用 new S3Client(credentials).presign("my-file.txt", { expiresIn: 3600 })

S3Client.list(静态)

要列出存储桶中的部分或全部(最多 1,000 个)对象,可以使用 S3Client.list 静态方法。

import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};

// List (up to) 1000 objects in the bucket
const allObjects = await S3Client.list(null, credentials);

// List (up to) 500 objects under `uploads/` prefix, with owner field for each object
const uploads = await S3Client.list({
  prefix: 'uploads/',
  maxKeys: 500,
  fetchOwner: true,
}, credentials);

// Check if more results are available
if (uploads.isTruncated) {
  // List next batch of objects under `uploads/` prefix
  const moreUploads = await S3Client.list({
    prefix: 'uploads/',
    maxKeys: 500,
    startAfter: uploads.contents!.at(-1).key
    fetchOwner: true,
  }, credentials);
}

这等同于调用 new S3Client(credentials).list()

S3Client.exists(静态)

要检查 S3 文件是否存在,可以使用 S3Client.exists 静态方法。

import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};

const exists = await S3Client.exists("my-file.txt", credentials);

同样的方法也适用于 S3File 实例。

import { s3 } from "bun";

const s3file = s3.file("my-file.txt", {
  ...credentials,
});
const exists = await s3file.exists();

S3Client.size(静态)

要在不下载 S3 文件的情况下快速检查其大小,可以使用 S3Client.size 静态方法。

import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};

const bytes = await S3Client.size("my-file.txt", credentials);

这等同于调用 new S3Client(credentials).size("my-file.txt")

S3Client.stat(静态)

要获取 S3 文件的大小、etag 和其他元数据,可以使用 S3Client.stat 静态方法。

import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};

const stat = await S3Client.stat("my-file.txt", credentials);
// {
//   etag: "\"7a30b741503c0b461cc14157e2df4ad8\"",
//   lastModified: 2025-01-07T00:19:10.000Z,
//   size: 1024,
//   type: "text/plain;charset=utf-8",
// }

S3Client.delete(静态)

要删除 S3 文件,可以使用 S3Client.delete 静态方法。

import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
};

await S3Client.delete("my-file.txt", credentials);
// equivalent to
// await new S3Client(credentials).delete("my-file.txt");

// S3Client.unlink is alias of S3Client.delete
await S3Client.unlink("my-file.txt", credentials);

s3:// 协议

为了更容易地将相同的代码用于本地文件和 S3 文件,fetchBun.file() 支持 s3:// 协议。

const response = await fetch("s3://my-bucket/my-file.txt");
const file = Bun.file("s3://my-bucket/my-file.txt");

您还可以将 s3 选项传递给 fetchBun.file 函数。

const response = await fetch("s3://my-bucket/my-file.txt", {
  s3: {
    accessKeyId: "your-access-key",
    secretAccessKey: "your-secret-key",
    endpoint: "https://s3.us-east-1.amazonaws.com",
  },
  headers: {
    "range": "bytes=0-1023",
  },
});

UTF-8、UTF-16 和 BOM(字节顺序标记)

ResponseBlob 类似,S3File 默认假定为 UTF-8 编码。

当在 S3File 上调用 text()json() 方法之一时

  • 当检测到 UTF-16 字节顺序标记 (BOM) 时,它将被视为 UTF-16。JavaScriptCore 本地支持 UTF-16,因此它会跳过 UTF-8 转码过程(并剥离 BOM)。这在大多数情况下是好的,但它确实意味着如果您的 UTF-16 字符串中存在无效的代理对字符,它们将被传递到 JavaScriptCore(与源代码相同)。
  • 当检测到 UTF-8 BOM 时,它会在字符串传递给 JavaScriptCore 之前被剥离,并且无效的 UTF-8 码点将被替换为 Unicode 替换字符(\uFFFD)。
  • 不支持 UTF-32。