window.postMessage 跨域調用方法

父子頁面之間的通訊,如果同源的通過 window對象直接調用父(子)中的方法與對象(全局方法與對象)。如果出現跨域的,多系統情況下這種就被同源策略禁止掉了。因此需要使用其他方法解決。可以使用 window.postMessage 方法,附上技術規範 https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage

vue 等工程也同樣適用

案例

由於 postMessage 是單向的調用,是沒有回調概念的,下面的案例自行封裝了一套代碼,父頁面發送請求後,子頁面處理完成後即可回調函數,而不需要手動聲明。

parent.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>parent.html</title>
</head>
<body>
<span>parent.html</span>
<br>
<br>
<br>
<iframe src="./child.html"></iframe>
<br>
<br>
<button onclick="callChild()">調用子頁面</button>
</body>

<script>

    let callChild = function () {
        window._postMessage("childFunction", "my is params", function (data) {
            alert("parent: callChild childFunction(), return "+data);
            console.log("p:" + data);
        })
    }

    /**
     * 支持直接對子頁面進行調用,而且子頁面處理完成會回調 _callback 方法
     * @param _method iframe中的函數
     * @param _params 參數
     * @param _callback 調用 _method 成功後返回的數據
     * @private
     */
    window._postMessage = function (_method, _params, _callback) {

        let messageBody = {
            method: _method,
            params: _params,
        }

        if (_callback != undefined) {
            let proxyMethod = "proxy" + parseInt(Math.random() * 1000);
            //用於回調當前頁面的方法使用
            messageBody.returnMethod = proxyMethod;
            window[proxyMethod] = function (_p) {
                try {
                    _callback(_p);
                } finally {
                    delete window[proxyMethod];
                }
            }
        }

        //TODO 這裏的對象需要按需求修改
        let iframeWin = document.getElementsByTagName("iframe")[0].contentWindow;
        iframeWin.postMessage(messageBody, '*');

    }

    /**
     * 監聽其他地方調用的對象信息,由於回調
     */
    window.addEventListener("message", function (event) {
        let data = event.data;
        if (null == data || data.method == undefined) {
            return;
        }
        //判斷是調用者還是被調用者的數據返回(存在的話就意味着是別人調用你,需要執行完成本地方法後執行回調)
        if (data.returnMethod != undefined) {
            let returnData = window._callActualMethod(data.method, data.params);
            //回調
            window._postMessage(data.returnMethod, returnData);
        } else {
            //這裏是直接調用自身的方法(前面調用的時候已經將此方法丟到window裏面了這裏回調就可以調用到 window[proxyMethod] 方法)
            window._callActualMethod(data.method, data.params);
        }
    });

    window._callActualMethod = function (_method, _params) {
        // console.log(data.method);
        try {
            //這裏可以去實現具體調用的方法是在 vue 作用域還是其他作用域中的函數
            return window[_method](_params);
        } catch (e) {
            console.warn(e);
        }
    }

    window.parentFunction = function () {
        return "parent";
    }


</script>
</html>

child.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>child.html</title>
</head>
<body style="background-color: red">

<span>child.html</span>
<br>
<br>
<button onclick="callParent()">調用父頁面</button>

</body>

<script>

    var callParent = function () {
        window._postMessage("parentFunction", "my is params", function (data) {
            alert("child: callParent parentFunction(), return "+data);
            console.log("c:" + data);
        })
    }

    //支持直接對子頁面進行調用,而且子頁面處理完成會回調 _callback 方法
    window._postMessage = function (_method, _params, _callback) {

        let messageBody = {
            method: _method,
            params: _params,
        }

        if (_callback != undefined) {
            let proxyMethod = "proxy" + parseInt(Math.random() * 1000);
            //用於回調當前頁面的方法使用
            messageBody.returnMethod = proxyMethod;
            window[proxyMethod] = function (_p) {
                try {
                    _callback(_p);
                } finally {
                    delete window[proxyMethod];
                }
            }
        }

        //TODO 這裏的對象需要按需求修改
        let iframeWin = window.parent;
        iframeWin.postMessage(messageBody, '*');

    }

    /**
     * 監聽其他地方調用的對象信息,由於回調
     */
    window.addEventListener("message", function (event) {
        let data = event.data;
        if (null == data || data.method == undefined) {
            return;
        }
        //判斷是調用者還是被調用者的數據返回(存在的話就意味着是別人調用你,需要執行完成本地方法後執行回調)
        if (data.returnMethod != undefined) {
            let returnData = window._callActualMethod(data.method, data.params);
            //回調
            window._postMessage(data.returnMethod, returnData);
        } else {
            //這裏是直接調用自身的方法(前面調用的時候已經將此方法丟到window裏面了這裏回調就可以調用到 window[proxyMethod] 方法)
            window._callActualMethod(data.method, data.params);
        }
    });

    window._callActualMethod = function (_method, _params) {
        // console.log(data.method);
        try {
            //這裏可以去實現具體調用的方法是在 vue 作用域還是其他作用域中的函數
            return window[_method](_params);
        } catch (e) {
            console.warn(e);
        }
    }


    window.childFunction = function () {
        return "child";
    }

</script>
</html>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章