上一篇文章 前端跨域(一):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
文件中,更改一下傳入參數,進行驗證。
(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中唯一需要前后端一起約定字段的地方。
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);
jsonp
可省略,或設置為false
,則查詢參數就不會出現在 URL 中了,但是回調函數的名稱需要前后端約定,因為無法從請求中獲取回調函數的名稱,后端只能將名稱寫死。
jsonpCallback
也可省略,jQuery 會自動生成一個隨機字符串作為函數名,可以減少不必要的命名工作。
4. 參考
5分鍾徹底明白 JSONP
Understanding JSONP