适用场景
父窗口与iframe之间通信
多个iframe之间通信
上述的情况,都需要确保对不同域的页面有修改权限,并且同时加载脚本
IE不持支跨窗口通信
常见的跨域问题
- 跨子域
- 跨全域
- 跨协议(HTTP与HTTPS)
设计思路
消息都使用字符串的形式存在,可以看作是一个”报文”, 我们使用一个对象(Messenger)发送和接受报文。
Messenger
主要分为发送消息(send
)与监听(listen
) ,消息都是字符串,如果消息是一个复杂的结构化对象,我们可以使用一个JSON.stringify
将其改编成字符串,然后收到消息以后使用JSON.parse
解析成原来的数据结构对象
开始实现
- 创建target类
/**
* @desc 消息对象
* @param {Object} target 一个window对象, 发送消息的时候用到这个对象
* @param {String} name 我们添加对象的名字,后期访问调用
* @param {String} prefix [假如有多个项目,又不希望相互又冲突的时候使用]
*/
function Target (target, name, prefix) {
var errMsg = ''
if (arguments.length < 2) {
errMsg = 'target error - name 属性不能为空';
} else if (typeof target != 'object') {
errMsg = 'target error - target 必须是window对象'
} else if (typeof name != 'string') {
errMsg = 'target error - name 必须是一个字符串'
}
//如果有错误,直接返回一个错误独享
if (errMsg) {
throw new Error(errMsg)
}
// 进行赋值
this.target = target
this.name = name
this.prefix = prefix
}
- 创建
Messange
类
/**
* @desc 信使类,主要负责消息的传递
* @param {String} messengerName [注册当前页面的信使名字]
* @param {[String]} projectName [当前项目名称,可以为空]
* !注意:父子页面中的projectName必须保持一致,否则无法匹配
*/
function Messenger (messengerName, projectName) {
this.targets = {} //保存监听对象
this.name = messengerName //当前对象的名字
this.listenFunc = [] //监听函的事件池
this.prefix = projectName || prefix //当前项目的项目名称
}
实现监听消息
/** * @desc 事件监听函数 * @param {Function} callback [事件的调函数] * @return {[object]} [当前的执行主体] */ Messenger.prototype.listen = function (callback) { var i = 0, len = this.listenFunc.length, cbIsExist = false; //循环判断看事件池是否已经有当前回调函数,防止多次(绑定)调用同一个函数 for (; i < len; i++) { if(this.listenFunc[i] == callback) { cbIsExist = true break; } } //如果在事件池没有找到当前函数,则将callback添加到事件池 if (!cbIsExist) { this.listenFunc.push(callback) } return this }
2.实现注销和广播消息
// 往 target 发送消息, 出于安全考虑, 发送消息会带上前缀
if ( supportPostMessage ){
// IE8+ 以及现代浏览器支持
Target.prototype.send = function(msg){
this.target.postMessage(this.prefix + '|' + this.name + '__Messenger__' + msg, '*');
};
} else {
// 兼容IE 6/7
Target.prototype.send = function(msg){
var targetFunc = window.navigator[this.prefix + this.name];
if ( typeof targetFunc == 'function' ) {
targetFunc(this.prefix + msg, window);
} else {
throw new Error("target callback function is not defined");
}
};
}
// 注销监听
Messenger.prototype.clear = function(){
this.listenFunc = []
}
/**
* @desc 消息发送函数
* @param {String} msg 我们需要广播的消息
* @return {none} [none]
*/
Messenger.prototype.send = function (msg) {
var targets = this.targets,
target;
//便利所有的消息对象
for (target in targets) {
//判断如果有当前的消息对象,就发送消息
if (targets.hasOwnProperty(target)) targets[target].send(msg)
}
}
3.实现初始化监听函数
/**
* @desc 初始化监听函数
* @return {none} [none]
*/
Messenger.prototype.initListen = function () {
var self = this;
// 事件执行函数,在这里会遍历当前监听的所有targets,然后拿到相应的函数调用
var generalCallback = function (msg) {
// 首先判断msg是不是一个对象,是的话拿到data重新赋值给msg
if (typeof msg == 'object' && msg.data) {
msg = msg.data
}
//在这两个i分割字符串,拿到我们的需要的内容
var msgPairs = msg.split('__Messenger__'),
msg = msgPairs[1],
pairs = msgPairs[0].split('|'),
prefix = pairs[0],
name = pairs[1]
//循环当前事件池,判断如果prefix + name == self.prefix + self.name, 则调用当前fn
for (var i = 0; i < self.listenFunc.length; i++) {
if (prefix + name == self.prefix + self.name) {
self.listenFunc[i](msg)
}
}
}
// soupportPostMessage = 'postMessage' in window
// 判断浏览器是否支持PostMessage
if (soupportPostMessage) {
if('addEventListener' in document) {
window.addEventListener('message', generalCallback, false)
} else if ('attachEvent' in document) {
window.attachEvent('onmessage', generalCallback)
}
} else {
// 兼容IE 6/7
window.navigator[this.prefix + this.name] = generalCallback;
}
}
- 完成封装
window.Messenger = (function(){
// 消息前缀, 建议使用自己的项目名, 避免多项目之间的冲突
// !注意 消息前缀应使用字符串类型
var prefix = "[PROJECT_NAME]",
supportPostMessage = 'postMessage' in window;
/**
* @desc 消息对象
* @param {Object} target 一个window对象, 发送消息的时候用到这个对象
* @param {String} name 我们添加对象的名字,后期访问调用
* @param {String} prefix [假如有多个项目,又不希望相互又冲突的时候使用]
*/
function Target (target, name, prefix) {
var errMsg = ''
if (arguments.length < 2) {
errMsg = 'target error - name 属性不能为空';
} else if (typeof target != 'object') {
errMsg = 'target error - target 必须是window对象'
} else if (typeof name != 'string') {
errMsg = 'target error - name 必须是一个字符串'
}
//如果有错误,直接返回一个错误独享
if (errMsg) {
throw new Error(errMsg)
}
// 进行赋值
this.target = target
this.name = name
this.prefix = prefix
}
// 往 target 发送消息, 出于安全考虑, 发送消息会带上前缀
if (supportPostMessage){
// IE8+ 以及现代浏览器支持
Target.prototype.send = function(msg){
this.target.postMessage(this.prefix + '|' + this.name + '__Messenger__' + msg, '*');
};
} else {
// 兼容IE 6/7
Target.prototype.send = function(msg){
var targetFunc = window.navigator[this.prefix + this.name];
if ( typeof targetFunc == 'function' ) {
targetFunc(this.prefix + msg, window);
} else {
throw new Error("target callback function is not defined");
}
};
}
/**
* @desc 信使类,主要负责消息的传递
* @param {String} messengerName [注册当前页面的信使名字]
* @param {[String]} projectName [当前项目名称,可以为空]
* !注意:父子页面中的projectName必须保持一致,否则无法匹配
*/
function Messenger (messengerName, projectName) {
this.targets = {} //保存监听对象
this.name = messengerName //当前对象的名字
this.listenFunc = [] //监听函的事件池
this.prefix = projectName || prefix //当前项目的项目名称
this.initListen();
}
/**
* @desc 事件监听函数
* @param {Function} callback [事件的调函数]
* @return {[object]} [当前的执行主体]
*/
Messenger.prototype.listen = function (callback) {
var i = 0,
len = this.listenFunc.length,
cbIsExist = false;
//循环判断看事件池是否已经有当前回调函数,防止多次(绑定)调用同一个函数
for (; i < len; i++) {
if(this.listenFunc[i] == callback) {
cbIsExist = true
break;
}
}
//如果在事件池没有找到当前函数,则将callback添加到事件池
if (!cbIsExist) {
this.listenFunc.push(callback)
}
return this
}
// 添加一个消息对象
Messenger.prototype.addTarget = function(target, name){
var targetObj = new Target(target, name, this.prefix);
this.targets[name] = targetObj;
};
/**
* @desc 初始化监听函数
* @return {none} [none]
*/
Messenger.prototype.initListen = function () {
var self = this;
// 事件执行函数,在这里会遍历当前监听的所有targets,然后拿到相应的函数调用
var generalCallback = function (msg) {
// 首先判断msg是不是一个对象,是的话拿到data重新赋值给msg
if (typeof msg == 'object' && msg.data) {
msg = msg.data
}
//在这两个i分割字符串,拿到我们的需要的内容
var msgPairs = msg.split('__Messenger__'),
msg = msgPairs[1],
pairs = msgPairs[0].split('|'),
prefix = pairs[0],
name = pairs[1]
//循环当前事件池,判断如果prefix + name == self.prefix + self.name, 则调用当前fn
for (var i = 0; i < self.listenFunc.length; i++) {
if (prefix + name == self.prefix + self.name) {
self.listenFunc[i](msg)
}
}
}
// soupportPostMessage = 'postMessage' in window
// 判断浏览器是否支持PostMessage
if (supportPostMessage) {
if('addEventListener' in document) {
window.addEventListener('message', generalCallback, false)
} else if ('attachEvent' in document) {
window.attachEvent('onmessage', generalCallback)
}
} else {
// 兼容IE 6/7
window.navigator[this.prefix + this.name] = generalCallback;
}
}
// 注销监听
Messenger.prototype.clear = function(){
this.listenFunc = []
}
/**
* @desc 消息发送函数
* @param {String} msg 我们需要广播的消息
* @return {none} [none]
*/
Messenger.prototype.send = function (msg) {
var targets = this.targets,
target;
//便利所有的消息对象
for (target in targets) {
//判断如果有当前的消息对象,就发送消息
if (targets.hasOwnProperty(target)) targets[target].send(msg)
}
}
return Messenger;
})();
调用方式
- 初始化
/ 父窗口中 - 初始化Messenger对象
// 推荐指定项目名称, 避免Mashup类应用中, 多个开发商之间的冲突
var messenger = new Messenger('Parent', 'aff');
// iframe中 - 初始化Messenger对象
// 注意! Messenger之间必须保持项目名称一致, 否则无法匹配通信
var messenger = new Messenger('iframe1', 'aff');
// 多个iframe, 使用不同的名字
var messenger = new Messenger('iframe2', 'aff');
- 添加事件监听
// 监听消息
// 回调函数按照监听的顺序执行
messenger.listen(function(msg){
console.log("收到消息: " + msg);
});
- 添加消息对象
// 父窗口中 - 添加消息对象, 明确告诉父窗口iframe的window引用与名字
messenger.addTarget(iframe1.contentWindow, 'iframe1');
// 父窗口中 - 可以添加多个消息对象
messenger.addTarget(iframe2.contentWindow, 'iframe2');
- 发送消息
// 父窗口中 - 向单个iframe发消息
var msg = "I'm the test message"
messenger.targets['iframe1'].send(msg1);
// 父窗口中 - 向所有目标iframe广播消息
messenger.send(msg);