资讯专栏INFORMATION COLUMN

messengerjs iframe 跨域传数据

tomorrowwu / 1822人阅读

摘要:刚来公司时做得第一个项目是跨部门合作,使用了来做通信,十分简单,代码不长,这里分析一下间通信的实现方式源码消息前缀建议使用自己的项目名避免多项目之间的冲突注意消息前缀应使用字符串类型类消息对象往发送消息出于安全考虑发送消息会带上前缀

刚来公司时做得第一个项目是跨部门合作,使用了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

本文转载自笔者个人博客:Gaoxuefeng"s Blog

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/79403.html

相关文章

  • messengerjs iframe 域传数据

    摘要:消息前缀建议使用自己的项目名避免多项目之间的冲突类消息对象往发送消息出于安全考虑发送消息会带上前缀以及现代浏览器支持兼容信使类添加一个消息对象初始化消息监听剥离消息前缀兼容监听消息广播消息用法页面页面跨域传数据跨域传高度 messengerjs /** * __ ___ *...

    qpwoeiru96 评论0 收藏0
  • 附实例!实现iframe父窗体与子窗体的通信

    摘要:小结小结本文主要是介绍了一个方案及其使用方法,来解决父窗体与子窗体的通信问题。同时提供了一个完整的实例,可以实现子窗体向父窗体传递消息,父窗体通过监听消息事件,来获取子窗体消息的目的。欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由前端林子发表于云+社区专栏 本文主要会介绍如何基于MessengerJS,实现iframe父窗体与子窗体间的通信,传递数据信息。同时本文会提...

    xiaochao 评论0 收藏0
  • 关于跨域简单总结

    摘要:开发者需要在中设置属性为跨域是的简称这是一种利用浏览器漏洞解决跨域的办法脚本元素可以不受浏览器同源策略的限制。 什么是浏览器同源策略? 同源是指,域名,协议,端口号均相同,如图: showImg(https://segmentfault.com/img/bV9rAO?w=1088&h=394); 注意:localhost和127.0.0.1虽然都指向本机,但也是跨域. 浏览器同源策略(...

    learning 评论0 收藏0
  • iframe跨域POST提交

    摘要:说到跨域,就不得不提起浏览器的同源策略。跨域无刷新提交跨域的方法有很多,像等等,由于项目中用到了进行跨域,所以本文主要总结一下如何利用进行无刷新提交。为了拿到返回回来的,需要使用一个函数,函数名后台已经告知。 以前在面试的时候经常遇到问关于跨域的事儿,所以自己对跨域有一定的概念性了解,知道什么是跨域以及解决跨域的方法,但是具体实际从来没有操作过,直到最近在公司项目中,遇到了一个需要使i...

    wwq0327 评论0 收藏0

发表评论

0条评论

tomorrowwu

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<