前言 ❝
跨域,简单来说是指不同域之间相互请求资源,例如AJAX请求,浏览器根据同源策略对响应结果进行拦截,这是浏览器对JavaScript实施的安全限制。所谓同源是指相同的域名、协议和端口,只要其中一项不同就为跨域
❞ 背景
最近在调用公司其他部门的iframe标签的 SDK脚本 出现了问题,
在web项目中通过iframe嵌入另一个第三方web项目,第三方web项目里点击某个按钮要实时调用web项目的全局函数打开某个全局弹窗或者进行路由跳转,这时候两个项目存在了数据交互,显然违反了同源策略,在HTML5标准引入的window对象下的postMessage方法,可以允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递
兼容性
可以看出,postMessage在各大主流浏览器中,除低版本IE浏览器外,其他浏览器的支持度良好
语法
具体介绍可戳这里前往MDN,这里通俗地解释一下每个参数
使用
我们假设现在有两个不同源的页面,父页面地址:,子页面地址:,父页面通过iframe引入子页面
我们假设现在有两个不同源的页面,父页面地址:,子页面地址:,父页面通过iframe引入子页面
<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)
注意点
需要等到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 的页面,就应该调用清理函数。
以下是避免内存泄漏的建议:
调用清理函数:确保在组件卸载或页面销毁时,调用 resolve 方法返回的清理函数。
避免匿名函数:避免了使用匿名函数作为事件监听器,这是好的做法。匿名函数更难被正确移除,因为你需要精确相同的函数引用来移除监听器。
检查iframe的存在性:在移除事件监听器之前,检查 iframe 是否仍然存在于DOM中。如果 iframe 已经被移除,根据具体情况,监听器可能已经自动被清除。
使用iframe的contentWindow属性来添加和移除事件监听器:如果消息是在 iframe 内部发送和接收的,
考虑在 iframe 的 contentWindow 上添加和移除事件监听器,而不是全局的 window 对象上,这样当 iframe 被移除时,与之相关的监听器更有可能被垃圾回收。
总的来说,只要在不需要监听器或 iframe 被移除时,通过适当的机制移除了事件监听器,就可以大大减少内存泄漏的风险。
小学生编程C++【第2课】数据类型浮点数、字符、长整型、数位分离
大家好,我是川游锅锅。今天来继续和大家分享一下我家娃学习C++编程的一些日常,这是第2次课,记录一下每节课学习的内容,这算是一种记录、备忘、分享的文章。
在这节C++课程中,学习了基本数据类型,包括浮点数、字符和长整型。通过这些数据类型的练习,我们掌握了如何使用double类型进行高精度计算,并应用printf()函数来输出结果,解决如圆的面积和电阻值等实际问题。此外,我们还进行了数位分离的练习,学习了如何通过取整和取余操作来反向输出三位数和四位数。
课程总结:
数据类型描述用途示例
int整型,用于存储整数。年龄、数量、分数等
float单精度浮点型,用于存储带小数的数字。身高、体重、价格等
double双精度浮点型,精度比 float 高,用于存储更精确的浮点数。科学计算中的数值、高精度计算等
char字符型,用于存储单个字符。字母、数字、符号等
bool布尔型,用于存储 值。判断条件的结果(true 或 false)
以下是课堂上的一些程序例子:
以上程序分别使用了常用的各种基本数据类型来演示它们的用途。
定义一个变量,并对变量赋值,并完成计算
定义和使用双精度浮点型变量
%.4lf是格式控制符,表示以双精度浮点数形式输出,并且保留 4 位小数。这条语句会依次输出直径d、周长c和面积s的值,且每个值都保留 4 位小数。
做一个特定计算(并联电阻计算)
声明了 3 个双精度小数变量r1、r2和R 。接着通过cin从键盘读取两个数存到r1和r2里。之后用这两个数计算出R的值,这里计算的是两个电阻并联后的总电阻值(1/(1/r1 + 1/r2)是并联电阻计算公式)。结果输出:用printf("%.2lf", R);把计算出的总电阻值R输出,并且保留两位小数。
用代码算球的体积,学会用 C++ 输入半径、算球体积,再按要求格式把体积数打印出来
数据处理:声明 3 个小数变量r、pi和V ,pi设为3.14。用cin从键盘读入球的半径存到r里。然后按球体积公式4/3 * π * r³算出体积,存到V里。
结果输出:用printf("%.5lf",V);把算好的体积V输出,保留 5 位小数 。
用 C++ 做除法运算并按要求输出结果 。
将一个三位数反向输出,例如输入 358358,反向输出 853853
重点练习:
拆位练习:掌握如何通过数学运算分离和重新组合数字的各个位。
double类型的应用:利用double类型进行高精度计算,并结合printf()函数进行输出。
实际应用:
圆的面积计算:使用double类型存储半径和面积,确保计算的精度。
电阻值计算:涉及浮点数运算,确保结果的准确性。
课堂及课后作业
#少儿编程##教育部竞赛白名单##科技特长生##四川教育##成都教育#