postMessage 跨越通信Demo

261 阅读4分钟

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- <script src="../build/postMessage.js"></script> -->
    <style>
        html, body{
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
            background: tan;
        }
        .wrapper{
            width: 100%;
            height: 100%;
            padding-top: 50px;
            box-sizing: border-box;
            position: relative;
        }
        .header{
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 50px;
            background: tomato;
            box-sizing: border-box;
            padding: 0 10px;
        }
        .header > button{
            margin-top: 10px;
        }
        .content{
            width: 100%;
            height: 100%;
        }
        .content iframe{
            width: 100%;
            height: 100%;
            overflow: auto;
            margin: -2px 0;
            background: white;
        }
    </style>
</head>
<body>
<div class="wrapper">
    <div class="header">
        <button onclick="handleSendMessage()">向下发起通知[emit]</button>
        接收的数据[on]: <span id="msg"></span>
    </div>
    <div class="content">
        <iframe src="./iframe.html" frameborder="0" id="iframe"></iframe>
    </div>
</div>
<script>
if (!Array.prototype.findIndex) {
    Object.defineProperty(Array.prototype, 'findIndex', {
        value: function (predicate) {
            // 1. Let O be ? ToObject(this value).
            if (this == null) {
                throw new TypeError('"this" is null or not defined');
            }

            var o = Object(this);

            // 2. Let len be ? ToLength(? Get(O, "length")).
            var len = o.length >>> 0;

            // 3. If IsCallable(predicate) is false, throw a TypeError exception.
            if (typeof predicate !== 'function') {
                throw new TypeError('predicate must be a function');
            }

            // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
            var thisArg = arguments[1];

            // 5. Let k be 0.
            var k = 0;

            // 6. Repeat, while k < len
            while (k < len) {
                // a. Let Pk be ! ToString(k).
                // b. Let kValue be ? Get(O, Pk).
                // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
                // d. If testResult is true, return k.
                var kValue = o[k];
                if (predicate.call(thisArg, kValue, k, o)) {
                    return k;
                }
                // e. Increase k by 1.
                k++;
            }

            // 7. Return -1.
            return -1;
        }
    });
}
const uuid = () => Math.random().toString(36).substr(2) + Date.now().toString(36)
const handleEmit = function ({ $request, params }) {
    debugger
    const eventPool = this.eventPool[$request]
    if (eventPool) {
        for (let i = 0; i< eventPool.length; i++) {
            if (eventPool[i].cb) eventPool[i].cb(params)
            if (eventPool[i].once) {
                eventPool.splice(i, 1)
                i--
            }
        }
    }
}

const listen = function () {
    const addEventListener = window.addEventListener || window.attachEvent
    const type = window.addEventListener ? 'message' : 'onmessage'
    addEventListener(type, (event) => {
        // 不处理非目标源请求
        if (event.origin !== this.targetOrigin && this.targetOrigin !== '*') return
        const { data: { $type, $request, params } } = event
        switch ($type) {
            case 'emit': {
                handleEmit.call(this, { $request, params })
                break
            }
            default:
        }
    }, false)
}

class PostMessage {
    constructor (targetOrigin = '*', targetWindow = window.parent) {
        debugger
        this.targetOrigin = targetOrigin
        this.targetWindow = () => typeof targetWindow === 'function' ? targetWindow() : targetWindow
        this.eventPool = {}
        listen.call(this)
    }

    emit (event, params) {
        const payload = { $request: event, $type: 'emit', params }
        this.targetWindow().postMessage(payload, this.targetOrigin)
    }

    on (event, cb) {
        const $uuid = uuid()
        if (!this.eventPool[event]) this.eventPool[event] = []
        if (cb) this.eventPool[event].push({ cb, $uuid })
        return $uuid
    }

    off (event, eventId) {
        if (!eventId) {
            delete this.eventPool[event]
        } else if (this.eventPool[event]) {
            const eventIndex = this.eventPool[event].findIndex(item => item.$uuid === eventId)
            if (eventIndex !== -1) this.eventPool[event].splice(eventIndex, 1)
        }
    }

    once (event, cb) {
        const $uuid = uuid()
        if (!this.eventPool[event]) this.eventPool[event] = []
        if (cb) this.eventPool[event].push({ cb, $uuid, once: true })
    }
}
</script>
<script>
    var innerWindow = document.querySelector('#iframe').contentWindow
    console.log(document.querySelector('#iframe'))
    console.log(innerWindow)
    var codeWrapper = document.querySelector('#msg')
    // 初始化
    var PM = new PostMessage('*', innerWindow)
    function handleSendMessage() {
        // 发起testSendToInnerWindow事件
        PM.emit('sendToInnerWindow', {
            msg: Math.random().toString(36).substr(3).toLocaleUpperCase()
        })
    }
    // 监听testSendToWrapper事件
    PM.on('sendToWrapper', function (data) {
        codeWrapper.innerHTML = data.text
    })
</script>
</body>
</html>
iframe.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- <script src="../build/postMessage.js"></script> -->
    <!-- <script src="../src/postMessage/index.js"></script> -->
    <style>
        .item{
            margin: 10px 0;
        }
    </style>
</head>
<body>
<br>
<h3>常用方法</h3>
<fieldset class="item">
    <legend>向上发起通知 [emit]</legend>
    <button onclick="handleSendMessage()">发起通知</button>
    发出的信息: <span id="sendMsg"></span>
</fieldset>
<fieldset class="item">
    <legend>接收通知 [on]</legend>
    接收的信息: <span id="receiveMsg"></span>
</fieldset>


<br>
<h3>非常用方法</h3>
<fieldset class="item">
    <legend>每次执行只会监听一次, 适合特殊场景 [once]</legend>
    <button onclick="startListenOnce()">开始监听</button>
    接收的信息: <span id="receiveMsgOnce"></span>
</fieldset>
<fieldset class="item">
    <legend>接收通知并可关闭监听 [off 有参]</legend>
    <button onclick="stopListen()">停止监听</button>
    接收的信息: <span id="receiveMsgAndStop"></span>
</fieldset>
<fieldset class="item">
    <legend>关闭全部'sendToInnerWindow'事件监听 [off 无参]</legend>
    <button onclick="stopAllListen()">停止全部监听</button>
</fieldset>
<script>
if (!Array.prototype.findIndex) {
    Object.defineProperty(Array.prototype, 'findIndex', {
        value: function (predicate) {
            // 1. Let O be ? ToObject(this value).
            if (this == null) {
                throw new TypeError('"this" is null or not defined');
            }

            var o = Object(this);

            // 2. Let len be ? ToLength(? Get(O, "length")).
            var len = o.length >>> 0;

            // 3. If IsCallable(predicate) is false, throw a TypeError exception.
            if (typeof predicate !== 'function') {
                throw new TypeError('predicate must be a function');
            }

            // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
            var thisArg = arguments[1];

            // 5. Let k be 0.
            var k = 0;

            // 6. Repeat, while k < len
            while (k < len) {
                // a. Let Pk be ! ToString(k).
                // b. Let kValue be ? Get(O, Pk).
                // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
                // d. If testResult is true, return k.
                var kValue = o[k];
                if (predicate.call(thisArg, kValue, k, o)) {
                    return k;
                }
                // e. Increase k by 1.
                k++;
            }

            // 7. Return -1.
            return -1;
        }
    });
}
const uuid = () => Math.random().toString(36).substr(2) + Date.now().toString(36)
const handleEmit = function ({ $request, params }) {
    debugger
    const eventPool = this.eventPool[$request]
    if (eventPool) {
        for (let i = 0; i< eventPool.length; i++) {
            if (eventPool[i].cb) eventPool[i].cb(params)
            if (eventPool[i].once) {
                eventPool.splice(i, 1)
                i--
            }
        }
    }
}

const listen = function () {
    debugger
    const addEventListener = window.addEventListener || window.attachEvent
    const type = window.addEventListener ? 'message' : 'onmessage'
    addEventListener(type, (event) => {
        // 不处理非目标源请求
        if (event.origin !== this.targetOrigin && this.targetOrigin !== '*') return
        const { data: { $type, $request, params } } = event
        switch ($type) {
            case 'emit': {
                handleEmit.call(this, { $request, params })
                break
            }
            default:
        }
    }, false)
}

class PostMessage {
    constructor (targetOrigin = '*', targetWindow = window.parent) {
        this.targetOrigin = targetOrigin
        this.targetWindow = () => typeof targetWindow === 'function' ? targetWindow() : targetWindow
        this.eventPool = {}
        listen.call(this)
    }

    emit (event, params) {
        debugger
        const payload = { $request: event, $type: 'emit', params }
        this.targetWindow().postMessage(payload, this.targetOrigin)
    }

    on (event, cb) {
        const $uuid = uuid()
        if (!this.eventPool[event]) this.eventPool[event] = []
        if (cb) this.eventPool[event].push({ cb, $uuid })
        return $uuid
    }

    off (event, eventId) {
        if (!eventId) {
            delete this.eventPool[event]
        } else if (this.eventPool[event]) {
            const eventIndex = this.eventPool[event].findIndex(item => item.$uuid === eventId)
            if (eventIndex !== -1) this.eventPool[event].splice(eventIndex, 1)
        }
    }

    once (event, cb) {
        const $uuid = uuid()
        if (!this.eventPool[event]) this.eventPool[event] = []
        if (cb) this.eventPool[event].push({ cb, $uuid, once: true })
    }
}
</script>
<script>
    // 初始化 默认参数为 ('*', window.parent)
    var PM = new PostMessage()
    console.log('postMessage' + PM)

    // 发送testSendToWrapper事件
    function handleSendMessage() {
        debugger    
        var info = Math.random().toString(36).substr(3).toLocaleUpperCase()
        PM.emit('sendToWrapper', {
            text: info
        })
        document.querySelector('#sendMsg').innerHTML = info
    }

    // sendToInnerWindow
    var receiveMsg = document.querySelector('#receiveMsg')
    PM.on('sendToInnerWindow', function(data) {
        receiveMsg.innerHTML = data.msg
    })


    // sendToInnerWindow, 可独立处理
    var receiveMsgAndStop = document.querySelector('#receiveMsgAndStop')
    var testSendToInnerWindowId = PM.on('sendToInnerWindow', function(data) {
        receiveMsgAndStop.innerHTML = data.msg
    })


    // 只监听一次
    var receiveMsgOnce = document.querySelector('#receiveMsgOnce')
    startListenOnce()
    function startListenOnce() {
        receiveMsgOnce.innerHTML = ''
        PM.once('sendToInnerWindow', function(data) {
            receiveMsgOnce.innerHTML = data.msg
        })
    }
    // 关闭监听sendToInnerWindow off有ID参数
    function stopListen() {
        PM.off('sendToInnerWindow', testSendToInnerWindowId)
    }
    // 关闭全部sendToInnerWindow监听 off无ID参数
    // !! 非sendToInnerWindow事件不受影响
    function stopAllListen() {
        PM.off('sendToInnerWindow')
    }
</script>
</body>
</html>