早前,项目中有个需求:其他平台需要携带数据跳转到我们平台,且自动打开弹窗并渲染所携带的数据。
我最初的方案选 localStorage,在其他平台跳转过来之前,将数据存入 localStorage,跳转到我们平台后,我会先判断 localStorage 中是否有指定的“键”(此处要求存入 localStorage 中的“键”要特殊且唯一),如果存在,我就将数据取出处理并打开弹窗渲染数。
// 其他平台跳转前将数据存入 localStorage
localStorage.setItem("popDataList",JSON.stringify(data))
// 跳转到我们平台,从 localStorage 取出数据
let popDataList = JSON.parse(localStorage.getItem("popDataList"))
if(popDataList){
// 存在则进行处理并打开弹窗渲染数据
}
// 关闭弹窗,清掉缓存,避免影响页面正常功能
localStorage.removeItem("popDataList")
最初与其他平台进行联调时,一切顺利。但是这两天出现了一个问题:在与某个平台进行联调时,发现在跳转前将数据存入 localStorage,但是跳转到我们平台后 localStorage 并没有数据,即存入失败。定位后发现他们平台的域名跟我们的不一样,即跨域了,而 localStorage 并不支持跨域,因此我们平台并没有相应的 localStorage。之前之所以能成功,是因为联调的平台域名相同,而对于不同的域名此方案就不适用了。
怎么办?只能进行二次优化了。很明显,我们需要选择一种支持跨域的方案,于是,我选择了 postMessage。
postMessage() 用于安全地实现跨源通信。
发送消息
使用语法如下:
otherWindow.postMessage(message, targetOrigin, [transfer]);
参数 | 说明 |
---|---|
otherWindow | 其他窗口的一个引用,比如 iframe 的 contentWindow 属性、执行 window.open 返回的窗口对象、或者是命名过或数值索引的 window.frames。 |
message | 将要发送到其他 window的数据。 |
targetOrigin | 指定哪些窗口能接收到消息事件,其值可以是 *(表示无限制)或者一个 URI。 |
transfer | 可选,是一串和 message 同时传递的 Transferable 对象。这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。 |
接收消息
接收程序有一个事件监听器,监听 "message" 事件,同时我们也可以验证消息来源地址,以确保是个可信的发送地址。
window.addEventListener('message', function (e) { // 监听 message 事件
if (e.origin !== "https://www.myTest.com") { // 验证消息来源地址
return;
}
// 取出数据
console.log(e.data)
});
项目二次优化
以下模拟真实项目场景
其他平台:http://localhost:8080
我们平台:http://localhost:8081
其他平台在跳转之前将数据通过 postMessage 发送出去
window.open("http://localhost:8081")
window.postMessage("postMessage发送数据!","http://localhost:8081")
我们平台在指定页面进行监听,获取数据
window.addEventListener("message",function(e){
console.log(e,'123')
alert(e.data)
})
当我以为 No problem,另一个问题出现了,我从其他平台跳到我们平台时候,效果如下:
控制台打印如下:
很明显,发送失败了,data 跟 origin 都不正确。于是我捉摸了一下,尝试修改代码
let popWindow = window.open("http://localhost:8081")
popWindow.postMessage("postMessage发送数据!","http://localhost:8081")
再次验证,结果与上面一样,我很好奇,为什么会这样?接收方没监听到指定的 message。
我在想,会不会是我们在使用 postMessage 发送数据的同时进行页面跳转,新开的页面此时监听并没有建立,于是监听不到,而当我们建立了监听之后,postMessage 动作已经过期了。
于是我尝试修改发送数据的代码,加上定时器延迟发送
let popWindow = window.open("http://localhost:8081")
setTimeout(() => {
popWindow.postMessage("postMessage发送数据!","http://localhost:8081")
},1000)
重新试了一下,有趣的事情发生了,提示框出现了两次。
第一次结果依旧与上面一致,提示跟打印都不正确
但是第二次拿到了发送的数据
控制台打印,data 跟 origin 都是正确的
加定时器能解决掉这个同时发送数据和打开新页面获取数据失败的问题,也验证了我的猜想。
到这里,我们的问题基本解决了,需求基本完成,当然也可以进一步优化,如验证消息来源地址,以确保是个可信的发送地址
window.addEventListener("message",function(e){
if(e.origin !== "http://localhost:8080"){
return
}
console.log(e,'123')
alert(e.data)
})
以上优化,既能确保来源可信,同时减少不必要的处理,如优化之后错误提示框
不会再弹出,因为从上面的错误打印信息来看,它的 origin 并不是我们指定,即来源不可信。
当然,你也可以在发送消息时减少定时器延长时间,缩短用户等待时间,确保良好体验
let popWindow = window.open("http://localhost:8081")
setTimeout(() => {
popWindow.postMessage("postMessage发送数据!","http://localhost:8081")
},200)
经过以上优化之后,需求目标达到,用户体验良好,奈斯。
最后附完整代码(很简单)
发送端
let popWindow = window.open("http://localhost:8081")
setTimeout(() => {
popWindow.postMessage("postMessage发送数据!","http://localhost:8081")
},200)
// 注意:必须使用定时器,发送的数据如果是对象类型,可以先使用 JSON.stringify() 进行序列化
接收端
window.addEventListener("message",function(e){
if(e.origin !== "http://localhost:8080"){ // 验证来源可信
return;
}
console.log(e.data)
// 注意:如果 e.data 是 JSON.stringify() 序列化后的数据,需要使用 JSON.parse() 进行还原
// 后续操作。。。
})