messengerjs iframe 跨域傳數據

剛來公司時作得第一個項目是跨部門合做,使用了MessengerJS來作通訊,十分簡單,MessengerJS代碼不長,這裏分析一下iframe間通訊的實現方式javascript

源碼java

/**
 *     __  ___
 *    /  |/  /___   _____ _____ ___   ____   ____ _ ___   _____
 *   / /|_/ // _ \ / ___// ___// _ \ / __ \ / __ `// _ \ / ___/
 *  / /  / //  __/(__  )(__  )/  __// / / // /_/ //  __// /
 * /_/  /_/ \___//____//____/ \___//_/ /_/ \__, / \___//_/
 *                                        /____/
 *
 * @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;
})();

下面主要分析代碼結構git

supportPostMessage變量

用來檢測當前瀏覽器是否支持postMessagegithub

postMessage是HTML5引入的通訊API,它能夠避開同源策略的限制,實現安全的跨域通訊web

向外界窗口發送消息json

otherWindow.postMessage(message, targetOrigin);
  • otherWindow: 指目標窗口,也就是給哪一個window發消息,是 window.frames 屬性的成員或者由 window.open 方法建立的窗口跨域

  • message: 是要發送的消息,類型爲 String、Object (IE八、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

本文轉載自筆者我的博客:Gaoxuefeng's Blog

相關文章
相關標籤/搜索