Ajax - 手寫JSONP跨域實現及原理詳解

JSONP實現原理

jsonp,其實就是單純爲了實現跨域請求而創造的一個欺騙(trick)。

雖然,因爲同源策略的影響,不能通過XMLHttpRequest請求不同域上的數據(Cross -origin reads)。但是,在頁面上引入不同域上的js腳本文件卻是可以的(Cross -origin embedding)。因此,在js文件載入完畢之後,觸發回調,可以將需要的data作爲參數傳入
注意,實現方式(需前後端配合)

 

優點

兼容性好(兼容低版本IE)

缺點

JSONP只支持GET請求,XMLHttpRequest相對於JSONP有着更好的錯誤處理機制。

 

實現

1.服務端採用的是Node,服務端處理請求方法如下

router.get('/', function(req, res, next) {
    console.log('收到客戶端的請求:', req.query);
    // 傳回到客戶端的數據
    let data = JSON.stringify({
        'status':200,
        'result':{
            'name':'柳成蔭',
            'site':'123456'
        }
    });
    // 獲取方法名稱 - 這是客戶端傳過來的方法名參數
    // 因爲這個方法名必須是客戶端有的,必須要客戶端告訴服務端是哪個方法
    let methodName = req.query.callback;
    let methodStr = methodName + '(' + data + ')';
    // 快速結束沒有任何數據的響應,客戶端會執行這個方法,從而獲取到服務端返回的數據
    res.end(methodStr)
});

2.封裝JSONP

(function (w) {
        /**
         * jsonp的實現
         * @param {Object}option
         */
        function jsonp(option) {
            // 把success函數掛載在全局的getDate函數上
            w.getData = option.success;
            // 處理url,拼接參數 - 回調方法是getData
            option.url = option.url + '?callback=getData';
            // 創建script標籤,並插入body
            let scriptEle = document.createElement('script');
            scriptEle.src = option.url;
            document.body.appendChild(scriptEle);
        }
        // 全局掛載一個jsonp函數
        w.jsonp = jsonp;
    })(window);

    /**
     * 把對象轉換成拼接字符串
     * 把形如
       data:{
          "sex":"男",
          "name":"九月"
        }
       轉換成sex=男&name=九月
     * @param paramObj 對象參數
     * @param words
     * @returns {string} 字符串
     */
    function getStrWithObject(paramObj,words){
        let resArr = [];
        // 1.轉換對象
        for(let key in paramObj){
            let str = key + '=' + paramObj[key];
            resArr.push(str);
        }
        resArr.push(words);
        // 3.數組轉換成字符串
        return resArr.join("&");
    }

看似代碼沒有任何問題。但是,我們測試一下,多次調用jsonp方法。

jsonp({
        url:'http://localhost:3000/',
        data:{
            "sex":"男",
            "name":"九月"
        },
        success:function (data) {
            console.log(data);
            alert(1);
        }
    });

    jsonp({
        url:'http://localhost:3000/',
        data:{
            "sex":"男",
            "name":"九月"
        },
        success:function (data) {
            console.log(data);
            alert(2);
        }
    });

結果會怎麼樣?的確,還是會執行2次,但是每次執行的都是第二個jsonp方法。這是爲什麼?

問題出現在這裏,多次調用,會發生函數覆蓋。

解決方案就是讓每一次調用函數名不一致,在JS裏有很多方法,比如通過隨機數、時間戳之類的。這裏採用的是隨機數,修改如下。

// 0.產生不同的函數名 - 解決了調用多次請求,造成覆蓋的問題
// 如果方法名相同,調用多次,都會執行最後一個,出現覆蓋現象
let callBackName = 'lcy' + Math.random().toString().substr(2) 
+ Math.random().toString().substr(2);
// 把success函數掛載在全局的getDate函數上
w[callBackName] = option.success;
// 處理url,拼接參數
option.url = option.url + '?' + getStrWithObject(option.data,'callback='+callBackName);

 好,現在看似完美解決。然而,我們發現,每次調用jsonp方法,都會在body裏插入一個script標籤。我們並不希望在調用jsonp之後,添加這樣一個標籤,我們需要在調用完之後,將其移除。

怎麼做呢?我們在將success函數掛載在全局時,我們在success外層再套個函數。在這個函數裏調用success方法之後,即已經請求到數據之後,我們就去把這個script標籤給它移除就行了。修改如下

// 1.函數掛載在全局
w[callBackName] = function(data){
    option.success(data);
    // 刪除script標籤
    document.body.removeChild(scriptEle);
};

整個過程就是這樣,以下是完整代碼。

 (function (w) {
        /**
         * jsonp的實現
         * @param {Object}option
         */
        function jsonp(option) {
            // 0.產生不同的函數名 - 解決了調用多次請求,造成覆蓋的問題
            // 如果方法名相同,調用多次,都會執行最後一個,出現覆蓋現象
            let callBackName = 'lcy' + Math.random().toString().substr(2) + Math.random().toString().substr(2);
            // 1.函數掛載在全局
            w[callBackName] = function(data){
                option.success(data);
                // 刪除script標籤
                document.body.removeChild(scriptEle);
            };
            // 2.處理url
            option.url = option.url + '?' + getStrWithObject(option.data,'callback='+callBackName);
            // 3.創建script標籤插入body
            let scriptEle = document.createElement('script');
            scriptEle.src = option.url;
            document.body.appendChild(scriptEle);
        }
        w.jsonp = jsonp;
    })(window);

    /**
     * 把對象轉換成拼接字符串
     * @param paramObj 對象參數
     * @returns {string} 字符串
     */
    function getStrWithObject(paramObj,words){
        let resArr = [];
        // 1.轉換對象
        for(let key in paramObj){
            let str = key + '=' + paramObj[key];
            resArr.push(str);
        }
        resArr.push(words);
        // 3.數組轉換成字符串
        return resArr.join("&");
    }

 

發佈了74 篇原創文章 · 獲贊 13 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章