通用Iframe跨域通信库实现

前言

前端在页面嵌入其他站点的页面时,常常会遇到跨域通信的问题,浏览器为了安全问题,限制了不同域名的JS直接调用。

解决方案

浏览器再限制跨域调用的同时,也预留了一个比较安全的通道。那就是message通道。 只要得到一个页面的window对象,都可以调用其postMessage的api,而不会有任何限制。而希望接受到其他页面message的页面只需要侦听这一消息即可。
发送者代码示例

var parentWindow = window.parent; //假设这是一个放在iframe的页面,那么这样可以得到其父页面的window对象
parentWindow.postMessage({
           data: 'test'
 }, '*')

侦听者代码示例

window.addEventListener('message', function(event) {

}, false);

经过以上几行简单的代码,就完成一个基本的页面通信了。

写一个通用的通信库

接下来对以上的代码进行改良,使其成为一个通用的iframe通信库。

代理端 iframe-proxy.js的内容

var IFRAME_PROXY = {
    invoke: function(ns, params, callback, source, domain) {
        try {
            var arr = ns.split('.');
            var target = window;
            for (var i = 0; i < arr.length; i++) {
                var t = arr[i]
                target = target[t];
            }
        } catch (error) {
            console.log(ns + 'not found, check your method.');
            return;
        }
        params.success = function(res) {
            source.postMessage({
                'type': 'QN_PROXY_CALLBACK',
                'params': res,
                'callback': callback,
                'code': 200
            }, domain);
        }

        params.error = function(res) {
            source.postMessage({
                'type': 'IFRAME_PROXY_CALLBACK',
                'params': res,
                'callback': callback,
                'code': 999
            }, domain);

        };
        target.invoke(params);
    }
}

window.addEventListener('message', function(event) {
    //console.log('received response:  ', event.data);
    var data = event.data;
    var domain = event.origin;
    var source = event.source;
    switch (data.type) {
        case 'IFRAME_PROXY':
            IFRAME_PROXY.invoke(data.ns, data.params, data.callback, source, domain);
            break;
    }
}, false);

调用端iframe-proxy-client.js

var __sourceWin__ = null;
var __callbackMap__ = {};
window.addEventListener('message', function(event) {
    var data = event.data;
    switch (data.type) {
        case 'IFRAME_PROXY_CALLBACK':
            var fn = data.callback;
            var isSuccess = data.code == 200;
            if (isSuccess) {
                __callbackMap__[fn].success(data.params);
            } else {
                __callbackMap__[fn].error(data.params);
            }
            break;
    }

});

function ___noop() {};
var IFRAME_PROXY = {
    invoke: function(ns, params) {
        var callbackId = 'qnproxy' + new Date().getTime() + Math.floor(Math.random() * 100000) + Math.floor(Math.random() * 100000);
        __callbackMap__[callbackId] = {
            success: params.success || ___noop,
            error: params.error || ___noop
        }
        for (var o in params) {
            if (typeof params[o] == 'function') {
                delete(params[o])
            }
        }
        window.parent.postMessage({
            type: 'IFRAME_PROXY',
            ns: ns,
            params: params,
            callback: callbackId
        }, '*')
    }
}

如何使用

在iframe的的页面引入iframe-proxy-client.js,在父页面引入 iframe-proxy.js, 假设子页面需要调用父页面的方法, Util.getName('abc'),那么调用

IFRAME_PROXY.invoke('Util.getName','abc', function(res) {
  //res就是父页面调用的函数结果
})

经过以上几步,就实现了一个通用了IFRAME通信库。

上一篇:各版本React路由的跳转的方法


下一篇:Java 希尔排序