jsonp跨域的原理


1. 前言


jsonp是一種常用的跨域手段,和反向代理,服務端做跨域處理相比,jsonp更顯得方便輕巧,因而被大量用來處理跨域的請求,那么,這種請求方式到底是用了什么黑魔法,來解決令我們頭疼的跨域問題。

2. 原理


jsonp其實並沒有用到什么黑魔法,能達到跨域這種效果,無非是利用script標簽自身的跨域能力。我們知道,img,script,這種標簽如果有相應的src,那么便會發起一個htttp請求來請求相應的資源,如果有script標簽對應的路徑是一個js文件,那么在下載完畢這個js之后會馬上執行

<script type="text/javascript" src="www.somewhere.com/test.js"></script>
<!--此時會發起一個請求來獲取test.js,下載完成之后會立即執行test.js-->


假設我們需要從’www.localhost.com’發起一個獲取數據的請求’www.somewhere.com/getdata’,如果有我們以ajax來發起請求,那么由於瀏覽器同源保護策略的限制,該請求的返回值不會被瀏覽器所接受,這就是跨域問題。但是script這種標簽會發起一個get請求,並且這個請求是不受同源策略限制的,如果有我們將’www.somewhere.com/getdata’以script標簽來發送變成如下請求方式,那么是不是就不會有跨域問題了

 

<script type="text/javascript" src="www.somewhere.com/getdata"></script>
<!--需要這樣一個script來發起get請求-->




答案是肯定的,這也是jsonp跨域的原理。但是同時,這里也出現了兩個問題,第一,怎么使用script來發送請求,第二,請求得到的數據應該怎么在前端頁面上接收並處理。對於第一個問題,我們一般會將script標簽寫在html文檔中,當我們通常遇到的都會是動態請求,如果有我們還像原來一樣把標簽提前寫好在html中,那么瀏覽器解析文檔到這個script標簽時就會立即發起請求,等我們想要用到這些數據時,再去找前面加載好的數據,這樣顯然太費時費力,不太靈活,而且頁面上如果有很多請求,豈不是要提前些很多script標簽在頁面上,這樣頁面丑陋的根本沒法看了。我們需要的是在請求服務的時候,再發起請求,那么我們完全可以用動態標簽來實現,通多document.createElement來動態創建一個script標簽,然后為其設置src屬性,等請求完畢之后再將script標簽移除,那么第一個問題便迎刃而解了。

 

let script = document.createElement('script');
srcipt.src = 'www.somewhere.com/getdata';
document.querySelector('head').appendChild(script);




但同時,這也產生了第二個問題,我們怎么知道請求什么時候完畢,請求回來的數據要怎么處理,以及,請求完畢之后要怎么清除標簽。前面說到過,script標簽下載完畢之后會立即執行(async和defer暫時按下不表),而我們的請求通常會返回一個json對象,然而json直接執行是要報錯的,如{“name”: “柳輕侯”, “job”: “FE”}.如果花括號位於語句句首,那么花括號中的內容會被識別為一個語句塊,外層的花括號會被直接忽略,如果是{name: ‘柳輕侯’, job: ‘FE’}這種形式可能並不會報錯,因為即使忽略了花括號,name: ‘柳輕侯’也是一個合法的js語句,叫做標簽語句,但是如有給標簽語句加上引號,”name”: “柳輕侯”,這種形式卻並不合法,因為標簽語句不可以用引號引起來,然而json的key卻必須用雙引號引起來,所以直接返回一個json是不行的,必須返回一個可執行的js語句才行。而且一般來說我們需要請求結果來執行一些js邏輯操作,那么我們的操作邏輯要寫在哪里,怎么跟返回結果相結合呢?這時候就需要callback出場了。我們可以把請求結果”包裝”一下,將數據的處理邏輯寫到一個函數中,然后在script的結果中來調用這個函數,把需要的數據傳給這個函數,那么一切問題就都可以解決了。假設請求結果內容是{“name”: “柳輕侯”, “job”: “FE”},處理這個結果的函數叫callback

// 結果的處理,callback函數,必須在script請求之前就已經在頁面上聲明或賦值
function callback (data) {
console.log(data.name)
}
 
 
// 注意,script一定要返回一個js文件,文件內容是用回調函數將請求結果包裝起來,形成函數調用的形式
// 文件內容
callback({ "name": "柳輕侯", "job": "FE"})


但是如果頁面上有多jsonp請求,總不能所有的回調函數都叫callback吧,那么這時候就需要指定回調函數的名字,不同的jsonp請求調用不同的回調函數。可以通過script請求將函數名傳到服務端,然后服務端相應的將結果用此函數名包裝,然后返回到前端,這樣就可以按名稱調用了。我們將請求做以簡單的封裝。

function getJSONP (url, callback) {
let script = document.createElement('script');
script.type= "type="text/javascript;
srcipt.src = url + '?callback=' + callback;
document.querySelector('head').appendChild(script);
}


如果有這時候有兩個請求需要處理,”www.somewhere.com/getdata1”和”www.somewhere.com/getdata2”兩個請求需要處理,請求結果分別是{“name”: “柳輕侯”, “job”: “FE”}和{“name”: “天棠”, “job”: “fe”},處理函數分別是dealData1和dealData2,那我們該怎么處理?

 

function getJSONP (url, callback) {
let script = document.createElement('script');
script.type= "type="text/javascript;
srcipt.src = url + '?callback=' + callback;
document.querySelector('head').appendChild(script);
}
 
const dealData1 = function (data) {
console.log('這是getData1的回調:' + data.name);
}
const dealData2 = function (data) {
console.log('這是getData1的回調:' + data.name);
}
 
// 分別發送請求
getJSONP( 'www.somewhere.com/getdata1', 'dealData1'); // www.somewhere.com/getdata1?callback=dealData1
getJSONP( 'www.somewhere.com/getdata2', 'dealData2'); // www.somewhere.com/getdata1?callback=dealData2



// 請求結果分別是
dealData1({ "name": "柳輕侯", "job": "FE"})
dealData2({ "name": "天棠", "job": "fe"})
 
//執行結果
這是getData1的回調: 柳輕侯
這是getData2的回調: 天棠




這時候兩個結果會分別用傳過去的callback來包裝,然后輸出不同的結果,這時候我們的需求基本上被滿足了,最后還要處理的一點,每發一條請求,頁面上被憑空創建了一個script標簽,如果有請求很多,那么頁面上就會多出很多無意義的標簽(請求結束之后相應的標簽就失去了意義),所以我們需要在請求處理結束之后清除創建的script標簽。但是頁面上還有別的script標簽,必須只清除當前請求的jsonp生成的標簽,如果將其他的script標簽,可能就會造成其他的嚴重問題。由於每個jsonp的回調函數名稱不一樣,我們可以通過回調函數名來找出我們想要清除的script。

const dealData1 = function (data) {
console.log('這是getData1的回調:' + data.name);
// 處理完畢之后清除相應的script標簽
let callbackName = arguments.callee.name;
document.querySelector('script[src*="callback=' + callbackName + '"]').remove();
}

3. jsonp的缺點

    • 只能發送get請求。因為script只能發送get請求
    • 需要后台配合。此種請求方式應該前后端配合,將返回結果包裝成callback(result)的形式。


免責聲明!

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



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