跨文档通信解决方案

适用场景

  • 父窗口与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 //当前项目的项目名称
    }
  1. 实现监听消息

     /**
         * @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);

猜你喜欢

转载自blog.csdn.net/weixin_33768153/article/details/80732472
今日推荐