前端跨域通信的幾種方式


本文最初發表於博客園,並在GitHub上持續更新前端的系列文章。歡迎在GitHub上關注我,一起入門和進階前端。

以下是正文。

前言

前端通信類的問題,主要包括以下內容:

  • 1、什么是同源策略及限制

同源策略是一個概念,就一句話。有什么限制,就三句話。能說出來即可。

  • 2、前后端如何通信

如果你不准備,估計也就只能說出ajax。

  • 3、如何創建Ajax

Ajax在前后端通信中經常用到。做業務時,可以借助第三方的庫,比如vue框架里的庫、jQuery也有封裝好的方法。但如果讓你用原生的js去實現,該怎么做?

這就是考察你的動手能力,以及框架原理的掌握。如果能寫出來,可以體現出你的基本功。

  • 4、跨域通信的幾種方式

這部分非常重要。無非就是問你:什么是跨域、跨域有什么限制、跨域有幾種方式

下面分別講解。

同源策略的概念和具體限制

同源策略:限制從一個源加載的文檔或腳本如何與來自另一個源的資源進行交互。這是一個用於隔離潛在惡意文件的關鍵的安全機制。(來自MDN官方的解釋)

具體解釋:

(1)包括三個部分:協議、域名、端口(http協議的默認端口是80)。如果有任何一個部分不同,則不同,那就是跨域了。

(2)限制:這個源的文檔沒有權利去操作另一個源的文檔。這個限制體現在:(要記住)

  • Cookie、LocalStorage和IndexDB無法獲取。

  • 無法獲取和操作DOM。

  • 不能發送Ajax請求。我們要注意,Ajax只適合同源的通信。

前后端如何通信

主要有以下幾種方式:

  • Ajax:不支持跨域。

  • WebSocket:不受同源策略的限制,支持跨域。

  • CORS:不受同源策略的限制,支持跨域。一種新的通信協議標准。可以理解成是:同時支持同源和跨域的Ajax

如何創建Ajax

關於Ajax請求,可以看本人的基礎文章:Ajax入門和發送http請求

在回答 Ajax 的問題時,要回答以下幾個方面:

  • 1、XMLHttpRequest 的工作原理

  • 2、兼容性處理

XMLHttpRequest只有在高級瀏覽器中才支持。在回答問題時,這個兼容性問題不要忽略。

  • 3、事件的出發條件

  • 4、事件的觸發順序

XMLHttpRequest有很多觸發事件,每個事件是怎么觸發的。

發送 Ajax 請求的五個步驟(XMLHttpRequest的工作原理)

(1)創建XMLHttpRequest 對象。

(2)使用open方法設置請求的參數。open(method, url, 是否異步)。

(3)發送請求。

(4)注冊事件。 注冊onreadystatechange事件,狀態改變時就會調用。

如果要在數據完整請求回來的時候才調用,我們需要手動寫一些判斷的邏輯。

(5)獲取返回的數據,更新UI。

發送 get 請求和 post 請求

get請求舉例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<h1>Ajax 發送 get 請求</h1>
<input type="button" value="發送get_ajax請求" id='btnAjax'>

<script type="text/javascript">
    // 綁定點擊事件
    document.querySelector('#btnAjax').onclick = function () {
        // 發送ajax 請求 需要 五步

        // (1)創建異步對象
        var ajaxObj = new XMLHttpRequest();

        // (2)設置請求的參數。包括:請求的方法、請求的url。
        ajaxObj.open('get', '02-ajax.php');

        // (3)發送請求
        ajaxObj.send();

        //(4)注冊事件。 onreadystatechange事件,狀態改變時就會調用。
        //如果要在數據完整請求回來的時候才調用,我們需要手動寫一些判斷的邏輯。
        ajaxObj.onreadystatechange = function () {
            // 為了保證 數據 完整返回,我們一般會判斷 兩個值
            if (ajaxObj.readyState == 4 && ajaxObj.status == 200) {
                // 如果能夠進到這個判斷 說明 數據 完美的回來了,並且請求的頁面是存在的
                // 5.在注冊的事件中 獲取 返回的 內容 並修改頁面的顯示
                console.log('數據返回成功');

                // 數據是保存在 異步對象的 屬性中
                console.log(ajaxObj.responseText);

                // 修改頁面的顯示
                document.querySelector('h1').innerHTML = ajaxObj.responseText;
            }
        }
    }
</script>
</body>
</html>

post 請求舉例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<h1>Ajax 發送 get 請求</h1>
<input type="button" value="發送put_ajax請求" id='btnAjax'>
<script type="text/javascript">

    // 異步對象
    var xhr = new XMLHttpRequest();

    // 設置屬性
    xhr.open('post', '02.post.php');

    // 如果想要使用post提交數據,必須添加此行
    xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

    // 將數據通過send方法傳遞
    xhr.send('name=fox&age=18');

    // 發送並接受返回值
    xhr.onreadystatechange = function () {
        // 這步為判斷服務器是否正確響應
        if (xhr.readyState == 4 && xhr.status == 200) {
            alert(xhr.responseText);
        }
    };
</script>
</body>
</html>

onreadystatechange 事件

注冊 onreadystatechange 事件后,每當 readyState 屬性改變時,就會調用 onreadystatechange 函數。

readyState:(存有 XMLHttpRequest 的狀態。從 0 到 4 發生變化)

  • 0: 請求未初始化

  • 1: 服務器連接已建立

  • 2: 請求已接收

  • 3: 請求處理中

  • 4: 請求已完成,且響應已就緒

事件的觸發條件

事件的觸發順序

上圖的參考鏈接:

實際開發中用的 原生Ajax請求


    var util = {};

    //獲取 ajax 請求之后的json
    util.json = function (options) {

        var opt = {
            url: '',
            type: 'get',
            data: {},
            success: function () {
            },
            error: function () {
            },

        };
        util.extend(opt, options);
        if (opt.url) {
            //IE兼容性處理:瀏覽器特征檢查。檢查該瀏覽器是否存在XMLHttpRequest這個api,沒有的話,就用IE的api
            var xhr = XMLHttpRequest ? new XMLHttpRequest() : new window.ActiveXObject('Microsoft.XMLHTTP');

            var data = opt.data,
                url = opt.url,
                type = opt.type.toUpperCase();
            dataArr = [];
        }

        for (var key in data) {
            dataArr.push(key + '=' + data[key]);
        }

        if (type === 'GET') {
            url = url + '?' + dataArr.join('&');
            xhr.open(type, url.replace(/\?$/g, ''), true);
            xhr.send();
        }

        if (type === 'POST') {
            xhr.open(type, url, true);
            // 如果想要使用post提交數據,必須添加此行
            xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            xhr.send(dataArr.join('&'));
        }

        xhr.onload = function () {
            if (xhr.status === 200 || xhr.status === 304) { //304表示:用緩存即可。206表示獲取媒體資源的前面一部分
                var res;
                if (opt.success && opt.success instanceof Function) {
                    res = xhr.responseText;
                    if (typeof res === 'string') {
                        res = JSON.parse(res);  //將字符串轉成json
                        opt.success.call(xhr, res);
                    }
                }
            } else {
                if (opt.error && opt.error instanceof Function) {
                    opt.error.call(xhr, res);
                }
            }
        };
    }


Ajax 的推薦鏈接:https://segmentfault.com/a/1190000006669043

跨域通信的幾種方式

方式如下:

  • 1、JSONP

  • 2、WebSocket

  • 3、CORS

  • 4、Hash

  • 5、postMessage

上面這五種方式,在面試時,都要說出來。

1、JSONP

面試會問:JSONP的原理是什么?怎么實現的?

在CORS和postMessage以前,我們一直都是通過JSONP來做跨域通信的。

JSONP的原理:通過<script>標簽的異步加載來實現的。比如說,實際開發中,我們發現,head標簽里,可以通過<script>標簽的src,里面放url,加載很多在線的插件。這就是用到了JSONP。

JSONP的實現:

比如說,客戶端這樣寫:

    <script src="http://www.smyhvae.com/?data=name&callback=myjsonp"></script>

上面的src中,data=name是get請求的參數,myjsonp是和后台約定好的函數名。
服務器端這樣寫:

        myjsonp({
            data: {}

        })

於是,本地要求創建一個myjsonp 的全局函數,才能將返回的數據執行出來。

實際開發中,前端的JSONP是這樣實現的:

<script>

    var util = {};

    //定義方法:動態創建 script 標簽
    /**
     * [function 在頁面中注入js腳本]
     * @param  {[type]} url     [description]
     * @param  {[type]} charset [description]
     * @return {[type]}         [description]
     */
    util.createScript = function (url, charset) {
        var script = document.createElement('script');
        script.setAttribute('type', 'text/javascript');
        charset && script.setAttribute('charset', charset);
        script.setAttribute('src', url);
        script.async = true;
        return script;
    };


    /**
     * [function 處理jsonp]
     * @param  {[type]} url      [description]
     * @param  {[type]} onsucess [description]
     * @param  {[type]} onerror  [description]
     * @param  {[type]} charset  [description]
     * @return {[type]}          [description]
     */
    util.jsonp = function (url, onsuccess, onerror, charset) {
        var callbackName = util.getName('tt_player'); //事先約定好的 函數名
        window[callbackName] = function () {      //根據回調名稱注冊一個全局的函數
            if (onsuccess && util.isFunction(onsuccess)) {
                onsuccess(arguments[0]);
            }
        };
        var script = util.createScript(url + '&callback=' + callbackName, charset);   //動態創建一個script標簽
        script.onload = script.onreadystatechange = function () {   //監聽加載成功的事件,獲取數據
            if (!script.readyState || /loaded|complete/.test(script.readyState)) {
                script.onload = script.onreadystatechange = null;
                // 移除該script的 DOM 對象
                if (script.parentNode) {
                    script.parentNode.removeChild(script);
                }
                // 刪除函數或變量
                window[callbackName] = null;  //最后不要忘了刪除
            }
        };
        script.onerror = function () {
            if (onerror && util.isFunction(onerror)) {
                onerror();
            }
        };
        document.getElementsByTagName('head')[0].appendChild(script); //往html中增加這個標簽,目的是把請求發送出去
    };

</script>

2、WebSocket

WebSocket的用法如下:

    //

    var ws = new WebSocket('wss://echo.websocket.org'); //創建WebSocket的對象。參數可以是 ws 或 wss,后者表示加密。

    //把請求發出去
    ws.onopen = function (evt) {
        console.log('Connection open ...');
        ws.send('Hello WebSockets!');
    };


    //對方發消息過來時,我接收
    ws.onmessage = function (evt) {
        console.log('Received Message: ', evt.data);
        ws.close();
    };

    //關閉連接
    ws.onclose = function (evt) {
        console.log('Connection closed.');
    };

Websocket的推薦鏈接:http://www.ruanyifeng.com/blog/2017/05/websocket.html

3、CORS

CORS 可以理解成是既可以同步、也可以異步*的Ajax。

fetch 是一個比較新的API,用來實現CORS通信。用法如下:

      // url(必選),options(可選)
      fetch('/some/url/', {
          method: 'get',
      }).then(function (response) {  //類似於 ES6中的promise

      }).catch(function (err) {
        // 出錯了,等價於 then 的第二個參數,但這樣更好用更直觀
      });

推薦鏈接里有詳細的配置。

另外,如果面試官問:“CORS為什么支持跨域的通信?”

答案:跨域時,瀏覽器會攔截Ajax請求,並在http頭中加Origin。

4、Hash

url的#后面的內容就叫Hash。Hash的改變,頁面不會刷新。這就是用 Hash 做跨域通信的基本原理。

補充:url的?后面的內容叫Search。Search的改變,會導致頁面刷新,因此不能做跨域通信。

使用舉例:

場景:我的頁面 A 通過iframe或frame嵌入了跨域的頁面 B。

現在,我這個A頁面想給B頁面發消息,怎么操作呢?

(1)首先,在我的A頁面中:

    //偽代碼
    var B = document.getElementsByTagName('iframe');
    B.src = B.src + '#' + 'jsonString';  //我們可以把JS 對象,通過 JSON.stringify()方法轉成 json字符串,發給 B

(2)然后,在B頁面中:

    // B中的偽代碼
    window.onhashchange = function () {  //通過onhashchange方法監聽,url中的 hash 是否發生變化
        var data = window.location.hash;
    };

5、postMessage()方法

H5中新增的postMessage()方法,可以用來做跨域通信。既然是H5中新增的,那就一定要提到。

場景:窗口 A (http:A.com)向跨域的窗口 B (http:B.com)發送信息。步驟如下。

(1)在A窗口中操作如下:向B窗口發送數據:

	// 窗口A(http:A.com)向跨域的窗口B(http:B.com)發送信息
 	Bwindow.postMessage('data', 'http://B.com'); //這里強調的是B窗口里的window對象

(2)在B窗口中操作如下:

    // 在窗口B中監聽 message 事件
    Awindow.addEventListener('message', function (event) {   //這里強調的是A窗口里的window對象
        console.log(event.origin);  //獲取 :url。這里指:http://A.com
        console.log(event.source);  //獲取:A window對象
        console.log(event.data);    //獲取傳過來的數據
    }, false);

我的公眾號

想學習代碼之外的軟技能?不妨關注我的微信公眾號:生命團隊(id:vitateam)。

掃一掃,你將發現另一個全新的世界,而這將是一場美麗的意外:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM