前言
出于浏览器的同源策略,运行在同一浏览器中的框架、标签页、窗口间的通信都受到了严格的限制。只有同源的文档才可以通信。这个安全策略虽然防止恶意网站与其他内容交互,但是也让制作有多个数据来源的聚合应用变得很困难。
使用场景
在工作中碰到这样一个问题,需要新建一个vue项目,然后多页面打包,把打包之后的页面通过iframe的方式嵌入到一个旧的项目里,并且要让这个旧项目中的页面和嵌入的页面进行消息传收。
这个时候问题来了,如何让两个项目的页面之前进行通信。
通过一番查资料,发现了window.postmessage这个方法可以实现我们的需求。
那么window.postmessage是什么呢?
window.postmessage是浏览器厂商和标准制定机构引入一种新功能:跨文档消息通信。 它使跨源通信成为一种安全的方法。window.postMessage 通过安全可靠的方式实现了对这个限制的突破,很方便的实现了跨文档消息通信。
postMessage(data,origin)方法接受两个参数:
1.data:
html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()方法对对象参数序列化,在低版本IE中引用json2.js可以实现类似效果。
2.origin:
指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,所以可以不写,这个参数是为了安全考虑,postMessage()方法只会将message传递给指定窗口,当然如果愿意也可以建参数设置为"*",这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
如想实现父页面和嵌入到它里面的iframe之前的通信,可以这样写:
父页面:
window.addEventListener('message',function(e){
console.log(e)
})
iframe页:
window.parent.postMessage({data:'params'},'*')
为了方便传递消息,统一接收的数据格式,个人封装了一个cPostMsg方法,代码如下:
const cPostMsg={
_tool:{
cIndexOf:function(data,obj,last){
//功能: 兼容低版本的indexOf方法;
//参数: data是要查找的数据,可以是数组,也可以是字符串;obj是需要匹配的字符串;last为true时,从后面开始匹配
//返回: 查找到的开始索引,没找到返回 数值:-1
if(typeof(data)==="string"){
if(last){
return data.lastIndexOf(obj)
}else{
return data.indexOf(obj)
}
}else if(!last&&Array.prototype.indexOf){
return data.indexOf(obj)
}else if(!last){
for(var i=0; i<data.length; i++){
if(data[i]==obj){
return i;
}
}
}else if(Array.prototype.lastIndexOf){
return data.lastIndexOf(obj)
}else{
var len=data.length;
for(var i=len;i>=0;i--){
if(data[i]===obj){
return len-i;
}
}
}
return -1;
},
getRootPath:function (noProjectName) {
//功能: 获取web根路径;
var curWwwPath = window.document.location.href; //获取当前网址,如: http://localhost:8083/uimcardprj/share/meun.jsp
var pathName = window.document.location.pathname; //获取主机地址之后的目录,如: uimcardprj/share/meun.jsp
var pos = cPostMsg._tool.cIndexOf(curWwwPath,pathName);
var localhostPaht = curWwwPath.substring(0, pos); //获取主机地址,如: http://localhost:8083
var projectName = pathName.substring(0, cPostMsg._tool.cIndexOf(pathName.substr(1),"/") + 1); //获取带"/"的项目名,如:/uimcardprj
if(noProjectName){
projectName=""
}
return (localhostPaht + projectName);
}
},
sendMsg:function(op){
//功能: 发送信息;
//参数说明:op={"win":目标win对象,默认为window.top,"url":发送源URL,不提供url将自动通过_tool.getRootPath获取,"data":你要发送的数据,"code":数据标识,"iframeId":如果不提供win,也可以提供iframe的Id}
if(!(op&&typeof(op)=="object")){
console.error("参数错误,参考:{win:目标win对象,默认为window.top,url:发送源URL,不提供url将自动通过_tool.getRootPath获取,data:你要发送的数据,code:数据标识,iframeId:如果不提供win,也可以提供iframe的Id}")
return
}
var myWin=op["win"]?op["win"]:window.top
var iframeId=op["iframeId"]
if(iframeId){
var iframeDom=document.getElementById(iframeId)
if(iframeDom){
myWin=iframeDom.contentWindow
iframeDom=null
}else{
myWin=window
}
}
var myUrl=op["url"]?op["url"]:cPostMsg._tool.getRootPath(true)
var msg=op["data"]
var myCode=op["code"]?op["code"]:"_cpm_"
try{
if(myCode){
msg={"__code__":myCode,"msg":msg}
}
myWin.postMessage(msg,myUrl)
}
catch(err){
console.log(err)
}
},
onMsg:function(op){
//功能: 信息监听;
//参数说明:op={callback:接收到数据后触发回调函数,"url":针对来源是否屏蔽信息,默认是本地地址,"code":数据标识}
if(!(op&&typeof(op)=="object")){
console.error("参数错误,参考:{callback:接收到数据后触发回调函数,url:针对来源是否屏蔽信息,默认是本地地址,code:数据标识}")
return
}
var myCode=op["code"]?op["code"]:"_cpm_"
var callback=op["callback"]
var myUrl=op["url"]?op["url"]:cPostMsg._tool.getRootPath(true)
if(!callback){
console.error("需要回调参数")
return
}
window.addEventListener("message", function(e){
if(myUrl=='all'||e.origin==myUrl){ //判断来源url(e.origin) 是否是被允许的
if(myCode){
if(e.data&&typeof(e.data)=="object"&&e.data["__code__"]==myCode){ //仅带有指定数据标识的才触发回调
callback(e)
}
}else{
callback(e)
}
}
}, true);
}
}
export default cPostMsg;
使用方法
1.发送消息示例:
cPostMsg.sendMsg({
data:{
name:"张三"
},
iframeId:"Iframe"//指定要发送给哪个iframe
})
2.接收消息示例:
cPostMsg.onMsg({
callback:function(e){
console.log(' 收到的消息是:',e.data.msg.name)
}
})