百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

宇宙厂:有了 structureClone 为何还需要可转移对象Transferable?

myzbx 2025-10-19 10:03 19 浏览

Chrome 13 引入了一种名为结构化克隆 (structureClone) 的算法,其允许将 ArrayBuffer 发送到 Web Worker 或从 Web Worker 接收 ArrayBuffer。该特性使得 postMessage() API 不仅可以接收字符串类型的消息,还可以接收 File、Blob、ArrayBuffer 和 JSON 对象等复杂类型的消息。

1. 可转移对象 vs. 结构化克隆技术

结构化克隆算法虽然很好,但其仍需要复制,而将 32MB 的 ArrayBuffer 传递给 Worker 的开销可能高达数百毫秒。因此,新版本的浏览器在消息传递方面引入了名为可转移对象 (Transferable Object) 的重大性能改进。

可转移对象是指可以转移到其他 JavaScript 上下文,例如:另一个 Window 或 Worker 的对象,转移后该对象在原始上下文不可用。转移对象比在其他上下文中重新创建对象轻量的多。

可转移对象由于底层是基于零拷贝技术,从而极大地提高了向 Worker 发送数据的性能。

该技术与 C/C++ 中按引用传递类似,但是与按引用传递不同的是:一旦传输到新的上下文,源上下文中的对象不再可用。例如:当将 ArrayBuffer 从主应用传输到 Worker 时,原始 ArrayBuffer 会被清除,其内容仅存在于 Worker 上下文。

下面是在 postMessage() 中使用可转移对象的示例:

worker.postMessage(arrayBuffer, [transferableList]);
window.postMessage(arrayBuffer, targetOrigin, [transferableList]);

对于 Worker 来说,第一个参数是 ArrayBuffer 消息,第二个参数是需要转移的对象列表。

2. 典型的可转移对象实例

典型的可转移对象包括以下几种,分别给出示例代码:

2.1 ArrayBuffer 对象

const worker = new Worker("worker.js");
// 创建一个 ArrayBuffer(比如表示图像或音频数据)
const buffer = new ArrayBuffer(1024 * 1024);
// 1MB 缓冲区
const view = new Uint8Array(buffer);
// 填充一些测试数据
for (let i = 0; i < view.length; i++) {
  view[i] = i % 256;
}
console.log("主线程发送 ArrayBuffer 前可用:", buffer.byteLength / 1024 + "KB");
// 将 ArrayBuffer 转移给 Worker
worker.postMessage({buffer}, [buffer]);
console.log("ArrayBuffer 已转移,主线程中不可再用");

下面是 worker 中的代码:

onmessage = function (e) {
  const {buffer} = e.data;
  // 检查是否成功接收并查看内容
  const view = new Uint8Array(buffer);
  console.log("Worker 收到 ArrayBuffer 大小:", buffer.byteLength / 1024 + "KB");
  console.log("前几个字节:", view[0], view[1], view[2]);
  // 继续进行加密、压缩、绘图等操作
};

值得一提的是,worker.postMessage 方法的第二个参数是可转移对象数组,用于转移所有权,所有权转移至目标端后发送端将不再可用。

2.2 ImageBitmap 对象

下面代码演示了从主线程加载图片 → 创建 ImageBitmap → 转移给 Worker 渲染或处理图像数据的完整示例:

const worker = new Worker("worker.js");
fetch("example.png")
  .then((response) => response.blob())
  .then((blob) => createImageBitmap(blob))
  .then((bitmap) => {
    // 将 ImageBitmap 转移给 Worker
    worker.postMessage({bitmap}, [bitmap]);
    console.log("ImageBitmap 已转移,主线程中不可再用");
  });

下面是 worker 中代码示例:

onmessage = function (e) {
  const {bitmap} = e.data;
  const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
  const ctx = canvas.getContext("2d");
  ctx.drawImage(bitmap, 0, 0);
  console.log("ImageBitmap 成功在 Worker 中使用");
};

与 ArrayBuffer 中的示例一样,worker.postMessage 方法的第二个参数是可转移对象数组,用于转移所有权,所有权转移至目标端后发送端将不再可用。

2.3 MessagePort 对象

下面代码示例通过 MessageChannel 创建两个 MessagePort,将其中一个传给 Worker,实现长期通信通道。

const worker = new Worker("worker-port.js");
const channel = new MessageChannel();
channel.port1.onmessage = function (e) {
  console.log("主线程收到消息:", e.data);
};
// 第二个参数是可转移对象数组,用于转移所有权
// 所有权转移至目标端后发送端将不再可用
worker.postMessage({port: channel.port2}, [channel.port2]);
channel.port1.postMessage("来自主线程的问候");

下面是 worker 端的代码示例:

let port;
onmessage = function (e) {
  // 接收传来的 port
  port = e.data.port;
  // 监听 port 消息
  port.onmessage = function (ev) {
    console.log("Worker 收到消息:", ev.data);
    port.postMessage("Worker 的回应");
  };
};

其实除了上面说的 ArrayBuffer、MessagePort、ImageBitmap 外,还包括很多可用的转移对象,例如:

  • ReadableStream
  • WritableStream
  • TransformStream
  • WebTransportReceiveStream
  • WebTransportSendStream
  • AudioData
  • VideoFrame
  • OffscreenCanvas
  • RTCDataChannel
  • MediaSourceHandle
  • MIDIAccess
  • MediaStreamTrack

例如,下面示例是使用 ReadableStream 作为可转移对象(Transferable Object)并转移到 Web Worker 的完整代码示例,结合了流式数据处理和线程间高效传输的技术要点:

// 创建可读流
const readableStream = new ReadableStream({
  start(controller) {
    // 模拟分块数据
    const chunks = ['Hello', '','World','!'];
    chunks.forEach(chunk => controller.enqueue(new TextEncoder().encode(chunk)));
    controller.close();
  }
});
// 创建 Worker
const worker = new Worker('worker.js');
// 提取可读流的底层控制器(用于转移)
const [streamForTransfer, streamForLocal] = readableStream.tee();
// 将可读流转移到 Worker
worker.postMessage(
  {type: 'stream', data: streamForTransfer},
  [streamForTransfer]
  // 指定可转移对象
);
// 本地保留的流用于调试
const localReader = streamForLocal.getReader();
localReader.read().then(processChunk);
function processChunk({done, value}) {
  if (!done) {
    console.log('Local stream chunk:', new TextDecoder().decode(value));
    localReader.read().then(processChunk);
  }
}

下面是 worker 中的代码:

self.onmessage = async (event) => {
  if (event.data.type === 'stream') {
    const readableStream = event.data.data;
    const reader = readableStream.getReader();
    // 异步读取流数据
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      // 处理二进制数据块,例如:文本解码
      const text = new TextDecoder().decode(value);
      self.postMessage({ type: 'chunk', data: text });
    }
    self.postMessage({ type: 'complete' });
  }
};

3. 可转移对象带来的性能提升明显

下图是使用 postMessage() 将一个 32MB 的 ArrayBuffer 发送给一个 Worker 并返回,同时为了准确性同一份代码运行了 5 次。如果浏览器不支持可转移对象,该示例将回退到结构化克隆。

在 MacBook Pro/10.6.8/2.53 GHz/Intel Core 2 Duo 上,使用结构化克隆时,FF 速度最快。平均需要 302 毫秒才能将 32MB ArrayBuffer 发送到 Worker 并将其发送回主线程(RRT - 往返时间)。而对于可转移对象的数据,相同测试仅需 6.6 毫秒。性能提升显著。

如此明显的性能提升使得大量 WebGL 纹理 / 网格能够在 Worker 和主应用之间无缝传递。

为了判断浏览器是否支持可转移对象,开发者可以向工作进程发送一个小的 ArrayBuffer。如果缓冲区被传输但未被复制,其 .byteLength 将始终为 0:

var ab = new ArrayBuffer(1);
worker.postMessage(ab, [ab]);
if (ab.byteLength) {
  alert("Transferables are not supported in your browser!");
} else {
  // Transferables are supported.
}

参考资料

https://developer.chrome.com/blog/transferable-objects-lightning-fast

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects

https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage

https://www.youtube.com/watch?v=UgfhWP-1gek

相关推荐

如何设计一个优秀的电子商务产品详情页

加入人人都是产品经理【起点学院】产品经理实战训练营,BAT产品总监手把手带你学产品电子商务网站的产品详情页面无疑是设计师和开发人员关注的最重要的网页之一。产品详情页面是客户作出“加入购物车”决定的页面...

怎么在JS中使用Ajax进行异步请求?

大家好,今天我来分享一项JavaScript的实战技巧,即如何在JS中使用Ajax进行异步请求,让你的网页速度瞬间提升。Ajax是一种在不刷新整个网页的情况下与服务器进行数据交互的技术,可以实现异步加...

中小企业如何组建,管理团队_中小企业应当如何开展组织结构设计变革

前言写了太多关于产品的东西觉得应该换换口味.从码农到架构师,从前端到平面再到UI、UE,最后走向了产品这条不归路,其实以前一直再给你们讲.产品经理跟项目经理区别没有特别大,两个岗位之间有很...

前端监控 SDK 开发分享_前端监控系统 开源

一、前言随着前端的发展和被重视,慢慢的行业内对于前端监控系统的重视程度也在增加。这里不对为什么需要监控再做解释。那我们先直接说说需求。对于中小型公司来说,可以直接使用三方的监控,比如自己搭建一套免费的...

Ajax 会被 fetch 取代吗?Axios 怎么办?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!今天给大家带来的主题是ajax、fetch...

前端面试题《AJAX》_前端面试ajax考点汇总

1.什么是ajax?ajax作用是什么?AJAX=异步JavaScript和XML。AJAX是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换,AJAX可以使网页实...

Ajax 详细介绍_ajax

1、ajax是什么?asynchronousjavascriptandxml:异步的javascript和xml。ajax是用来改善用户体验的一种技术,其本质是利用浏览器内置的一个特殊的...

6款可替代dreamweaver的工具_替代powerdesigner的工具

dreamweaver对一个web前端工作者来说,再熟悉不过了,像我07年接触web前端开发就是用的dreamweaver,一直用到现在,身边的朋友有跟我推荐过各种更好用的可替代dreamweaver...

我敢保证,全网没有再比这更详细的Java知识点总结了,送你啊

接下来你看到的将是全网最详细的Java知识点总结,全文分为三大部分:Java基础、Java框架、Java+云数据小编将为大家仔细讲解每大部分里面的详细知识点,别眨眼,从小白到大佬、零基础到精通,你绝...

福斯《死侍》发布新剧照 &quot;小贱贱&quot;韦德被改造前造型曝光

时光网讯福斯出品的科幻片《死侍》今天发布新剧照,其中一张是较为罕见的死侍在被改造之前的剧照,其余两张剧照都是死侍在执行任务中的状态。据外媒推测,片方此时发布剧照,预计是为了给不久之后影片发布首款正式预...

2021年超详细的java学习路线总结—纯干货分享

本文整理了java开发的学习路线和相关的学习资源,非常适合零基础入门java的同学,希望大家在学习的时候,能够节省时间。纯干货,良心推荐!第一阶段:Java基础重点知识点:数据类型、核心语法、面向对象...

不用海淘,真黑五来到你身边:亚马逊15件热卖爆款推荐!

Fujifilm富士instaxMini8小黄人拍立得相机(黄色/蓝色)扫二维码进入购物页面黑五是入手一个轻巧可爱的拍立得相机的好时机,此款是mini8的小黄人特别版,除了颜色涂装成小黄人...

2025 年 Python 爬虫四大前沿技术:从异步到 AI

作为互联网大厂的后端Python爬虫开发,你是否也曾遇到过这些痛点:面对海量目标URL,单线程爬虫爬取一周还没完成任务;动态渲染的SPA页面,requests库返回的全是空白代码;好不容易...

最贱超级英雄《死侍》来了!_死侍超燃

死侍Deadpool(2016)导演:蒂姆·米勒编剧:略特·里斯/保罗·沃尼克主演:瑞恩·雷诺兹/莫蕾娜·巴卡林/吉娜·卡拉诺/艾德·斯克林/T·J·米勒类型:动作/...

停止javascript的ajax请求,取消axios请求,取消reactfetch请求

一、Ajax原生里可以通过XMLHttpRequest对象上的abort方法来中断ajax。注意abort方法不能阻止向服务器发送请求,只能停止当前ajax请求。停止javascript的ajax请求...