前端跨域(二):JSONP


上一篇文章 前端跨域(一):CORS 實現了跨域的一種解決方案,IE8 和其他瀏覽器分別通過 XDomainRequest 和 XHR 對象原生支持 CORS。這次我將補一補 Web 服務中也非常流行的一種跨域技術——JSONP,同時,將復用上次的前端跨域場景。

1. JSONP(JavaScript Object Notation with padding,填充式JSON/參數式JSON)

【簡單理解】:JSONP = 回調函數(Padding) + 數據(JSON),可以將 Padding 理解為回調函數,JSONP 則為被包含在函數調用中的 JSON。

callback({ "name": "Nicholas" });

【原理】:Ajax 的跨域受到“同源策略”的限制,但是像 <script><img> 標簽帶有 src 屬性,都可不受限制地其他域中加載資源,JSONP 則是通過動態 <script> 元素來使用的。

【實現方式】:回調函數是當響應到來時應該在頁面中調用的函數,其名字一般在請求中指定。在請求完成后,即 JSONP 響應加載到頁面中以后,就會立即執行。

function handleResponse(response) {
	alert(response.city);
}

var script = document.createElement('script');
// 指定回調函數的名字為 handleResponse
script.src = 'http://freegeoip.net/json/?callback=handleResponse';
document.body.insertBefore(script, document.body.firstChild);

【優點】:能夠直接訪問響應文本,支持在瀏覽器域服務器之間雙向通信。
【缺點】:JSONP 從其他域中加載代碼執行,存在安全隱患,因此,使用時需要保障web服務安全可靠。

2. 本地模擬 JSONP 跨域

接下來讓我們來一步一步實現 JSONP,從中我們也將看到,JSONP 跟 AJAX 毫無關系。

(1)首先,如果不使用 JSONP,實現一個正常的函數調用:創建 2 個 <script> 標簽,一個用來定義函數以便處理數據,另一個則用來進行函數調用。

通過進行函數調用

(2)接着,我們將第二個 <script> 標簽中的函數調用放到單獨的 js 文件中,更改一下傳入參數,進行驗證。

通過引入 js 文件進行函數調用

(3)到此為止, callback 函數是在本地的 js 文件中被調用的。現在,假設這個 js 文件在服務端,需要通過請求才能獲得,則給 <script> 標簽指定相應的 src 即可。

Server 端:

var http = require('http');

http.createServer(function(req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    // 發送字符串,等客戶端獲得響應時,即會調用該函數
    res.end("callback('This is the callback from server.')");
}).listen(8888);

通過請求服務調用回調函數

服務器寫死的回調函數名稱

(4)上面的回調函數是在服務端寫死的,而現實的情況,應該是客戶端以參數的形式將回調函數名稱傳遞給服務端,服務端獲取這個變量,從而進行調用。這樣,服務端就不用關心這個回調函數的名稱是否改變了,而且前端也可以自行定義回調函數的名稱。

OK,既然要傳參,就得約定參數 key 值,這也是JSONP中唯一需要前后端一起約定字段的地方

通過在請求URL中加上查詢參數實現JSONP

Server:

var http = require('http');
var url = require('url');

http.createServer(function(req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});

    // 解析 url 參數
    var params = url.parse(req.url, true).query;
    // jsonpCallback 為前后端約定的字段,用於獲取回調函數的名稱
    res.end(params.jsonpCallback + "('This is JSONP.')");
}).listen(8888);

回調函數通過查詢參數獲得

(5)最后一步:為了提高代碼的靈活性,實現 <script> 標簽動態插入

<script>
    // 定義回調函數
    var cb = function(data) {
        var oDiv = document.getElementById('content');
        oDiv.innerHTML = data;
    }

    var url = 'http://localhost:8888?jsonpCallback=cb';

    // 創建 script 標簽,並設置其 src 屬性
    var script = document.createElement('script');
    script.src = url;

    // 插入 body,此時調用開始
    document.body.appendChild(script);
</script>

瞧,整個過程,我們並沒有用到 XHR 對象,只是利用了 <script>src 屬性,因此 JSONP 與 AJAX 並不是一回事兒。

3. jQuery 實現 JSONP

jQuery 是前端經常使用的庫,因此有必要了解 JSONP 在 jQuery 中的使用方式。
jQuery 將其包含在 $.ajax() 中,其中用到的具體有3個參數:

dataType(默認:none) :預期服務器返回的數據類型('json', 'jsonp', 'xml', 'html', 'text')
jsonp(默認:“callback”): JSONP回調查詢參數的名稱,即前后端約定的字段名
jsonpCallback(默認:“jsonp{N}”) :全局JSONP回調函數的字符串(或返回的一個函數)名。設置該項能啟用瀏覽器的緩存。

Client:

var oDiv = document.getElementById('content');

// 定義回調函數
// 只是用於服務端獲取名稱,也可以自行實現,從而在 `success` 中進行調用
var cb = function() {};

$.ajax({
    url: 'http://localhost:8888',
    type: 'get',
    dataType: 'jsonp',  // 預期服務器返回的數據類型
    jsonp: 'A_callback',  // 指定回調查詢參數的名稱,即前后端約定的字段,默認為“callback"
    jsonpCallback: 'cb',  // 指定回調函數名稱
    cache: true,
    success: function(data) {   // jQuery 將 JSON 數據剝離出來,傳入 success 和 error
        console.log(data);  // 'This is JSONP realized by jQuery.'
        oDiv.innerHTML = data;
    }
});

Server:不變

var http = require('http');
var url = require('url');

http.createServer(function(req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});

    // 解析 url 參數
    var params = url.parse(req.url, true).query;
    // A_callback 為前后端約定的字段,用於獲取回調函數的名稱
    res.end(params.A_callback+ "('This is JSONP realized by jQuery.')");
}).listen(8888);

jQuery實現JSONP

jsonp 可省略,或設置為 false,則查詢參數就不會出現在 URL 中了,但是回調函數的名稱需要前后端約定,因為無法從請求中獲取回調函數的名稱,后端只能將名稱寫死。

jsonpCallback 也可省略,jQuery 會自動生成一個隨機字符串作為函數名,可以減少不必要的命名工作。

4. 參考

5分鍾徹底明白 JSONP
Understanding JSONP



免責聲明!

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



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