浏览器不同源的页面之间如何跨域通信

news2024/11/23 3:12:24

目录

  • 1,需求
  • 2,难点
  • 3,思路
    • 浏览器不同源的页面之间如何跨域通信?
  • 4,实现
    • 第1版
    • 第2版
    • 最终版
    • 其他的问题
      • 1,页面路径需完全一致。
      • 2,事件注册问题

1,需求

现在有2个项目,页面路径不同源。

  • ToC 的收银台项目

类似在PC端京东淘宝,支付最后一步的收银台页面,可以选择不同支付工具付款。

  • ToB 的后台管理项目

可以对收银台项目整体做一些配置:样式,支付工具相关的等等,配置项很多。

需求

  1. 想要在后台管理项目中增删配置项后,能够实时预览收银台项目最终的展示效果
  2. 展示效果符合预期,则提交修改配置项的审批电子流
  3. 审批通过,上线

2,难点

因为2个项目的页面路径不同源,传递数据是个问题。

3,思路

首先,后台管理项目需要增加【预览】按钮,收银台项目需要增加【预览】页面。

  1. 需要后端参与。收银台项目的相关配置项,本来就是通过接口获取的。所以再增加一个预览数据的接口,在预览页面调用获取数据。

  2. 不需要后端参与。前端直接在2个页面之间通信。

第1种思路没有什么好说的,重点来说下第2种。

浏览器不同源的页面之间如何跨域通信?

  1. 通过 url 传参。最简单直接,不过传递的数据大小有限。

  2. postMessage,传递的数据大小我实测可以超200MB!(不知道极限,因为没再往上测试)

简单来说,我们可以获取从当前页面A通过window.open打开的页面B的引用 targetWindowB,然后在A页面通过 targetWindowB.postMessage() 向B页面分发消息。

再介绍下 window.open

简单说明:window.open 有3个参数,我们只关注前2个

  1. strUrl:新页面的地址
  2. strWindowName:新页面的名称,如果指定了该参数,则再次调用 window.open(strUrl, strWindowName) 时,不会再打开第2个新页面,而是跳转到打开的第1个页面并重新加载。(效果下面会有展示)

另外需要注意:调用window.open()方法以后,远程 URL 不会被立即载入,载入过程是异步的
会有什么问题,看下实现过程就知道了。

4,实现

通过 vite 创建2个项目模拟,A 会向 B 发送数据。启动后的页面地址分别是:

  1. A页面(后台管理项目 manage)http://localhost:5173
  2. B页面(收银台项目 cashier)http://localhost:5174

第1版

A页面后台管理项目 manage

<script setup>
const cashierUrl = "http://localhost:5174";
const data = { name: "下雪天的夏风" };
let cashierWindow;

function init() {
  cashierWindow = window.open(cashierUrl, "cashierWindow");
  if (cashierWindow) {
    cashierWindow.postMessage(data, cashierUrl);
  }
}
</script>

<template>
  <h1>manage</h1>
  <button @click="init">发送预览数据</button>
</template>

B页面收银台项目 cashier

<script setup>
window.addEventListener(
  "message",
  function (event) {
    if (event.origin !== "http://localhost:5173") return;
    if (event.data) {
      console.log(event.data);
    }
  },
  false
);
</script>

<template>
  <h1>cashier</h1>
</template>

效果:

在这里插入图片描述

可以看到收银台项目并没有接收到消息!

原因就是:调用window.open()方法以后,远程 URL 不会被立即载入,载入过程是异步的

换句话说,因为B页面还没有加载完成,message 事件还没有被绑定时,A页面已经把消息发送了。

第2版

延迟发送消息。

function init() {
  cashierWindow = window.open(cashierUrl, "cashierWindow");
  setTimeout(() => {
    if (cashierWindow) {
      const data = { name: "下雪天的夏风" };
      cashierWindow.postMessage(data, cashierUrl);
    }
  }, 1000);
}

效果:

在这里插入图片描述

B页面成功收到消息!

问题来了,因为这个测试用例比较简单,所以 1s B页面就会加载完成。
可面对复杂的页面+网络问题,A页面如何知道B页面已经加载完成了message 事件绑定了)?

答案是:此时B页面可以通过 window.opener 获取 A页面的引用,使用 postMessage 向A页面发送数据!

实现思路:

  1. B页面加载完成后,通过window.opener.postMessage() 向A页面发送一个约定字段。
  2. A页面接收到约定字段后,再向B页面发送目标数据。

最终版

A页面后台管理项目 manage

<script setup>
import { ref } from "vue";
const cashierUrl = "http://localhost:5174";
const cashierLoaded = ref(false);
let cashierWindow;

function init() {
  cashierWindow = window.open(cashierUrl, "cashierWindow");
  if (cashierLoaded.value) {
    requestData();
  } else {
    window.addEventListener("message", receiveMessage, false);
  }
}

function receiveMessage(event) {
  if (event.origin !== cashierUrl) return;
  cashierLoaded.value = event.data === "__done__";
  requestData();
}

const data = { name: "下雪天的夏风" };
function requestData() {
  cashierWindow.postMessage(data, cashierUrl);
}
</script>

<template>
  <h1>manage</h1>
  <button @click="init">发送预览数据</button>
</template>

B页面收银台项目 cashier

<script setup>
const manageUrl = "http://localhost:5173";

if (window.opener) {
  window.opener.postMessage("__done__", manageUrl);
}

window.addEventListener(
  "message",
  function (event) {
    if (event.origin !== manageUrl) return;
    if (event.data) {
      console.log(event.data);
    }
  },
  false
);
</script>

<template>
  <h1>cashier</h1>
</template>

效果

在这里插入图片描述

其他的问题

1,页面路径需完全一致。

2个不同源页面通信时,要注意设置的 url 要完全一致才能接收到消息。例如 http://localhost:5174http://localhost:5174/ 是不一样的!

2,事件注册问题

看下面的代码

function init() {
  cashierWindow = window.open(cashierUrl, "cashierWindow");
  if (cashierLoaded.value) {
    requestData();
  } else {
    window.addEventListener("message", receiveMessage, false);
  }
}

function receiveMessage(event) {
  if (event.origin !== cashierUrl) return;
  cashierLoaded.value = event.data === "__done__";
  requestData();
}
  1. init 方法中,每次都要执行window.open 吗,不能把 cashierWindow 保存起来调用requestData 吗?

也可以这样做。但这个例子中是为了每次执行后,默认跳转到 B页面并刷新。

  1. init 方法中,每次都要注册message 事件吗,万一打开的B页面加载较慢,又返回到A页面再次点击发送数据,岂不是又会再次注册事件吗?

确实会再次注册事件,不过没关系,因为注册相同的事件监听器,多余的监听器会被移除,只保留一个。参考
只保留一个的前提是:事件回调函数不能是匿名函数,否则还是会注册多个!所以把 receiveMessage 提取出来了。

我们来验证下最终版代码的效果:

在这里插入图片描述

而如果监听 message 事件这样写,

window.addEventListener(
    "message",
    // function receiveMessage(event) { // 效果一样
    function(event) {
      if (event.origin !== cashierUrl) return;
      cashierLoaded.value = event.data === "__done__";
      requestData();
    },
    false
  );

再来看下效果:

在这里插入图片描述


以上。如果对你有帮助,可以点赞支持下!

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

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

相关文章

三分钟带你快速了解MongoDB是什么及其相关基础概念

文章目录 前言1. MongoDB简介2. 业务应用场景3. 体系结构4. 数据模型5. MongoDB的特点 总结 前言 为了巩固所学的知识&#xff0c;作者尝试着开始发布一些学习笔记类的博客&#xff0c;方便日后回顾。当然&#xff0c;如果能帮到一些萌新进行新技术的学习那也是极好的。作者菜…

SpringBoot第31讲:SpringBoot集成ShardingJDBC - Sharding-JDBC简介和基于MyBatis的单库分表

SpringBoot第31讲&#xff1a;SpringBoot集成ShardingJDBC - Sharding-JDBC简介和基于MyBatis的单库分表 本文是SpringBoot第31讲&#xff0c;主要介绍分表分库&#xff0c;以及SpringBoot集成基于ShardingJDBCMyBatis的单库分表实践 文章目录 SpringBoot第31讲&#xff1a;Spr…

微服务——操作索引库+文档操作+RestClient操作索引库和文档(java程序)

索引库操作 mapping属性 mapping是对文档的约束&#xff0c;常见约束属性包括: 创建索引库 #创建索引库 PUT /heima {"mappings": {"properties": {"info":{"type": "text","analyzer": "ik_smart"},…

K8s中的Controller

Controller的作用 &#xff08;1&#xff09;确保预期的pod副本数量 &#xff08;2&#xff09;无状态应用部署 &#xff08;3&#xff09;有状态应用部署 &#xff08;4&#xff09;确保所有的node运行同一个pod&#xff0c;一次性任务和定时任务 1.无状态和有状态 无状态&…

AIDL与HIDL核心概念

目录 一. 概述 二. 核心流程的核心理解 三. 一些术语 四. 参考样例 一. 概述 AIDL和HIDL都是主要用于跨进程通信&#xff0c;本质是Binder通信。 总体流程都是先写.aidl文件或.hal文件&#xff0c;这个文件只有接口定义哦不是实现&#xff0c;然后利用工具自动生成代码&a…

MapReduce基础原理、MR与MPP区别

MapReduce概述 MapReduce&#xff08;MR&#xff09;本质上是一种用于数据处理的编程模型&#xff1b;MapReduce用于海量数据的计算&#xff0c;HDFS用于海量数据的存储&#xff08;Hadoop Distributed File System&#xff0c;Hadoop分布式文件系统&#xff09;。Hadoop MapR…

数据库的约束 详解

一、约束的概述 1.概念:约束是作用于表中字段上的规则&#xff0c;用于限制存储在表中的数据。 2&#xff0e;目的:保证数据库中数据的正确、有效性和完整性。 3.分类: 约束描述关键字非空约束限制该字段的数据不能为nullNOT NULL唯一约束保证该字段的所有数据都是唯一、不…

uni-app uView自定义底部导航栏

因项目需要自定义底部导航栏&#xff0c;我把它写在了组件里&#xff0c;基于uView2框架写的&#xff08;vue2&#xff09;&#xff1b; 一、代码 在components下创建tabbar.vue文件&#xff0c;代码如下&#xff1a; <template><view><u-tabbar :value"c…

DALLE2论文解读及实现(一)

DALLE2: Hierarchical Text-Conditional Image Generation with CLIP Latents paper: https://cdn.openai.com/papers/dall-e-2.pdf github: https://github.com/lucidrains/DALLE2-pytorch DALLE2概览&#xff1a; - CLIP模型&#xff1a; 用于生成text embedding zt 和image …

CentOS 搭建 Harbor 镜像仓库(图文详解)

本文目录 1. 下载 Harbor 安装包2. 解压3. 修改配置文件4. 安装 Harbor5. 修改 docker 配置6. docker 登录方式7. 访问 Harbor Web 界面8. 创建证书9. 生成证书10. 更新配置11. 网页登录 说明&#xff1a;在搭建 Harbor 镜像仓库之前&#xff0c;虚拟机要先安装 docker 和 dock…

123.买卖股票的最佳时机3

目录 一、题目 二、分析代码 一、题目 123. 买卖股票的最佳时机 III - 力扣&#xff08;LeetCode&#xff09; 二、分析代码 class Solution { public:int maxProfit(vector<int>& prices) {//0表示没有操作//1表示第1次买入&#xff0c;2表示第1次卖出//3表示第2…

Blazor前后端框架Known-V1.2.10

V1.2.10 Known是基于C#和Blazor开发的前后端分离快速开发框架&#xff0c;开箱即用&#xff0c;跨平台&#xff0c;一处代码&#xff0c;多处运行。 Gitee&#xff1a; https://gitee.com/known/KnownGithub&#xff1a;https://github.com/known/Known 概述 基于C#和Blazo…

LeetCode 42. 接雨水(动态规划 / 单调栈)

题目&#xff1a; 链接&#xff1a;LeetCode 42. 接雨水 难度&#xff1a;困难 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2…

【Spring Cloud 六】Hystrix熔断

这里写目录标题 系列文章目录背景一、Hystrix是什么服务雪崩服务容错的相关概念熔断器降级超时控制限流 二、会什么要有Hystrix三、如何使用Hystrix进行熔断处理整体项目代码服务提供者pom文件yml配置文件启动类controller 服务消费者pom文件yml配置文件启动类feignhystrixcont…

python机器学习(七)决策树(下) 特征工程、字典特征、文本特征、决策树算法API、可视化、解决回归问题

决策树算法 特征工程-特征提取 特征提取就是将任意数据转换为可用于机器学习的数字特征。计算机无法直接识别字符串&#xff0c;将字符串转换为机器可以读懂的数字特征&#xff0c;才能让计算机理解该字符串(特征)表达的意义。 主要分为&#xff1a;字典特征提取(特征离散化)…

数据结构和算法——了解哈希表(哈希查找、散列的基本思想)

目录 哈希查找 散列的基本思想 例一 例二 哈希查找 我们之前学过的几种查找方法&#xff1a; 顺序查找 二分查找&#xff08;静态查找&#xff09; 二叉搜索树 h为二叉查找树的高度平衡二叉树 还有没有更快的查找方法呢&#xff1f; …

安卓:实现复制粘贴功能

目录 一、介绍 &#xff08;一&#xff09;ClipboardManager介绍 1、ClipboardManager常用方法&#xff1a; 2、获取 ClipboardManager实例 &#xff08;二&#xff09;、ClipData介绍 1、创建ClipData对象&#xff1a; 2、获取ClipData的信息&#xff1a; 3、ClipData…

原型链污染攻击

原型链污染攻击 prototype 和 _proto_是什么 JavaScript中的类的简历 在JavaScript中&#xff0c;我们如果要定义一个类&#xff0c;需要以定义“构造函数”的方式来定义&#xff1a; function Foo() {this.bar 1 }new Foo() 解析&#xff1a; Foo函数的内容&#xff0c;就…

【Redis】——AOF持久化

什么是AOF日志 AOF日志是redis为数据的持久化提供了的一个技术,日志里面记录着执行redis写命令。每当redis执行一条写命令的时候&#xff0c;就会将该命令记录 到AOF日志当中。当redis启动的时候&#xff0c;可以加载AOF日志中的所有指令&#xff0c;并执行这些指令恢复所有的…

RocketMQ基本概念和高级原理

基础概念 消息模型 RocketMQ 主要由 Producer、Broker、Consumer 三部分组成&#xff0c;其中 Producer 负责生产消息&#xff0c;Consumer 负责消费消息&#xff0c;Broker 负责存储消息。Broker 在实际部署过程中对应一台服务器&#xff0c;每个 Broker 可以存储多个 Topic…