一、using 声明简介
using 是 ECMAScript 2023(ES14) 引入的一项新语法,用于自动管理资源的生命周期 。
它的主要目标是简化"资源使用完后自动释放"的场景,例如文件句柄、数据库连接、锁等。 相关提案可见:github.com/tc39/propos...
📜 语法结构:
csharp
using 变量名 = 表达式;
await using 变量名 = 异步表达式;
using 声明的变量必须是一个实现了 Symbol.dispose 或 Symbol.asyncDispose 方法的对象。
当作用域退出(无论是正常返回还是异常)时,JS 引擎会自动调用该方法释放资源。
二、基本使用示例
同步版本
javascript
class File {
constructor(name) {
this.name = name;
console.log(`打开文件: ${name}`);
}
[Symbol.dispose]() {
console.log(`关闭文件: ${this.name}`);
}
}
function main() {
using f = new File("data.txt");
console.log("读取文件中...");
}
main();
// 输出:
// 打开文件: data.txt
// 读取文件中...
// 关闭文件: data.txt
⚡ 当
main()执行完毕时,f离开作用域,系统会自动调用f[Symbol.dispose]()。
异步版本
javascript
class AsyncResource {
async [Symbol.asyncDispose]() {
console.log("异步释放资源...");
await new Promise(res => setTimeout(res, 1000));
console.log("资源已释放");
}
}
async function run() {
await using r = new AsyncResource();
console.log("使用异步资源中...");
}
run();
三、内部机制简析
-
using是 词法作用域绑定 (和let/const类似)。 -
离开作用域时:
- 如果是同步资源 → 调用
[Symbol.dispose]() - 如果是异步资源 → 调用
[Symbol.asyncDispose]()
- 如果是同步资源 → 调用
-
它可以和
try、catch、finally一起安全使用。
四、使用场景
| 场景 | 传统做法 | 新写法(using) |
|---|---|---|
| 文件操作 | try/finally 手动关闭 | 自动调用 dispose |
| 数据库连接 | 手动断开 | 自动释放连接 |
| 临时锁 | try/finally 释放锁 | 离开作用域自动释放 |
五、注意事项
using只能在模块或函数作用域中使用,不能在全局作用域直接声明。- 不能与
var共用。 - 不会影响 GC(垃圾回收),它只是提供结构化释放资源的机制。
六、实践demo------简化revokeObjectURL
在图片回显/文件下载等场景中,开发很容易忘记revokeObjectURL,我们可以利用using特性进行封装,消除revokeObjectURL的心智负担。
就比如上传图片后回显的操作:
js
console.log('🚀 using demo 启动...');
function objectURLResource(blob) {
const url = URL.createObjectURL(blob);
console.log('🆕 创建 URL:', url);
return {
url,
[Symbol.dispose]() {
console.log("释放资源...");
URL.revokeObjectURL(url);
console.log('🧹 自动释放 URL:', url);
},
// [Symbol.dispose]() {
// console.log("释放资源...");
// new Promise(resolve => setTimeout(resolve, 1000)).then(()=>{
// URL.revokeObjectURL(url);
// console.log('🧹 自动释放 URL:', url);
// })
// },
async [Symbol.asyncDispose]() {
console.log("异步释放资源...");
await new Promise(resolve => setTimeout(resolve, 3000)); // 模拟异步操作
URL.revokeObjectURL(url);
console.log('🧹 自动释放 URL:', url);
},
};
}
async function showImage(blob) {
// await using resource = objectURLResource(blob);
using resource = objectURLResource(blob);
const img = document.createElement('img');
img.src = resource.url;
img.alt = 'demo';
const { resolve, promise } = Promise.withResolvers()
img.onload = () => {
resolve()
}
document.body.appendChild(img);
await promise;
console.log('✅ 演示完毕,URL 将被自动 revoke');
}
document.querySelector('#file').addEventListener('change',async (e) => {
const f = e.target.files[0]
await showImage(f);
console.log('do others... ')
})

revokeObjectURL的注意事项
当img加载后,执行revokeObjectURL并不会影响内容显示,但如果img未加载完就执行了revokeObjectURL,则无法显示图片。因此以上demo中增加了await promise的处理,确保图片被显示出来。 
那么可能会有人问,此处能否用Symbol.asyncDispose来解决该问题?答案是不能。但这是一个很好的问题,触及到了 Symbol.asyncDispose与Symbol.dispose的根本区别.
七、Symbol.asyncDispose与Symbol.dispose的区别
表面上看,以上demo中如果写成await using resource = objectURLResource(blob);,释放资源用的是Symbol.asyncDispose,如果不加await,则调用Symbol.dispose。
异步释放: 
那么这两个api的使用场景分别是啥呢?我们不妨在同步释放中写下如下代码:
javascript
[Symbol.dispose]() {
console.log("释放资源...");
new Promise(resolve => setTimeout(resolve, 1000)).then(()=>{
URL.revokeObjectURL(url);
console.log('🧹 自动释放 URL:', url);
})
},
可以看到,do others...在释放完成前就执行了。
因此可以理解:Symbol.asyncDispose是为了让后续的代码等待异步释放完成后再执行,因为有些释放场景,可能需要进行io或其他异步校验,而Symbol.dispose释放过程是同步的,后续代码执行时可以认为资源已经被释放了。如果后续代码执行时并不关心该资源是否已经释放了,那使用Symbol.dispose即可。