首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >web messaging与Woker分类:漫谈postMessage跨线程跨页面通信

web messaging与Woker分类:漫谈postMessage跨线程跨页面通信

原创
作者头像
周陆军
发布于 2021-07-03 08:47:26
发布于 2021-07-03 08:47:26
2.4K00
代码可运行
举报
文章被收录于专栏:前端架构前端架构
运行总次数:0
代码可运行

web messaging

  • 跨文档通信(cross-document messaging):跨就是我们国内更为熟知的HTML5 window.postMessage()应用的那种通信;
  • 通道通信(channel messaging): 伴随着server-sent事件以及web sockets, 跨文档通信和通道通信成为HTML5 通信接口“套件”中有用的一部分。

window.postMessage

window.postMessage() 方法可以安全地实现跨源通信

iframe_contentWindow.postMessage(message, targetOrigin, [transfer]);

  • message发送的数据,它将会被结构化克隆算法序列化。符串,结构对象、数据对象(如:File和ArrayBuffer)或是数组都是可以的。
  • targetOrigin:接受方。通过窗口的origin属性来指定哪些窗口能接收到消息事件,字符串"*"(表示无限制)或者指定URI。
  • transfer:Transferable 对象。 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

其他window可以监听分发的message:window.addEventListener("message", callback, false);

window.postMessage安全问题

如果您不希望从其他网站接收message,请不要为message事件添加任何事件侦听器。 这是一个完全万无一失的方式来避免安全问题。

如果您确实希望从其他网站接收message,请始终使用origin和source属性验证发件人的身份。无法检查origin和source属性会导致跨站点脚本攻击。—— 任何窗口都可以向任何其他窗口发送消息,并且您不能保证未知发件人不会发送恶意消息。 但是,验证身份后,您仍然应该始终验证接收到的消息的语法。 否则,您信任只发送受信任邮件的网站中的安全漏洞可能会在您的网站中打开跨网站脚本漏洞。

使用postMessage将数据发送到其他窗口时,始终指定精确的目标origin,而不是*

无法检查origin和source属性会导致跨站点脚本攻击。

worker.postMessage

Worker 接口是Web Workers API 的一部分,代表一个后台任务,创建一个专用Web worker,它只执行URL指定的脚本,并且在工作线程中执行。主从线程通过 postMessage发送消息和 onmessage  onmessage  接受消息

worker 将运行在与当前 window不同的另一个全局上下文中,这个上下文由一个对象表示,标准情况下为DedicatedWorkerGlobalScope (标准 workers 由单个脚本使用; 共享workers使用SharedWorkerGlobalScope)。

除了无法读取DOM对象(包括:document、window、parent)、本地文件、对话框(alert/confirm/prompt),大部分 window 对象的方法和属性是可以使用的,如:  WebSocketsIndexedDB、 XMLHttpRequest 等,具体查看 Functions and classes available to workers

Woker分类

  • Shared Workers 一个Shared Workers可以被多个脚本使用——即使这些脚本正在被不同的window、iframe或者worker访问。,只要这些workers处于同一主域。共享worker 比专用 worker 稍微复杂一点 — 脚本必须通过活动端口进行通讯(MessageChannel的port1与port2间传递 )。详情请见SharedWorker
  • Broadcast Channel: 可以实现同  下浏览器不同窗口,Tab页,frame或者 iframe 下的 浏览器上下文 (通常是同一个网站下不同的页面)之间的简单通讯。 在任何页面new BroadcastChannel('mychannel')并postMessage,其他页面的BroadcastChannel实例onmessage 都能收收到消息 它与postMessage的区别就是:BroadcastChannel只能用于同源的页面之间进行通信,而postMessage却可以用于任何的页面之间的通信,换句话说,BroadcastChannel可以认为是postMessage的一个实例,它承担了postMessage的一个方面的功能。
  • Service Workers 一般作为web应用程序、浏览器和网络(如果可用)之间的代理服务。他们旨在(除开其他方面)创建有效的离线体验,拦截网络请求,以及根据网络是否可用采取合适的行动,更新驻留在服务器上的资源。他们还将允许访问推送通知和后台同步API
    • Service worker运行在worker上下文,因此它不能访问DOM。相对于驱动应用的主JavaScript线程,它运行在其他线程中,所以不会造成阻塞。它设计为完全异步,同步API(如XHR和localStorage)不能在service worker中使用。
    • 不同于普通Worker,Service Worker 是一个浏览器中的进程而不是浏览器内核下的线程(Service Worker是走的另外的线程,可以理解为在浏览器背后默默运行的一个线程,或者说是独立于当前页面的一段运行在浏览器后台进程里的脚本。)因此它在被注册安装之后,能够被在多个页面中使用,也不会因为页面的关闭而被销毁。
    • 出于对安全问题的考虑,Service Worker 只能被使用在 https 或者本地的 localhost 环境下。
  • subworker: worker 能够生成更多的 worker。这就是所谓的subworker(还是Woker),它们必须托管在同源的父页面内。而且,subworker 解析 URI 时会相对于父 worker 的地址而不是自身页面的地址。这使得 worker 更容易记录它们之间的依赖关系。
  • Chrome Workers: It works exactly like a standard Worker,you can do so by using ChromeWorker instead of the standard Worker object。我的理解是 只是在chrome 跑的worker 。详情请见ChromeWorker

Woker作用

Worker作用在《浏览器层面优化前端性能(1):Chrom组件与进程/线程模型分析》里面讲过

  • 要尽量避免JS执行时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。Web Worker  异步优化下》
    • 创建Worker时,JS引擎向浏览器申请开一个子线程(子线程是浏览器开的,完全受主线程控制,而且不能操作DOM)
    • JS引擎线程与worker线程间通过特定的方式通信(postMessage API,需要通过序列化对象来与线程交互特定的数据) JS引擎是单线程的,这一点的本质仍然未改变,Worker可以理解是浏览器给JS引擎开的外挂,专门用来解决那些大量计算问题。
  • SharedWorker是浏览器所有页面共享的,不能采用与Worker同样的方式实现,因为它不隶属于某个Render进程,可以为多个Render进程共享使用。所以Chrome浏览器为SharedWorker单独创建一个进程来运行JavaScript程序,在浏览器中每个相同的JavaScript只存在一个SharedWorker进程,不管它被创建多少次。
    • 页面A发送数据给worker:window.worker.port.postMessage('get'),然后打开页面B,调用window.worker.port.postMessage('get'),即可收到页面A发送给worker的数据。

worker 属性与方法

postMessage(data, transferList);
  • data:发送的数据,会被 结构化克隆 ( structured clone)
  • transferList:Transferable对象的数组,用于传递所有权。如果一个对象的所有权被转移,在发送它的上下文中将变为不可用(中止),并且只有在它被发送到的worker中可用。
  • 可转移对象是如ArrayBuffer,MessagePort或ImageBitmap的实例对象。transferList数组中可默认不传,但不可传入null。一般为MessageChannel port
terminate()

立即终止 Worker 的行为. 本方法并不会等待 worker 去完成它剩余的操作;worker 将会被立刻停止

onmessage(event)  

Worker 接口的onmessage属性表示一个EventHandler事件处理函数,当message 事件发生时,该函数被调用。这些事件所属MessageEvent类型,且当Worker子线程返回一条消息时被调用

  • event: event 对象  event.data 为  structured clone 数据
onerror()  onmessageerror 
  • onerror特性是一个事件句柄,在 Workererror事件触发并冒泡时
  • onmessageerror 事件处理器接口是一个EventListener, 在 MessageEvent 类型的事件 messageerror 触发时调用 — 也就是说, 它收到的消息是不能进行序列化的 deserialized.

Woker性能优化

worker.terminate()

使用完毕,为了节省系统资源,必须关闭 Worker

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 主线程
worker.terminate();
// Worker 线程
self.close();
worker.postMessage(arrayBuffer, [arrayBuffer])

主线程与 Worker 之间的通信内容,可以是文本,也可以是对象。需要注意的是,这种通信是拷贝关系,即是传值而不是传址,Worker 对通信内容的修改,不会影响到主线程。事实上,浏览器内部的运行机制是,先将通信内容串行化,然后把串行化后的字符串发给 Worker,后者再将它还原。这会造成性能问题!为了解决这个问题,JavaScript 允许主线程把二进制数据直接转移给子线程,但是一旦转移,主线程就无法再使用这些二进制数据了,这是为了防止出现多个线程同时修改数据的麻烦局面。这种转移数据的方法,叫做Transferable Objects

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var ab = new ArrayBuffer(1);
worker.postMessage(ab, [ab]);

对于大的数据处理,不会产生性能负担。

同页面的 Web Worker

Worker 载入的是一个单独的 JavaScript 脚本文件,但是也可以载入与主线程在同一个网页的代码。减少网络加载耗时

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<script id="worker" type="app/worker">
  addEventListener('message', function () {
    postMessage('some message');
  }, false);
</script>
<script>
  var blob = new Blob([document.querySelector('#worker').textContent]);
  var url = window.URL.createObjectURL(blob);
  var worker = new Worker(url);
  worker.onmessage = function (e) {
    // e.data === 'some message'
  };
</script>

先将嵌入网页的脚本代码(注意必须指定<script>标签的type属性是一个浏览器不认识的值),转成一个二进制对象,然后为这个二进制对象生成 URL,再让 Worker 加载这个 URL。

woker 在时间循环中执行顺序

worker 因为JavaScript 新开一个线程,执行worker代码。shareWoker因为不同tab(一个tab一个进程),因而新开一个进程。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 主线程
let woker = new Worker('./test2.js');
woker.onmessage = (res) => {
  console.log(res.data);
};
setTimeout(()=>{
  console.log('main')
  setTimeout(()=>{
    console.log('main2')
    setTimeout(()=>{
      console.log('main3')
    },0)
  },0)
},0)
// woker 线程,test2.js
setTimeout(() => {
  self.postMessage('woker1');
}, 0);
self.postMessage('woker2');

 输出顺序为,每次都不一样,以为有网络请求呀

main main2 woker2 main3 woker1

main main2 woker2 woker1 main3

main main2 main3 woker2 woker1

如果是桶页面内,顺序就机会不会变

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<script id="worker" type="app/worker">
setTimeout(() => {
  self.postMessage('woker1');
}, 0);
self.postMessage('woker2');
</script>

<script>
// let woker = new Worker('./test2.js');
var blob = new Blob([document.querySelector('#worker').textContent]);
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);
worker.onmessage = (res) => {
  console.log(res.data);
};
setTimeout(()=>{
  console.log('main')
  setTimeout(()=>{
    console.log('main2')
    setTimeout(()=>{
      console.log('main3')
    },0)
  },0)
},0)

</script>

这个,还是JavaScript的 event loop 事件机制觉得,推荐阅读《弄懂javascript的执行机制:事件轮询|微任务和宏任务

在浏览器环境中,常见的 macro task 有 setTimeout、MessageChannel、postMessage、setImmediate。而常见的 micro task 有 MutationObsever 和 Promise.then。需要留意的是:

MessageChannel

Vue中对于 macro task 的实现,优先检测是否支持原生 setImmediate,这是一个高版本 IE 和 Edge 才支持的特性,不支持的话再去检测是否支持原生的MessageChannel,如果也不支持的话就会降级为 setTimeout 0。

MessageChannel用法

MessageChannel创建了一个通信的管道,这个管道有两个端口[port1,port2],可以可以通过postMessage互相通信。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const channel = new MessageChannel();
let port1 = channel.port1;
let port2 = channel.port2;
port1.onmessage = function (event) {
  console.log("port1收到来自port2的数据:" + event.data);
};
port2.onmessage = function (event) {
  console.log("port2收到来自port1的数据:" + event.data);
};
port1.postMessage("发送给port2");
port2.postMessage("发送给port1");

MessageChannel用法很简单,但是功能却不可小觑。

MessageChannel作用

web worker间通信

多个web worker并想要在两个web worker之间实现通信的时候,MessageChannel就可以派上用场:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var w1 = new Worker("worker1.js");
var w2 = new Worker("worker2.js");
var ch = new MessageChannel();
w1.postMessage("initial port",[ch.port1]);
w2.postMessage("initial port",[ch.port2]);
w2.onmessage = function(e){
      console.log(e.data);
}

通过web worker的postMessage方法把两个MessageChannel的port传递给两个web woker,然后就可以通过每个port的postMessage方法传递数据了。

iframe兄弟间通

传统iframe父子间通信:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var iframe1 = document.getElementById('iframe1');
iframe1.postMessage(message, '*');

使用MessagePort.postMessage方法把一条消息和MessageChannel.port2传递给iframe。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var iframe1 = document.getElementById('iframe1');
var iframe2 = document.getElementById('iframe2');
iframe1.contentWindow.postMessage('main','*',[port1]);
iframe2.contentWindow.postMessage('main','*',[port2]);

代码地址:channel messaging basic demo

worker_threads兄弟线程通信

nodejs的MessageChannel虽然与浏览器的,实现方式不同,但是用法相同,都是一个模型。在此列出,阐释这种思想。

const {isMainThread, parentPort, threadId, MessageChannel, Worker} = require('worker_threads');

通信事件message事件对象

Message事件的定义可参见这里

data

包含任意字符串数据,由原始脚本发送

origin

一个字符串,包含原始文档的方案、域名以及端口(如:http://domain.example:80)

lastEventId

一个字符串,包含了当前的消息事件的唯一标识符。

source

原始文件的窗口的引用。更确切地说,它是一个WindowProxy对象。

ports

一个数组,包含任何MessagePort对象发送消息。

在跨文档通信和通道通信中,lastEventId的值一般是个空字符串;lastEventId应用在服务器端发送事件上。发送信息中如果没有ports, 则ports属性值就是个长度为0的数组。

MessageEvent继承DOM事件接口,且属性共享。然而,通信事件并没有冒泡,不能取消,也没有默认行为

Service Worker

前端缓存分析

前端缓存 大致可以分为 http缓存 与 浏览器缓存

http缓存推荐阅读《浏览器http缓存机制剖析:存储策略与过期策略的机理分析》,我们来分析下 浏览器缓存

storage

cookie、localStorage、sessionStorage

cookie 最大约为4k,每个域名最多50kcookie——不同浏览器限制不一样,一般用来存储关键数据(比如用户登录信息)

localStorage/sessionStorage通常有5MB的存储空间,比如微信文章 不需要改动的资源(如css/js)就基本存储在localStorage里面

推荐阅读《登录状态控制:cookies对比sessionStorage保持信息的分析

前端数据库:

WebSql和IndexDB,其中WebSql被规范废弃,他们都有大约50MB的最大容量,一般 当页面 store 的数据可以直接存储在里面。

manifest 缓存

已经被废弃,因为他的设计有些不合理的地方,他在缓存静态文件的同时,也会默认缓存html文件。这导致页面的更新只能通过manifest文件中的版本号来决定。所以,应用缓存只适合那种常年不变化的静态网站。如此的不方便,也是被废弃的重要原因。

推荐阅读《html5离线缓存manifest详解》、《HTML5离线存储实战之manifest的那些坑》

Service Worker

Service Worker本质上也是浏览器缓存资源用的,只不过他不仅仅是cache,也是通过worker的方式来进一步优化。

他基于h5的web worker,所以绝对不会阻碍当前js线程的执行,sw最重要的工作原理就是

  • 后台线程:独立于当前网页线程;
  • 网络代理:在网页发起请求时代理,来缓存文件;

这里不再赘述,再开一篇《ServiceWorker工作机制与生命周期:资源缓存与协作通信处理

参考文章:

MessageChannel是什么,怎么使用? https://www.jianshu.com/p/4f07ef18b5d7

HTML5 postMessage iframe跨域web通信简介 https://www.zhangxinxu.com/wordpress/2012/02/html5-web-messaging-cross-document-messaging-channel-messaging/

Web Worker 使用教程 www.ruanyifeng.com/blog/2018/07/web-worker.html

转载本站文章《web messaging与Woker分类:漫谈postMessage跨线程跨页面通信》, 请注明出处:https://www.zhoulujun.cn/html/webfront/SGML/html5/2020_0615_8465.html

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Web中的窗口通讯方式及使用(postMessage/MessageChannel/BroadcastChannel)
三种常用的跨窗口通信技术:postMessage、MessageChannel 和 BroadcastChannel。
码客说
2024/01/06
2.6K0
前端和前端联调的各种姿势,了解一下
背景:父页面index.html里面有一个iframe,iframe的src为子页面(另一个html的链接),下文都是基于此情况下进行
lhyt
2019/12/17
1.6K0
前端和前端联调的各种姿势,了解一下
nodejs中使用worker_threads来创建新的线程
之前的文章中提到了,nodejs中有两种线程,一种是event loop用来相应用户的请求和处理各种callback。另一种就是worker pool用来处理各种耗时操作。
程序那些事
2021/01/28
2.4K0
Web Workers RPC:Comlink 源码解析
Comlink 通过提供 RPC 实现将基于 Worker.postMessage(someObject) 的 API 变成了对开发人员更友好的“类似本地调用”方式。
奋飛
2022/11/02
8400
Web Workers RPC:Comlink 源码解析
一文搞懂 Web Worker(原理到实践)
曾经的浏览器对于 JS 的处理模式是单线程模式,页面更新要先 串行 做 2 件事情。
coder_koala
2021/01/06
6.7K0
一文搞懂 Web Worker(原理到实践)
一文搞懂Electron的四种视图容器和它们之间的IPC通信机制
Electron作为一种基于JS语言搭建的桌面框架,其基础视图容器是包含了Chromium内核的窗口,称为BrowserWindow。对于更复杂的项目,如果需要在窗口内部嵌入第三方业务的页面,则有BrowserView、webView Tag和Iframe三种方案可供选择。
WendyGrandOrder
2022/12/20
12.4K1
React源码解析之requestHostCallback
前言: 在React源码解析之scheduleWork(下)中,我们讲到了unstable_scheduleCallback,其中在「按计划插入调度任务」后,会调用requestHostCallback()方法:
进击的小进进
2019/10/08
1.1K0
postMessage实现跨域通信
web通信(洋名:web messaging)是一种文档中独立的浏览上下文间的DOM不会被恶意的跨域脚本暴露数据分享方式。
javascript.shop
2019/09/04
1.8K0
postMessage实现跨域通信
前端魔法堂:可能是你见过最详细的WebWorker实用指南
JavaScript从使用开初就一直基于事件循环的单线程运行模型,即使是成功进军后端开发的Nodejs也没有改变这一模型。那么对于计算密集型的应用,我们必须创建新进程来执行运算,然后执行进程间通信实现传参和获取运算结果。否则会造成UI界面卡顿,甚至导致浏览器无响应。 从功能实现来看,我们可以通过新增iframe加载同域页面来创建JSVM进程执行运算从而避免造成界面卡顿的问题。但存在如下问题:
^_^肥仔John
2020/12/18
2K0
前端魔法堂:可能是你见过最详细的WebWorker实用指南
通过源码分析nodejs线程架构
nodejs支持了进程之后,又支持了线程。类似浏览器端的web worker。因为nodejs是单线程的,但是底层又实现了一个线程池,接着实现了进程,又实现了线程。一下变得混乱起来,我们要了解这些功能的实现原理,才能更好地使用他。上篇大致分析了进程的原理,这一篇来讲一下线程的原理。只有了解线程的实现,才能知道什么时候应该用线程,为什么可以用线程。 线程的实现也非常复杂。虽然底层只是对线程库的封装,但是把它和nodejs原本的架构结合起来似乎就变得麻烦起来。下面开始分析创建线程的过程。分析线程实现之前,我们先看一下线程通信的实现,因为线程实现中会用到。通俗来说,他的实现类似一个管道。
theanarkh
2020/03/31
6470
通过源码分析nodejs线程架构
requestIdleCallback
requestIdleCallback 是一个还在实验中的 api,可以让我们在浏览器空闲的时候做一些事情
用户4396583
2024/07/17
3600
打造多线程 Web
由于浏览器的限制,注定了每个网页只能在一个进程程当中运行, 而且,js又只能运行在一个线程当中. 所以, 作为一名开发者来说, 对于这样的结果就只能呵呵了. 如果你想进行高复杂度的运算, 基本上就可以go die了(只要运行, 你网页基本上就崩掉了). 当然,聪明的W3C早就知道developer心里的小猫腻. 推出了web worker 这个概念. 我们接下来,来正式接触一下 web worker吧. 初入web worker web worker 既然是一个线程. 那必定会设计到线程间的通信. 这里,w
villainhr
2018/07/03
4520
你不知道的 Web Workers
下面我们开始步入正题,为了让大家能够更好地理解和掌握 Web Workers,在正式介绍 Web Workers 之前,我们先来介绍一些与 Web Workers 相关的基础知识。
陈皮皮
2020/07/10
1.6K0
你不知道的 Web Workers
HTML 面试要点:跨标签页通信
BroadcastChannel (opens new window) 接口代理了一个命名频道,可以让指定 origin (opens new window) 下的任意 browsing context (opens new window) 来订阅它。它允许同源的不同浏览器窗口,Tab 页,frame 或者 iframe 下的不同文档之间相互通信。通过触发一个 message 事件,消息可以广播到所有监听了该频道的 BroadcastChannel 对象。
Cellinlab
2023/05/17
1.5K0
HTML 面试要点:跨标签页通信
Web Worker
众所周知,JavaScript 是单线程的语言。当我们面临需要大量计算的场景时(比如视频解码等),UI 线程就会被阻塞,甚至浏览器直接卡死。现在前端遇到大量计算的场景越来越多,为了有更好的体验,HTML5 中提出了 Web Worker 的概念。Web Worker 可以使脚本运行在新的线程中,它们独立于主线程,可以进行大量的计算活动,而不会影响主线程的 UI 渲染。当计算结束之后,它们可以把结果发送给主线程,从而形成了高效、良好的用户体验。Web Worker 是一个统称,具体可以细分为普通的 Worker、SharedWorker 和 ServiceWorker 等,接下来我们一一介绍其使用方法和适合的场景。
政采云前端团队
2022/04/27
1.2K0
Web Worker
JSON-RPC & postMessage 谈谈浏览器消息通信的封装技巧
postMessage 常见于内嵌 iframe 或是 Web Workers 中,用于跨页面(线程) 的消息通信,在一些其他开发环境中也能看到类似的影子,如 Chrome 插件环境、Electron 环境、figma 插件等。
用户6256742
2024/06/12
3290
JSON-RPC & postMessage 谈谈浏览器消息通信的封装技巧
专用工作者线程
可以把专用工作者线程称为后台脚本(background script)。JavaScript 线程的各个方面,包括生命周
iwhao
2024/08/05
3550
web 通信--跨文档、worker、通道
跨文档通信(cross-document messaging)、worker通信(cross-worker messaging)、通道通信(channel messaging)
奋飛
2022/01/24
8960
web 通信--跨文档、worker、通道
WebWorker简介
本文介绍了Web Worker和Service Worker的基本概念、用途以及区别。Web Worker可以在浏览器后台运行JavaScript,实现异步加载和执行,提高页面性能。Service Worker是Web Worker的升级版,具有更丰富的功能和更好的兼容性,支持多种浏览器。使用Service Worker可以离线推送、后台同步、消息推送等。
IMWeb前端团队
2018/01/08
8530
不同页面通信与跨域0. 前言1. localstorage2. 玩转iframe3. 非同域的两个tab页面通信4.MessageChannel
相信跨域有什么手段,大家都背得滚瓜烂熟了。现在我们来做一些不在同一个tab页面或者跨域的实践。
lhyt
2018/10/31
4.7K0
相关推荐
Web中的窗口通讯方式及使用(postMessage/MessageChannel/BroadcastChannel)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档