messengerjs iframe 跨域傳數據
剛來公司時做得第一個項目是跨部門合作,使用了MessengerJS來做通信,十分簡單,MessengerJS代碼不長,這里分析一下iframe間通信的實現方式
源碼
/**
* __ ___
* / |/ /___ _____ _____ ___ ____ ____ _ ___ _____
* / /|_/ // _ \ / ___// ___// _ \ / __ \ / __ `// _ \ / ___/
* / / / // __/(__ )(__ )/ __// / / // /_/ // __// /
* /_/ /_/ \___//____//____/ \___//_/ /_/ \__, / \___//_/
* /____/
*
* @description MessengerJS, a common cross-document communicate solution.
* @author biqing kwok
* @version 2.0
* @license release under MIT license
*/
window.Messenger = (function(){
// 消息前綴, 建議使用自己的項目名, 避免多項目之間的沖突
// !注意 消息前綴應使用字符串類型
var prefix = "[PROJECT_NAME]",
supportPostMessage = 'postMessage' in window;
// Target 類, 消息對象
function Target(target, name, prefix){
var errMsg = '';
if(arguments.length < 2){
errMsg = 'target error - target and name are both required';
} else if (typeof target != 'object'){
errMsg = 'target error - target itself must be window object';
} else if (typeof name != 'string'){
errMsg = 'target error - target name must be string type';
}
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");
}
};
}
// 信使類
// 創建Messenger實例時指定, 必須指定Messenger的名字, (可選)指定項目名, 以避免Mashup類應用中的沖突
// !注意: 父子頁面中projectName必須保持一致, 否則無法匹配
function Messenger(messengerName, projectName){
this.targets = {};
this.name = messengerName;
this.listenFunc = [];
this.prefix = projectName || prefix;
this.initListen();
}
// 添加一個消息對象
Messenger.prototype.addTarget = function(target, name){
var targetObj = new Target(target, name, this.prefix);
this.targets[name] = targetObj;
};
// 初始化消息監聽
Messenger.prototype.initListen = function(){
var self = this;
var generalCallback = function(msg){
if(typeof msg == 'object' && msg.data){
msg = msg.data;
}
var msgPairs = msg.split('__Messenger__');
var msg = msgPairs[1];
var pairs = msgPairs[0].split('|');
var prefix = pairs[0];
var name = pairs[1];
for(var i = 0; i < self.listenFunc.length; i++){
if (prefix + name === self.prefix + self.name) {
self.listenFunc[i](msg);
}
}
};
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.listen = function(callback){
var i = 0;
var len = this.listenFunc.length;
var cbIsExist = false;
for (; i < len; i++) {
if (this.listenFunc[i] == callback) {
cbIsExist = true;
break;
}
}
if (!cbIsExist) {
this.listenFunc.push(callback);
}
};
// 注銷監聽
Messenger.prototype.clear = function(){
this.listenFunc = [];
};
// 廣播消息
Messenger.prototype.send = function(msg){
var targets = this.targets,
target;
for(target in targets){
if(targets.hasOwnProperty(target)){
targets[target].send(msg);
}
}
};
return Messenger;
})();
下面主要分析代碼結構
supportPostMessage變量
用來檢測當前瀏覽器是否支持postMessage
postMessage是HTML5引入的通信API,它可以避開同源策略的限制,實現安全的跨域通信
向外界窗口發送消息
otherWindow.postMessage(message, targetOrigin);
-
otherWindow: 指目標窗口,也就是給哪個window發消息,是 window.frames 屬性的成員或者由 window.open 方法創建的窗口
-
message: 是要發送的消息,類型為 String、Object (IE8、9 不支持),一般使用json數據
-
targetOrigin: 是限定消息接收范圍,協議+主機+端口號[+URL],URL會被忽略,所以可以不寫,不限制請使用 ‘*’
接受信息的message事件
var onmessage = function (event) {
var data = event.data;
var origin = event.origin;
//do someing
};
if (typeof window.addEventListener != 'undefined') {
window.addEventListener('message', onmessage, false);
} else if (typeof window.attachEvent != 'undefined') {
//for ie
window.attachEvent('onmessage', onmessage);
}
注意:ie6/7不支持postMessage,因此在ie6/7中跨域通信通常使用window.name
window.name的美妙之處:name 值在不同的頁面(甚至不同域名)加載后依舊存在,并且可以支持非常長的 name 值(2MB)
window.navigator有與window.name類似的特性,而且可以保存回調方法
MessengerJS的實現思路是高級瀏覽器使用postMessage,不支持postMessage的使用window.navigator來保存回調方法
Target類
消息類,發送執行者
function Target(target, name){
this.target = target;
this.name = name;
}
Target.prototype.send = function(msg){
// 發送消息
};
Messenger類
信使類,創建多個消息對象,注冊多個監聽事件,每一個消息對象的廣播消息會被這個信使類下面的所有監聽事件接收到
function Messenger(messengerName, projectName){
this.targets = {};
this.name = messengerName;
this.listenFunc = [];
this.initListen();
}
// 添加一個消息對象
Messenger.prototype.addTarget = function(target, name){};
// 初始化消息監聽
Messenger.prototype.initListen = function(){};
// 監聽消息
Messenger.prototype.listen = function(callback){};
// 注銷監聽
Messenger.prototype.clear = function(){};
// 廣播消息
Messenger.prototype.send = function(msg){};
實現邏輯是:
-
initListen方法初始化,將generalCallback回調方法注冊到message監聽中
-
addTarget將消息對象添加到targets對象中
-
listen方法將監聽方法添加到listenFunc數組中
-
send方法執行每一個target對象的send方法
-
target對象的send方法執行,觸發了message監聽,觸發了generalCallback的執行,從而執行了listenFunc數組中的方法
在postMessage的注冊回調方法里加了一個回調方法組listenFunc
在postMessage的監聽觸發方法外加了一層集體觸發對象targets
從而達到了廣播的效果
postMessage本身可以實現廣播的效果,但是MessengerJS為了兼容,限制了postMessage的能力,自行實現了廣播
使用場景
MessengerJS來做iframe通信解決的最常見的問題是,在主頁面為iframe留足高度
parent頁面
var messenger = new Messenger('parent');
var iframe = document.getElementById('iframepage');
messenger.addTarget(iframe.contentWindow, 'iframe');
messenger.listen(function (msg) {
var result = parseInt(msg, 10) + 20;
if (result < mainWindowHeight) {
result = mainWindowHeight;
}
$('#iframepage').height(result);
});
iframe頁面
// iframe跨域傳數據
var messenger = new Messenger('iframe');
messenger.addTarget(window.parent, 'parent');
// 跨域傳main 高度
var height = $('.main').height();
messenger.targets['parent'].send(height);
messenger.listen(function (msg) {
});
總結
postMessage是一個用于安全的使用跨源通信的方法,幫助web開發回歸正軌
MessengerJS實現效果很好,即便做頻繁的交互,也不會有明顯的卡頓,不過時代在進步,以后可能會很少用到這樣的兼容了
來源: MessengerJS