浏览器上下文通信

文章目录

  • 浏览器上下文通信
    • 同源通信
      • 同源通信流程
        • 同一浏览器上下文通信
        • 不同浏览器上下文通信
    • 跨域通信
      • 前端和前端跨域
      • 前端和后端跨域

浏览器上下文通信

浏览器上下文通信分为两种:同源和跨源。同样的同源通信也分为同一浏览器上下文和不同的浏览器上下文。

同源通信

同源通信方式多种多样,最常用的应该就是localStorage, sessionStorage,也有一个全局对象或者模块导出的对象,还有cookie, caches(cacheStorage)等,以及通过location对象,history.state, Event API, Messaging API, SharedWorker API等方式传递消息。

同源通信流程

通信流程大体分为两步:第一步是传递数据,第二步是通知目标

同一浏览器上下文通信

第一步很容易,上面的方法都可以。但是第二步就有点不同的。有些方法提供了官方的事件监听,比如storage, hashchange, popstate等,但有些是没有这些原生事件的,那就需要一种方式通知。

很容易想到的就是自定义事件(CustomEvent)

el.addEventListener('cookiechange', (e) => {console.log(e.detail);// ...
});const event = new CustomEvent('cookiechange', {detail: {getRandom(min, max) {return Math.floor(Math.random() * (max - min + 1)) + min;}},bubbles: true, // 可以调用 stopPropagationcancelable: true, // 可以调用 preventDefault
});// 更新操作后通知对应元素或其子元素(要设置冒泡)
el.dispatchEvent(event);

除了这种方式,还有一种常见的就是发布订阅

class PubSub {constructor() {this.subs = new Map() // 订阅者}// 订阅on(type, fn) {if (!this.subs.has(type)) {this.subs.set(type, new Set())}this.subs.get(type).add(fn)}// 发布emit(type, ...args) {if (this.subs.has(type)) {for (const fn of this.subs.get(type)) {fn(...args)}}}
}

最后一个就是window.postMessage,可以先监听message事件,然后在需要时通知。

window.addEventListener('message', (e) => {console.log(e);
});// 在需要时调用
window.postMessage({msg: '这是发给自己的消息',type: 'postMessage'},'*');
不同浏览器上下文通信

上面的方式都要求同一浏览器上下文,也就是处于同一个window下的文档,这属于内部通信。如果是不同的上下文,比如使用iframe或两个标签页,虽然是同源的但这样就无法通知到,这就需要跨浏览器上下文通信。

postMessage,Broadcast Channel API,Channel Messaging API, SharedWorker API都可以实现。
最简单的就是使用BroadcastSharedWorker(需要浏览器支持),其它两种更常用于跨域通信。可以点击上面的链接查看用法。

跨域通信

跨域通信也有很多类型,前端和前端跨域前端和后端跨域后端和后端跨域。后端和后端那就是纯后端的处理,和浏览器没什么关系了,除非使用了类似web socket这种长连接。

前端和前端跨域

想要实现两个跨域的前端页面通信,一方必须知道另一方的浏览器上下文或者消息句柄,也就是两个浏览器上下文必须要存在联系。比如使用<iframe>包含另一个上下文或者使用window.open打开另一个上下文。

需要注意的是,这两种方式都需要等待新的上下文加载完成之后才能通信的。所以<iframe>需要添加load事件监听,而window.open可以让对方先发送消息建立连接,类似TCP三次握手之后才正式发送消息。

// localhost:3000/a.html 向 localhost:4000/b.html 通信
// localhost:3000/a.html
const messageCallback = (e) => {if (e.origin === 'http://localhost:4000') {//接受返回数据console.log(e.data)}
}
let active = null;
const origin = 'http://localhost:4000';
frame.addEventListener('load', () => {window.addEventListener('message', messageCallback)active = frame.contentWindow;active.postMessage({type: 'init',msg: 'hello'},origin) //发送数据
})
frame.addEventListener('beforeunload', () => {window.removeEventListener('message', messageCallback)
})
// 发送消息函数
const postMsg = (payload) => {if (active && !active.closed) {active.postMessage(payload, origin);}
}// localhost:4000/b.html
let active = null;
let origin = '';
window.postMessage('message', (e) => {if (e.data && e.data.type === 'init') {active = e.source;origin = e.origin;return;}// 处理其它消息
});
const postMsg = (payload) => {// ...
}// 如果使用的是 window.open
let active = null;
let origin = '';
window.addEventListener('message', (e) => {// 处理初始化消息if (e.data === 'B_LOADED') {active = e.source;origin = e.origin;active.postMessage('A_ACCEPTED', origin);  // 第三步,完成之后可以正常通信了return;}// 处理其它消息
})
xxx.addEventListener('click', () => {// 默认创建标签页,如果传递第三个参数或`target: '_blank'`会创建新的窗口window.open('http://localhost:4000/b.html', 'b')  // 第一步// 这里创建之后可能页面还没加载完成,所以调用 postMessage 可能没有响应。// 要么使用定时器不断轮询,要么等待对方加载完成之后再建立连接。
})
const postMsg = (payload) => {// ...
}// localhost:4000/b.html
let active = null;
let origin = 'http://localhost:3000';
window.addEventListener('message', (e) => {if (e.data && e.data === 'A_ACCEPTED') {// 建立连接成功return;}// 处理其它消息
})
const postMsg = (payload) => {// ...
}
if (window.opener && !window.opener.closed) {active = window.opener;postMsg('B_LOADED'); // 第二步
}

这里使用的一般是上面的postMessageChannel Messaging API,可以点击上面的链接查看使用示例。

前端和后端跨域

前后端跨域是非常常见的,通用的解决方案一般使用使用JSONPnew Image需要使用callback作为参数的查询字符串,缺点就是只能发起GET请求。

另一种需要满足CORS的请求规范,也就是前后端同时遵守一套跨域规则,满足之后就允许跨域请求了。

一些普通的GET请求可以直接请求,但其它方法的请求需要先发起预检请求约定一些规则。

// server.js
import express from 'express'
const app = express()const whiteList = ['http://localhost:3000'] //设置白名单
// 设置跨域插件
app.use(function (req, res, next) {const origin = req.headers.originif (whiteList.includes(origin)) {// 设置哪个源可以访问我res.setHeader('Access-Control-Allow-Origin', origin)// 允许携带哪个头访问我res.setHeader('Access-Control-Allow-Headers', 'Access-Token')// 允许哪个方法访问我res.setHeader('Access-Control-Allow-Methods', 'POST,PUT,DELETE')// 允许携带cookieres.setHeader('Access-Control-Allow-Credentials', true)// 预检的存活时间res.setHeader('Access-Control-Max-Age', 24 * 60 * 60 * 1000)// 允许返回的头res.setHeader('Access-Control-Expose-Headers', 'latest-version')}next()
})
app.put('/getData', function (req, res) {console.log(req.headers)res.setHeader('latest-version', '3.2.1') // 返回一个响应头res.end('over');
})
app.listen(4000);

这个后端要求跨域请求的源必须是白名单里的,同时规定了请求需要使用的方法,跨域请求需要传递的请求头以及一些其它配置。只要用户满足请求方法和请求头的要求就可以请求http://localhost:4000/getData,这个方法设置了响应头latest-version和响应数据over

前端需要根据需求发起跨域请求。

// XMLHttpRequest
const xhr = new XMLHttpRequest()
xhr.withCredentials = true // 前端设置是否带cookie
xhr.open('PUT', 'http://localhost:4000/getData', true)
xhr.setRequestHeader('Access-Token', '123456')
xhr.onreadystatechange = function () {if (xhr.readyState === 4) {if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {//得到响应头console.log(xhr.getResponseHeader('latest-version')) // 3.2.1console.log(xhr.response) // over}}
}
xhr.send();// fetch
fetch('http://localhost:4000/getData', {mode: 'cors', // 跨域credentials: 'include', // 携带cookieheaders: {'Access-Token': '123456' // 这个自定义请求头是后台要求的},method: 'PUT' // 请求方式
}).then((res) => {for (const [key, value] of res.headers.entries()) {if (key === 'latest-version') {console.log(value) // 3.2.1}}return res.text()}).then((data) => {console.log(data) // over})

这样前端和后端的跨域请求就完成了,只要过期时间没到,再次跨域请求就不需要发起预检请求了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/77712.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux 离线部署 Docker 18.06.3 终极指南(附一键安装卸载脚本)

Linux 离线部署 Docker 18.06.3 终极指南&#xff08;附一键安装/卸载脚本&#xff09; 摘要&#xff1a;本文针对无外网环境的 Linux 服务器&#xff0c;提供基于二进制包的 Docker 18.06.3 离线安装全流程指南。包含自动化脚本设计、服务配置优化及安全卸载方案&#xff0c;…

【前端】跟着maxkb学习logicflow流程图画法

文章目录 背景1. 选定学习对象-maxkb应用逻辑编排2. 确定实现框架3. 关键逻辑&#xff1a;查看app-node.js4. 学习开始节点绘制流程数据形式 5. 给节点增加表单输入框遇到过的问题 背景 看看前端如何绘制流程图&#xff0c;界面好看点。 "logicflow/core": "1.…

Android 12系统静态壁纸深度定制指南

1. 需求背景与实现原理 在Android 12系统ROM定制开发中&#xff0c;扩展静态壁纸功能需要深入理解WallpaperManagerService的架构体系。系统壁纸管理通过双端协作实现&#xff1a; WallpaperManagerService&#xff08;frameworks层&#xff09;&#xff1a;负责壁纸状态管理、…

相得益彰 — 基于 GraphRAG 事理图谱驱动的实时金融行情新闻资讯洞察

*本文为亚马逊云科技博客文章&#xff0c;仅用于技术分享&#xff0c;不构成投资建议或金融决策支持。文中涉及的公司名称仅用于技术示例&#xff0c;不代表亚马逊云科技观点或与这些公司的商业合作关系。 背景介绍 在当今这个信息爆炸的时代&#xff0c;金融市场每天都在产生…

OpenCV---图像预处理(四)

OpenCV—图像预处理&#xff08;四&#xff09; 文章目录 OpenCV---图像预处理&#xff08;四&#xff09;九&#xff0c;图像掩膜9.1 制作掩膜9.2 与运算9.3 颜色替换9.3.19.3.2 颜色替换 十&#xff0c;ROI切割十 一&#xff0c;图像添加水印11.1模板输入11.2 与运算11.3 图像…

【MySQL】:数据库事务管理

一&#xff1a;学习路径 &#xff08;1&#xff09;下载安装mysql &#xff08;2&#xff09;学习语言&#xff1a;SQL(操作数据库&#xff09; &#xff08;3&#xff09;mysql集群&#xff08;提升数据库存储效率&#xff09; &#xff08;4&#xff09;SQL使用&#xff0c;M…

内存函数和动态内存管理

目录 一、memcpy库函数介绍 1. memcpy的使用 2. memcpy的模拟 二、memmove库函数介绍 1. memmove的使用 2. memmove的模拟 三、memset库函数介绍 四、memcmp库函数介绍 五、动态内存中malloc和free 1. malloc 2. free 六、动态内存中calloc和realloc 1. calloc 2. realloc 七、…

yarn的基本介绍

1.Hadoop的三大结构及各自的作用&#xff1a; Hadoop是一个开源的分布式计算框架&#xff0c;它主要包括三大核心组件&#xff1a;HDFS&#xff08;Hadoop Distributed File System&#xff09;、YARN&#xff08;Yet Another Resource Negotiator&#xff09;和MapReduce。以…

STM32的启动方式

目录 一、从主闪存存储器启动&#xff08;Main Flash Memory&#xff09; 二、从系统存储器启动&#xff08;System Memory&#xff09; 三、从内置SRAM启动&#xff08;Embedded SRAM&#xff09; 四、从外挂存储介质启动的实现方式 1. 存储介质选型 2. 硬件连接 3. 引…

STC定时器频率占空比程序

// // 一、宏定义区 // #include <STC15.H> //头文件 #include <intrins.h> //库函数文件 #define FOSC 12000000L //IRC频率 typedef …

数据库服务器架构

ORM ORM&#xff08;Object Relational Mapping&#xff09;&#xff1a;对象与关系数据之间的映射 映射关系表&#xff1a; 类&#xff08;class&#xff09;—— 数据库的表&#xff08;table&#xff09; 对象&#xff08;object&#xff09;——记录&#xff08;record…

【论文速递】2025年04周 (Robotics/Embodied AI/LLM)

目录 DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning摘要 Evolving Deeper LLM Thinking摘要 Kimi k1.5: Scaling Reinforcement Learning with LLMs摘要 Agent-R: Training Language Model Agents to Reflect via Iterative Self-Train…

FortiAI 重塑Fortinet Security Fabric全面智能化进阶

专注推动网络与安全融合的全球性综合网络安全解决方案供应商 Fortinet&#xff08;NASDAQ&#xff1a;FTNT&#xff09;&#xff0c;近日宣布&#xff0c;旗下 Fortinet Security Fabric 安全平台成功嵌入了 FortiAI 关键创新功能。这一举措将有效增强用户对各类新兴威胁的防护…

汽车免拆诊断案例 | 2019款大众途观L车鼓风机偶尔不工作

故障现象 一辆2019款大众途观L车&#xff0c;搭载DKV发动机和0DE双离合变速器&#xff0c;累计行驶里程约为8万km。车主进厂反映&#xff0c;鼓风机偶尔不工作。 故障诊断  接车后试车&#xff0c;鼓风机各挡位均工作正常。用故障检测仪检测&#xff0c;空调控制单元&#x…

MySQL为什么默认使用RR隔离级别?

大家好&#xff0c;我是锋哥。今天分享关于【MySQL为什么默认使用RR隔离级别?】面试题。希望对大家有帮助&#xff1b; MySQL为什么默认使用RR隔离级别? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 MySQL 默认使用 RR&#xff08;Repeatable Read&#xff09;…

目标检测篇---R-CNN梳理

目标检测系列文章 第一章 R-CNN 目录 目标检测系列文章&#x1f4c4; 论文标题&#x1f9e0; 论文逻辑梳理1. 引言部分梳理 (动机与思想) &#x1f4dd; 三句话总结&#x1f50d; 方法逻辑梳理&#x1f680; 关键创新点&#x1f517; 方法流程图补充边界框回归 (BBR)1. BBR 的…

Java技术栈 —— 基本规范

Java技术栈 —— 基本规范 一、接口文档生成工具二、接口设计2.1 开发顺序2.2 接口规范 三、数据类封装 一、接口文档生成工具 有很多jar包都支持swagger的接口文档&#xff0c;这样方便了接口测试&#xff0c;不需要用apifox自己写接口&#xff0c;直接调用文档里的swagger接…

Django ORM 定义模型

提示&#xff1a;定义模型字段的类型 文章目录 一、字段类型二、字段属性三、元信息 一、字段类型 常用字段 字段名描述备注AutoFieldint 自增必填参数 primary_keyTrue&#xff0c;无该字段时&#xff0c;django自动创建一个 BigAutoField&#xff0c;一个model不能有两个Au…

[密码学基础]GB与GM国密标准深度解析:定位、差异与协同发展

[密码学基础]GB与GM国密标准深度解析&#xff1a;定位、差异与协同发展 导语 在国产密码技术自主可控的浪潮下&#xff0c;GB&#xff08;国家标准&#xff09;与GM&#xff08;密码行业标准&#xff09;共同构建了我国商用密码的技术规范体系。二者在制定主体、法律效力、技术…

Day-1 漏洞攻击实战

实训任务1 漏洞攻击实战一 使用 御剑 得到网站后台地址 数据库登录与日志配置​​ 使用默认密码 root:root 登录phpMyAdmin&#xff0c;执行 SHOW VARIABLES LIKE general% 查看日志状态。 开启日志功能&#xff1a;set global general_log "ON";&#xff08;配图&…