超好用的iframe的postMessage穿参

前言



跨域,简单来说是指不同域之间相互请求资源,例如AJAX请求,浏览器根据同源策略对响应结果进行拦截,这是浏览器对JavaScript实施的安全限制。所谓同源是指相同的域名、协议和端口,只要其中一项不同就为跨域

背景


最近在调用公司其他部门的iframe标签的 SDK脚本 出现了问题,

在web项目中通过iframe嵌入另一个第三方web项目,第三方web项目里点击某个按钮要实时调用web项目的全局函数打开某个全局弹窗或者进行路由跳转,这时候两个项目存在了数据交互,显然违反了同源策略,在HTML5标准引入的window对象下的postMessage方法,可以允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递


兼容性

1
可以看出,postMessage在各大主流浏览器中,除低版本IE浏览器外,其他浏览器的支持度良好

语法

具体介绍可戳这里前往MDN,这里通俗地解释一下每个参数

  • otherWindow.postMessage(message, targetOrigin, [transfer]);
  • otherWindow:目标窗口(你想发送跨域消息的那个窗口),例如:iframe.contentWindowmessage 将要发送的数据
  • targetOrigin 目标窗口的地址(URL),或者字符串’*'表示无限制、任何URL都允许发送
  • transfer:可选参数,高级用法,这里不作讨论,是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

使用

我们假设现在有两个不同源的页面,父页面地址:http://a.index.com,子页面地址:http://b.index.com,父页面通过iframe引入子页面

我们假设现在有两个不同源的页面,父页面地址:http://a.index.com,子页面地址:http://b.index.com,父页面通过iframe引入子页面

<!-- a.index.html -->
<h1>父页面</h1>
<iframe id="iframe" src="http://b.index.com"></iframe>

①父页面向子页面发送一条消息

const iFrame = document.getElementById('iframe')
<!-- 需要等到iframe中的子页面加载完成后才发送消息,否则子页面接收不到消息 -->
iFrame.onload = function(){
  <!-- iFrame.contentWindow获取到iframe的window对象 -->
  iFrame.contentWindow.postMessage('父页面发送的消息','http://b.index.com');
}


②子页面接收父页面的消息

// 有发送就有接收,与postMessage配套使用的就是message事件
window.addEventListener('message',e=>{
    <!-- 对消息来源origin做一下过滤,避免接收到非法域名的消息导致的xss攻击 -->
    if(e.origin==='http://a.index.com'){
        console.log(e.origin) //父页面URL,这里是http://a.index.com
        console.log(e.source) // 父页面window对象,全等于window.parent/window.top
        console.log(e.data)  //父页面发送的消息
    }
},false)

③子页面向父页面发送一条消息 有了前面的基础,我们这里其实只要记住otherWindow.postMessage中otherWindow为你要发送数据的目标窗口的window对象

window.parent.postMessage('子页面发送的消息','http://a.index.com')

④父页面接收子页面的消息 接收的通信逻辑父传子和子传父一样


window.addEventListener('message',e=>{
    <!-- 对消息来源origin做一下过滤,避免接收到非法域名的消息导致的xss攻击 -->
    if(e.origin==='http://b.index.com'){
        console.log(e.origin) //子页面URL,这里是http://b.index.com
        console.log(e.source) // 子页面window对象,全等于iframe.contentWindow
        console.log(e.data) //子页面发送的消息
    }
},false)

注意点

  • window.postMessage中的window指的是你想发送跨域消息的那个窗口(你需要通信的目标窗口),而不是自身窗口的window

    • 父页面中:父页面向子页面发送跨域信息,window就是在父页面中嵌入的iframe指向的子页面的
      window,即:iFrame.contentWindow子页面中:

    • 子页面想父页面发送跨域信息,window就是父页面的window,在这里因为子页面是嵌入到父页面中的,对于子页面来讲,window就是top或者parent

  • 需要等到iframe中的子页面加载完成后才发送消息,否则子页面接收不到消息

  • 在监听message事件时需要判断一下消息来源origin

案例

我在接入统一登录的时候,发现其他部门写的脚本,场景:导致反复退出登录,就会多次回调成功的方法,导致多次验证报错,原因:函数执行栈内存没有销毁,需要添加cleanup函数,在单页面离开的时候进行销毁

;(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define([], factory);
  } else if (typeof exports === 'object' && typeof module !== 'undefined') {
    // Node.js or CommonJS
    module.exports = factory();
  } else {
    // Browser globals (root is window)
    root.createLoginComp = factory();
  }
}(typeof self !== 'undefined' ? self : this, function () {
  // 模块内容开始
  console.log('SDK初始化');
  function createLoginComp({
    info,
    messageCallback,
    loginSuccessCallback
  }) {
    return new Promise((resolve, reject) => {
      console.log('SDK初始化Dom');
      const el = document.getElementById(info.boxId);
      if (!el) {
        reject(`找不到id为${info.boxId}的元素,登录组件创建失败`);
        return;
      }
      const iframe = document.createElement("iframe");
      const url = `XXXXX`;
      iframe.src = url;
      iframe.allowTransparency = "true";
      iframe.style.cssText = "min-height: 100%; min-width: 100%; border-radius: 18px; box-shadow: 0px 2px 10px 0px #EAEDF3; background: #FFFFFF; border: none;";
      iframe.id = `${info.boxId}-frame`;
      el.innerHTML = "";
      el.appendChild(iframe);

      let messageEventListener = handleMessage.bind(null, messageCallback, loginSuccessCallback);

      iframe.onload = () => {
        if (!iframe.contentWindow.postMessage || !window.postMessage || !window.addEventListener) {
          reject('浏览器不支持 postMessage,登录组件创建失败');
          return;
        }
        window.addEventListener("message", messageEventListener);
        resolve({
          cleanup: () => {
            window.removeEventListener("message", messageEventListener);
            el.removeChild(iframe); // 移除 iframe
          }
        });
      };
      iframe.onerror = () => {
        reject('iframe 加载失败,可能是网络错误或资源不存在');
        el.removeChild(iframe); // 如果 iframe 加载失败,同样需要移除它
      };
    });
  }

  function handleMessage(messageCallback, loginSuccessCallback, event) {
    if (event.data.type === 'loginSuccess') {
      loginSuccessCallback(event.data);
    } else if (['success', 'error', 'businessAgreement'].includes(event.data.type)) {
      messageCallback(event.data);
    }
  }

  function queryURLParams(attr) {
    let obj = {},
        self = window.location.href;
    self.replace(/#([^?&=#]+)/g, (_, $1) => (obj['HASH'] = $1));
    self.replace(/([^?&=#]+)=([^?&=#]+)/g, (_, $1, $2) => (obj[$1] = $2));
    return typeof attr !== 'undefined' ? obj[attr] : obj;
  }
  // 模块内容结束
  return createLoginComp;
}));

总结

iframe DOM 元素本身不太可能造成内存泄漏。然而,事件监听器(特别是通过 window.addEventListener 添加的监听器)如果没有在适当的时机移除,

确实可能会导致内存泄漏。通过返回一个移除事件监听器的函数,并在 Promise 解决时提供给调用者,是一个很好的做法来避免这种情况。

为了确保不会造成内存泄漏,需要确保在 iframe 或包含 iframe 的组件被移除、替换或不再需要时,调用这个返回的函数来清除事件监听器。
在使用这个 SDK 的页面或组件中,需要在适当的生命周期钩子或事件中调用这个清理函数。

例如,在一个单页应用中,如果用户导航离开了使用了这个 iframe 的页面,就应该调用清理函数。

以下是避免内存泄漏的建议:

  1. 调用清理函数:确保在组件卸载或页面销毁时,调用 resolve 方法返回的清理函数。

  2. 避免匿名函数:避免了使用匿名函数作为事件监听器,这是好的做法。匿名函数更难被正确移除,因为你需要精确相同的函数引用来移除监听器。

  3. 检查iframe的存在性:在移除事件监听器之前,检查 iframe 是否仍然存在于DOM中。如果 iframe 已经被移除,根据具体情况,监听器可能已经自动被清除。

使用iframe的contentWindow属性来添加和移除事件监听器:如果消息是在 iframe 内部发送和接收的,

考虑在 iframe 的 contentWindow 上添加和移除事件监听器,而不是全局的 window 对象上,这样当 iframe 被移除时,与之相关的监听器更有可能被垃圾回收。

总的来说,只要在不需要监听器或 iframe 被移除时,通过适当的机制移除了事件监听器,就可以大大减少内存泄漏的风险。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++11异常:到底是怎么个异常

目录​​​​​​​ 一、C/C如何处理错误 1.1C语言传统的处理错误的方式 1.2C异常概念 二、异常的使用 2.1异常的抛出和捕获 2.2try/catch的使用 2.3异常安全 2.4异常的重新抛出 2.5异常的规范 三、服务器开发中异常体系的模拟 一、C/C如何处理错误 1.1C语言传统的处…

SEO关键词布局时如何查找用户爱搜索的关键词?

在关键词布局中&#xff0c;确定完核心词后&#xff0c;就要考虑在网站关键词扩展时&#xff0c;找到用户爱搜索的词&#xff0c;只有在网站页面关键词布局时&#xff0c;布局用户爱搜索的词&#xff0c;才能够使用户在搜索时网站的页面能够有机会出现在用户的搜索结果页&#…

蓝桥杯算法题:栈(Stack)

这道题考的是递推动态规划&#xff0c;可能不是很难&#xff0c;不过这是自己第一次靠自己想出状态转移方程&#xff0c;所以纪念一下&#xff1a; 要做这些题目&#xff0c;首先要把题目中会出现什么状态给找出来&#xff0c;然后想想他们的状态可以通过什么操作转移&#xf…

个人成长秘籍:参加六西格玛绿带培训的好处

在当今竞争激烈的商业环境中&#xff0c;追求卓越与持续改进已成为企业和个人成功的关键。六西格玛绿带培训&#xff0c;作为一种全面提升管理技能和工作效率的培训课程&#xff0c;不仅帮助企业优化流程、提高质量和效率&#xff0c;也为个人职业发展开辟了一条光明大道。张驰…

cog predict docker unknown flag: --file

如图&#xff1a; 使用cog predict -i image“link-to-image” 出现docker unknown flag: --file的问题。 解决方法&#xff08;对我可行&#xff09;&#xff1a;切换cog版本。 这个是我一开始的cog安装命令&#xff08;大概是下的最新版&#xff1f;&#xff09;&#xff1…

cannal的使用

搭建MySQL 安装canal 1.新建文件夹logs, 新建文件canal.properties instance.properties docker.compose.yml instance.properties ################################################# ## mysql serverId , v1.0.26 will autoGen # canal.instance.mysql.slaveId0# enable g…

【话题:工作生活】2022年工作总结--疫情下的上海,疫情中的我。

现在是阳历2023年11月27日星期一&#xff0c;我再次开始撰写自己的年终工作总结。希望再过1、2个月&#xff0c;这份年终总结能够出炉&#xff0c;与大家相遇。 给自己定个小目标&#xff0c;年终的工作生活总结坚持写10年。我2017年毕业&#xff0c;之后就开始写每年的年终总结…

MathJax的基本使用

一、引言 MathJax引擎是一个开源的JavaScript库&#xff0c;它允许Web开发者在网页中嵌入高质量的数学公式。通过利用Web的最新技术&#xff0c;MathJax引擎可以解析LaTeX、MathML和AsciiMath等数学标记语言&#xff0c;并将其渲染为可视化的数学公式&#xff0c;这些公式可以…

【智能制造-1】涂胶解决方案

平面或立体转角处的等距点胶&#xff0c;既是技术上的难点也是实现上的痛点。 如何更好地保证拐角点胶的均匀性&#xff1f; 1、位置同步输出算法&#xff08;PSO&#xff09;&#xff1a;可以在点胶阀设定频率不变的情况下实现恒速等距点胶&#xff0c;完美解决拐角堆胶问题…

jsonpath在线解析器网址

jsonpath在线解析器网址&#xff1a;https://jsonpath.com/

梦想CAD 在线编辑软件

前言 有用户集成网页CAD之后&#xff0c;需要提取图纸的各种信息和数据&#xff0c;下面我们讲一下Web版CAD如何在前端直接提取修改和转换后的图纸信息&#xff0c;没有集成过在线CAD的小伙伴可以先看一下快速入门&#xff08;https://help.mxdraw.com/?pid32&#xff09; 在…

Vue2.x实现商城购物车

1.实现购物车页面 在页面中显示购物车中的商品信息&#xff0c;并能进行数量增减及商品删除操作&#xff0c;购物车中金额也随商品数量的变化而变化 2.创建cart.html页面 创建cart.html页面&#xff0c;在其中创建Vue实例&#xff0c;实例中首先准备一些商品信息以供显示&a…

逆向入门:为CTF国赛而战day05day06

用的汉化版的 昨天做了一道题目&#xff0c;然后下了那个apkide改之理,就没了 今天再来一题。 我发现&#xff1a;ascii表要好好学。这里#号是35就被写到题目里去了。 CTF reverse 不一样的flag_ctf reverse flag.bin-CSDN博客

云原生技术:开启你的数字王国

在科技领域的飞速进步中&#xff0c;云计算已经成为了现代企业和个人不可或缺的技术。在这股云计算的热潮中&#xff0c;"云原生"这一概念正逐步成为焦点。云原生的话题越来越普及&#xff0c;无论是在日常生活中还是在专业工作场合&#xff0c;这个术语都频繁出现。…

1. VirtualBox安装CentOS

安装 VirtualBox 地址:https://www.virtualbox.org/wiki/Downloads 版本: 6.1和7.0+版本都可以 安装: windows上安装需要admin权限,右键菜单选中 “Run as administrator” 安装 CentOS 6.10 地址:https://vault.centos.org/6.10/isos/x86_64/ 版本: 如果不需要GUI,选择…

无人新零售引领的创新浪潮

无人新零售引领的创新浪潮 在数字化时代加速演进的背景下&#xff0c;无人新零售作为商业领域的一股新兴力量&#xff0c;正以其独特的高效性和便捷性重塑着传统的购物模式&#xff0c;开辟了一条充满创新潜力的发展道路。 依托人脸识别、物联网等尖端技术&#xff0c;无人新…

8.排序(直接插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、归并排序)的模拟实现

1.排序的概念及其运用 1.1排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录…

node.js-入门

定义 Node.js是一个跨平台Javascript运行环境&#xff0c;使开发者可以搭建服务器端的Javascript应用程序 作用&#xff1a;使用Node.js编写服务器端程序 1&#xff09;编写数据接口&#xff0c;提供网页资源浏览功能等 2&#xff09;前端工程化&#xff1a;为后续学习Vue和…

DSP笔记8-通用GPIO

电源类 AD引脚类 系统相关JTAG 时钟 GPIO (general purpose input output)复用&#xff0c; 复用&#xff0c;I/O引脚&#xff0c;外设的功能引脚&#xff0c; 88个GPIO引脚&#xff0c;通用的输入输出口&#xff0c;功能复用的。 GPIO特点 输入电平与TTL电平兼容。>2.0V…

记一次IP访问MySQL失败多次被自动锁定导致无法连接问题,解决方法一条SQL足以。

&#x1f469;&#x1f3fd;‍&#x1f4bb;个人主页&#xff1a;阿木木AEcru &#x1f525; 系列专栏&#xff1a;《Docker容器化部署系列》 《Java每日面筋》 &#x1f4b9;每一次技术突破&#xff0c;都是对自我能力的挑战和超越。 前言 今天下午还在带着耳机摸鱼&#xff…