前端 postMessage 技术——讲解:postMessage是浏览器提供的安全跨源通信API,解决同源策略限制下的数据交互问题。支持父页面与iframe、弹窗、WebWorker等场景通信,通过

前端 postMessage 技术——讲解:postMessage是浏览器提供的安全跨源通信API,解决同源策略限制下的数据交互问题。支持父页面与iframe、弹窗、WebWorker等场景通信,通过

1. postMessage 是什么?解决什么问题?

浏览器出于安全考虑有同源策略(scheme +host + port 完全一致才同源)。不同源页面之间默认不能直接读写彼此数据
window.postMessage 提供一个安全的跨源通信通道,允许:

  • 父页面 ↔ 子 iframe
  • 页面 ↔ 新开弹窗(window.open
  • 同源多个标签页(也可用 BroadcastChannel)
  • 页面 ↔ Service Worker(client.postMessage
  • 主线程 ↔ Web Worker(worker.postMessage
核心是消息传递:发送端调用 postMessage,接收端监听 message 事件。

2. 基本 API 与数据传输

2.1 发送端

targetWindow.postMessage(message, targetOrigin /* 必填! */, transferOrOptions); 
  • message:可被 structured clone 的数据(对象/数组/字符串/数值/布尔/ArrayBuffer/ImageBitmap/MessagePort/OffscreenCanvas 等)。
  • targetOrigin强烈建议指定确切源,如 'https://example.com'。仅调试时可用 "*"
  • transferOrOptions(可选):可转移所有权的对象列表(如 MessagePortArrayBuffer),或较新的 options 对象(兼容性以主流实现为准)。

2.2 接收端

window.addEventListener('message', (event) => { // event.data → 消息体 // event.origin → 发送方源(务必校验) // event.source → 发送方 window 引用(可回发) // event.ports → 携带的 MessagePort(如用 MessageChannel) }); 

2.3 结构化克隆(structured clone)

  • JSON.stringify 更强:可传复杂对象 & 二进制(可选“转移所有权”零拷贝)。
  • 不可传:函数、DOM 节点等。

3. 安全与健壮性要点(务必牢记)

  1. 永远校验 event.origin:只处理来自白名单源的消息。
  2. 不要使用 targetOrigin: "*" (除非完全公开且无敏感数据)。
  3. 永不信任消息内容:做 schema 校验 & XSS 过滤;严禁 eval
  4. 生命周期管理:在不需要时移除事件监听,防内存泄漏。
  5. 超时与重试:请求-响应模式要有超时、重试与取消。
  6. 内容安全策略(CSP):减少注入风险。

消息验签/版本/类型校验:设计统一数据格式:

interface Message<T = any> { v: '1.0'; // 协议版本 type: string; // 业务类型 id?: string; // 请求-响应关联ID payload?: T; // 负载 error?: string; // 错误信息 } 

4. 常见场景与代码

4.1 父页面 ↔ 子 iframe(跨源)

文件:parent.html(与 child.html 不同端口/域名即可模拟跨源)

<!doctype html> <html> <head><meta charset="utf-8"><title>Parent</title></head> <body> <h1>Parent</h1> <iframe src="http://127.0.0.1:5501/child.html"></iframe> <button>向子页面请求数据</button> <pre></pre> <script> const child = document.getElementById('child'); const CHILD_ORIGIN = 'http://127.0.0.1:5501'; // 白名单子源 const log = (...a)=>document.getElementById('log').textContent += a.join(' ')+'\n'; // 请求-响应:用 id 关联,并带超时 const pending = new Map(); const req = (type, payload, timeout=3000) => new Promise((resolve, reject) => { const id = Math.random().toString(36).slice(2); pending.set(id, {resolve, reject}); const timer = setTimeout(() => { pending.delete(id); reject(new Error('timeout')); }, timeout); pending.get(id).timer = timer; child.contentWindow.postMessage({v:'1.0', type, id, payload}, CHILD_ORIGIN); }); window.addEventListener('message', (e) => { if (e.origin !== CHILD_ORIGIN) return; // 安全校验 const {v, id, type, payload, error} = e.data || {}; if (v !== '1.0') return; // 协议版本 if (id && pending.has(id)) { const {resolve, reject, timer} = pending.get(id); clearTimeout(timer); pending.delete(id); return error ? reject(new Error(error)) : resolve(payload); } // 也可处理“推送类”消息 if (type === 'child:hello') log('来自子页面:', payload); }); document.getElementById('ask').onclick = async () => { try { const data = await req('parent:getTime', null, 5000); log('子页面返回时间:', data.now); } catch (err) { log('请求失败:', err.message); } }; </script> </body> </html> 

文件:child.html

<!doctype html> <html> <head><meta charset="utf-8"><title>Child</title></head> <body> <h3>Child iframe</h3> <script> const PARENT_ORIGIN = 'http://127.0.0.1:5500'; // 父页面源 // 启动时向父页面打个“招呼”(推送消息) window.parent.postMessage({v:'1.0', type:'child:hello', payload:'child ready'}, PARENT_ORIGIN); window.addEventListener('message', (e) => { if (e.origin !== PARENT_ORIGIN) return; // 校验来源 const {v, id, type, payload} = e.data || {}; if (v !== '1.0') return; if (type === 'parent:getTime') { // 处理请求并响应 const resp = {v:'1.0', id, type:'resp:time', payload: {now: new Date().toISOString()}}; e.source.postMessage(resp, e.origin); } }); </script> </body> </html> 
运行建议:开两个本地静态服务器(端口不同即不同源),如 5500parent.html5501child.html

4.2 页面 ↔ 弹窗(OAuth 登录/授权回调常用)

文件:main.html

<!doctype html> <html> <body> <button>打开登录弹窗</button> <pre></pre> <script> const AUTH_ORIGIN = 'https://auth.example.com'; // 假定第三方登录域 const out = (...a)=>document.getElementById('out').textContent += a.join(' ')+'\n'; document.getElementById('login').onclick = () => { const win = window.open(AUTH_ORIGIN + '/login.html', 'auth', 'width=400,height=600'); const id = Math.random().toString(36).slice(2); const timer = setInterval(() => { if (win.closed) { clearInterval(timer); out('弹窗被关闭'); }}, 300); const onMsg = (e) => { if (e.origin !== AUTH_ORIGIN) return; const {type, payload} = e.data || {}; if (type === 'auth:success') { out('登录成功,token=', payload.token); window.removeEventListener('message', onMsg); win.close(); } if (type === 'auth:error') { out('登录失败:', payload.reason); window.removeEventListener('message', onMsg); win.close(); } }; window.addEventListener('message', onMsg); }; </script> </body> </html> 

文件:登录页(第三方域)login.html(示意)

<!doctype html> <html> <body> <h3>模拟第三方登录</h3> <button>同意并返回</button> <button>失败</button> <script> // 注意:真实环境要把此处改为主站 origin const PARENT = 'https://your-app.example.com'; document.getElementById('ok').onclick = () => { window.opener.postMessage({type:'auth:success', payload:{token:'abc123'}}, PARENT); }; document.getElementById('fail').onclick = () => { window.opener.postMessage({type:'auth:error', payload:{reason:'user cancelled'}}, PARENT); }; </script> </body> </html> 

4.3 使用 MessageChannel 建立专用双工通道(更高效)

  • 创建 MessageChannel,将 port2 通过一次 postMessage 发送给子页面。
  • 之后双方用 port1/port2 通道通信,避免全局 message 池子里的“串台”。

父页面:

const channel = new MessageChannel(); const {port1, port2} = channel; const CHILD_ORIGIN = 'http://127.0.0.1:5501'; const iframe = document.querySelector('iframe'); port1.onmessage = (e) => { console.log('来自子页(专用通道):', e.data); }; // 把 port2 作为可转移对象发给子页面 iframe.contentWindow.postMessage({type:'init-port'}, CHILD_ORIGIN, [port2]); // 之后用 port1 发消息 port1.postMessage({type:'ping', t: Date.now()}); 

子页面:

let port; window.addEventListener('message', (e) => { // 校验 origin 省略… if (e.data?.type === 'init-port') { port = e.ports[0]; port.onmessage = (me) => { console.log('父页来的:', me.data); port.postMessage({type:'pong', t: Date.now()}); }; } }); 
优点:专线通信、更清晰、更易做请求-响应协议,也能避免误处理其它 postMessage

4.4 主线程 ↔ Web Worker(计算/IO 解耦)

main.js

const worker = new Worker('./worker.js', {type: 'module'}); const call = (cmd, payload) => new Promise((resolve) => { const id = Math.random().toString(36).slice(2); const onMsg = (e) => { if (e.data?.id === id) { worker.removeEventListener('message', onMsg); resolve(e.data.result); } }; worker.addEventListener('message', onMsg); worker.postMessage({id, cmd, payload}); }); // 调用 call('sum', [1,2,3,4]).then(res => console.log('sum=', res)); 

worker.js

self.addEventListener('message', (e) => { const {id, cmd, payload} = e.data || {}; if (cmd === 'sum') { const result = payload.reduce((a,b)=>a+b,0); self.postMessage({id, result}); } }); 
Worker 的 postMessage 同样基于结构化克隆,并支持转移 ArrayBuffer 进行零拷贝大数据传输(worker.postMessage(data, [data.buffer]))。

5. 可靠的请求-响应(Promise 封装)

在复杂业务里,经常需要像 RPC 一样“请求 → 等响应/错误”。下面给出可复用的小工具(父或子都能用):

// rpc.js export function createRPC(sendFn, onMessage) { const pendings = new Map(); function request(type, payload, {timeout=5000}={}) { const id = Math.random().toString(36).slice(2); return new Promise((resolve, reject) => { const timer = setTimeout(() => { pendings.delete(id); reject(new Error('timeout')); }, timeout); pendings.set(id, {resolve, reject, timer}); sendFn({v:'1.0', id, type, payload}); }); } function handle(msg) { const {v, id, type, payload, error} = msg || {}; if (v !== '1.0') return; if (id && pendings.has(id)) { const {resolve, reject, timer} = pendings.get(id); clearTimeout(timer); pendings.delete(id); return error ? reject(new Error(error)) : resolve(payload); } onMessage?.(msg); // 非请求响应类推送 } return {request, handle}; } 

用法举例(父页使用全局 postMessage)

import {createRPC} from './rpc.js'; const CHILD_ORIGIN = 'http://127.0.0.1:5501'; const childWin = document.querySelector('iframe').contentWindow; const rpc = createRPC( // sendFn (msg) => childWin.postMessage(msg, CHILD_ORIGIN), // onMessage(可选) (push) => console.log('推送:', push) ); window.addEventListener('message', (e) => { if (e.origin !== CHILD_ORIGIN) return; rpc.handle(e.data); }); // 调用 rpc.request('getProfile', {uid: 123}).then(console.log).catch(console.error); 

6. BroadcastChannel(同源多页群发)

同源多个标签页/iframe/worker 之间广播消息:

const bc = new BroadcastChannel('room-1'); bc.onmessage = (e) => console.log('收到:', e.data); bc.postMessage({type:'notify', text:'hello everyone'}); 
同源可用;无需管理窗口引用,API 简洁。

7. 调试与常见坑

  • 看不到消息?
    1. targetOrigin 不匹配;2) 接收方未监听或过早关闭;3) 被浏览器拦截的弹窗未创建成功。
  • 多次触发/串台?
    统一消息协议 + 指定 type + 使用 MessageChannel 专线。
  • 性能问题?
    批量发送合并/节流;大数据使用转移ArrayBuffer)避免拷贝。
  • Safari/移动端差异?
    事件循环时序可能有差异,初始化连接(如发送 port)时机要在 load 之后更稳妥。

8. 何时用 postMessage,何时用别的?

  • 多源页面通信 → postMessage
  • 同源群聊 → BroadcastChannel
  • 复杂、稳定的点对点通道 → MessageChannel
  • 计算/IO 解耦 → Web Worker
  • 页面 ↔ Service Worker → postMessagenavigator.serviceWorker.controller.postMessage

Read more

【Python篇】PyQt5 超详细教程——由入门到精通(序篇)

【Python篇】PyQt5 超详细教程——由入门到精通(序篇)

文章目录 * PyQt5 超详细入门级教程 * 前言 * 序篇:1-3部分:PyQt5基础与常用控件 * 第1部分:初识 PyQt5 和安装 * 1.1 什么是 PyQt5? * 1.2 在 PyCharm 中安装 PyQt5 * 1.3 在 PyCharm 中编写第一个 PyQt5 应用程序 * 1.4 代码详细解释 * 1.5 在 PyCharm 中运行程序 * 1.6 常见问题排查 * 1.7 总结 * 第2部分:创建 PyQt5 应用程序与布局管理 * 2.1 PyQt5 的基本窗口结构

By Ne0inhk
macOS 彻底卸载 Python 的完整指南

macOS 彻底卸载 Python 的完整指南

macOS 彻底卸载 Python 的完整指南 * macOS 彻底卸载 Python 的完整指南 * ⚠️ 重要警告 * 🔍 卸载前检查 * 🗑️ 卸载方法(按安装方式) * 1. 卸载 Homebrew 安装的 Python * 2. 卸载官方 pkg 安装的 Python * 3. 卸载 pyenv 管理的 Python * 4. 卸载 Miniconda/Anaconda * 🧹 全面清理残留文件 * 🔄 恢复系统默认 Python 环境 * 💡 最佳实践:使用虚拟环境 * ⚠️ 特殊情况处理 * 📊 卸载后验证 macOS 彻底卸载 Python 的完整指南 在 macOS 上安全卸载 Python 需要谨慎操作,因为系统自带 Python

By Ne0inhk
【Python】基础语法入门(一)

【Python】基础语法入门(一)

前言 Python作为一门入门门槛低、生态丰富的编程语言,Python早已成为编程初学者、数据分析从业者、后端开发者的首选工具之一。而掌握Python的第一步,就是吃透最核心的基础语法,常量与表达式、变量与类型、注释、输入输出及运算符。今天,我们就结合实例,手把手带你入门这些必备知识点,助你快速搭建Python语法框架。 一、常量与表达式 刚接触 Python 时,我们可以先把它当作一个功能强大的计算器 ,通过简单的表达式,以完成各类算术运算,比如简单的加减乘除,甚至复杂的乘方运算,都能直接通过“表达式”实现。 核心知识点: 1. 表达式与常量:形如1 + 2 * 3的算式称为“表达式”,运算结果为“表达式的返回值”;1、2、3这类固定值称为“字面值常量”,+、-、*、/则是“运算符”。 2. 运算规则:遵循“先乘除后加减”的数学逻辑,

By Ne0inhk
Python 基础与环境配置

Python 基础与环境配置

第一篇:Python 基础与环境配置 学习目标 💡 掌握 Python 语言的基本语法和编程思想 💡 学会安装和配置 Python 开发环境 💡 理解并熟练运用 Python 的数据类型、变量和运算符 💡 掌握 Python 的流程控制语句(条件判断、循环) 💡 学会使用 Python 的函数和模块 💡 了解 Python 的常用开发工具和集成开发环境(IDE) 💡 具备编写简单 Python 程序的能力 重点内容 * Python 语言的发展历程与特点 * Python 开发环境的安装与配置 * Python 的基本语法(变量、数据类型、运算符) * 流程控制语句(if 语句、for 循环、while 循环) * 函数的定义、调用和参数传递 * 模块和包的使用 * 常用开发工具和

By Ne0inhk