为什么越来越多的前端开发者重新关注 WebAssembly
页面没有白屏,接口也正常返回 200,但 CPU 使用率却悄悄飙升,浏览器标签页的风扇呼呼转。有经验的前端同学这时候就知道,这个问题大概率不是“再加一层虚拟列表”能解决的了。
最近两年,WebAssembly 又被提了出来。不是因为它突然变成了万能解决方案,而是前端要处理的“脏活累活”确实越来越多了。以前我们在浏览器里做的事情相对简单:改改 DOM,调调接口,做一下表单验证,JavaScript 完全够用。现在不一样了,视频剪辑、音频转码、CAD 图纸预览、PDF 渲染、图像压缩、规则引擎、端上加密、本地 SQLite 查询……这些东西一旦真的跑在浏览器里,JS 就开始吃力。
不是说 JS 慢到没法用。实事求是地讲,现代 JS 引擎的性能已经很猛了。问题在于,有些事情它天生干着不顺手。
一个常见的前端“脏活”:上传前压缩图片
很多项目最开始都是这样处理图片压缩的:
async function compressImage(file, quality = 0.7) {
const img = await loadImage(URL.createObjectURL(file));
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
return new Promise(function (resolve) {
canvas.toBlob(function (blob) {
resolve(blob);
}, 'image/jpeg', quality);
});
}
function loadImage(src) {
return new Promise(function (resolve, reject) {
const img = new Image();
img.onload = function () {
resolve(img);
};
img.onerror = reject;
img.src = src;
});
}这段代码能不能用?能用。后端同事也会觉得“挺不错,省了不少带宽”。
但是,一旦文件体积变大、批量处理的图片数量变多、再加上用户的设备配置不高,这段代码就会让主线程明显卡顿。更麻烦的是,很多实际的图像处理需求远不止“压一下”这么简单。裁剪边缘、转换格式、调整颜色、甚至跑一套完整的编解码流程……这些场景下如果还是硬用 Canvas 去顶,就会越来越吃力。
WebAssembly 重新进入前端的视野,往往就是从这种“不对劲”开始的。
WebAssembly 真正打动前端的地方
它打动人的地方,不是“比 JS 更高级”,而是给了浏览器一种更适合跑计算密集型任务的执行方式。说得直白一点:有些活本来就不该让 UI 线程硬扛。
前端现在越来越像一个“端”,而不只是一个页面。看几个越来越常见的场景就明白了。
第一个场景,是把原来放在服务端的能力搬到浏览器。比如 Markdown 转 PDF、音视频预处理、本地全文检索、文档解析。不是大家突然喜欢折腾,而是很多情况下,本地处理反而更顺畅:少一次网络上传,少一轮等待,用户数据的隐私也更稳妥。在离线优先和边缘计算这些需求出现之后,前端就不再只是“调一下后端接口”的角色了。
第二个场景,是历史积累终于能派上用场。很多成熟的算法库本来就不是为 JS 生态写的,C、C++、Rust 那边有一批久经考验的老牌库,算法稳定,已知的坑也基本都踩完了。让前端团队纯手写一个同等水平的解析器、编解码器或者数据库内核,成本高得离谱,质量还未必稳得住。WebAssembly 的现实价值之一,就是把这些经过时间检验的东西搬进浏览器,而不是从零再搞一套。
一个实际的例子:在浏览器里跑 SQLite
前端本地跑 SQLite,这件事以前听起来像整活,现在越来越像正经方案。筛选、分页、复杂条件组合,如果数据量稍微大一点,纯 JS 在内存里操作大数组,看着就让人不放心。
async function queryUsers(db, keyword) {
const stmt = db.prepare(`
SELECT id, name, dept
FROM user_profile
WHERE name LIKE ? OR dept LIKE ?
ORDER BY updated_at DESC
LIMIT 20
`);
const rows = [];
stmt.bind(['%' + keyword + '%', '%' + keyword + '%']);
while (stmt.step()) {
rows.push(stmt.getAsObject());
}
stmt.free();
return rows;
}上面这些 JS 代码本身很普通,真正干活的是背后那个跑在 WebAssembly 里的 SQLite 内核。前端同学在这里的感受会很直接:浏览器里原来真的能做这种事,而且不是 demo,是能接实际业务的。
WebAssembly 的局限性
WebAssembly 也不是没坑。我一向不太相信那种“以后前端全写 WASM”的判断。大部分前端业务,状态管理、交互编排、请求调度、组件组织,这些东西仍然是 JavaScript 和 TypeScript 的地盘,变不了。让业务同学把弹窗开关、表单联动、埋点上报全部塞进 WASM,等于是给自己找麻烦。
真正合理的用法,一般是分层处理。
UI 渲染、状态管理、网络请求这些,还放在 JS 这边。重计算、重解析、旧库复用这些,切出去给 WASM。
像下面这种写法就比较符合分层思路:
async function runWasmFilter(input) {
const wasm = await initWasm();
const inPtr = wasm.alloc(input.length);
const outPtr = wasm.alloc(input.length);
wasm.memory.set(input, inPtr);
wasm.filter_gray(inPtr, outPtr, input.length);
const result = wasm.memory.slice(outPtr, outPtr + input.length);
wasm.free(inPtr);
wasm.free(outPtr);
return result;
}这段代码一看就不是写页面的常规路子。指针操作、内存分配、数据拷贝、手动释放,前端同学平时不太碰这些东西。也正因如此,WebAssembly 一直没彻底铺开:它的能力确实强,但学习门槛不低,调试体验、构建链路、产物体积控制、跨边界调用的开销,都不是一句“性能更好”就能忽略的。
什么时候应该考虑 WebAssembly
越来越多前端重新关注 WebAssembly,我认为不是技术潮流在回潮这么简单,而是大家终于看清楚了一件事:前端要解决的问题,越来越多不再是“页面怎么写”,而是“浏览器能不能承载更重的计算能力”。一旦问题变成了这个,WASM 就绕不过去了。
以前它像展厅里的展品,大家会看一眼,点点头,但项目里不太敢真用。现在它开始往实际工具的位置上走了。不是每个项目都需要,但有些项目没它确实很难受。
遇到下面这几类需求,我一般会优先评估能不能上 WebAssembly,而不是先把 JS 代码写满再说:
图像和音视频处理
本地规则执行和加解密运算
大型文档解析和二进制协议处理
浏览器内的数据库和全文检索
需要复用现有 C、C++、Rust 原生库的场景
总结
WebAssembly 被重新重视,不是因为前端突然对底层技术感兴趣了,而是因为前端的边界已经被推到了这里。以前浏览器像个壳子,壳子阶段 JS 足够用。现在浏览器越来越像半个运行时环境,那就得允许更适合计算型任务的东西进来。
本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!