上一篇文章 前端跨域(一):CORS 實現了跨域的一種解決方案,IE8 和其餘瀏覽器分別經過 XDomainRequest 和 XHR 對象原生支持 CORS。此次我將補一補 Web 服務中也很是流行的一種跨域技術——JSONP,同時,將複用上次的前端跨域場景。javascript
【簡單理解】:JSONP = 回調函數(Padding) + 數據(JSON),能夠將 Padding 理解爲回調函數,JSONP 則爲被包含在函數調用中的 JSON。html
callback({ "name": "Nicholas" });
【原理】:Ajax 的跨域受到「同源策略」的限制,可是像 <script>
及 <img>
標籤帶有 src
屬性,均可不受限制地其餘域中加載資源,JSONP 則是經過動態 <script>
元素來使用的。前端
【實現方式】:回調函數是當響應到來時應該在頁面中調用的函數,其名字通常在請求中指定。在請求完成後,即 JSONP 響應加載到頁面中之後,就會當即執行。java
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服務安全可靠。jquery
接下來讓咱們來一步一步實現 JSONP,從中咱們也將看到,JSONP 跟 AJAX 毫無關係。git
(1)首先,若是不使用 JSONP,實現一個正常的函數調用:建立 2 個 <script>
標籤,一個用來定義函數以便處理數據,另外一個則用來進行函數調用。github
(2)接着,咱們將第二個 <script>
標籤中的函數調用放到單獨的 js
文件中,更改一下傳入參數,進行驗證。web
(3)到此爲止, callback
函數是在本地的 js
文件中被調用的。如今,假設這個 js
文件在服務端,須要經過請求才能得到,則給 <script>
標籤指定相應的 src
便可。ajax
Server 端:json
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 並非一回事兒。
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 會自動生成一個隨機字符串做爲函數名,能夠減小沒必要要的命名工做。