到底什么是JSONP启涯?它和JSON到底有什么關(guān)系?這是我碰到JSONP時腦海里的浮現(xiàn)的第一個問題。先看看維基百科上是怎么解釋的:
JSONP(JSON with Padding)是數(shù)據(jù)格式JSON的一種“使用模式”逝嚎,可以讓網(wǎng)頁從別的網(wǎng)域要數(shù)據(jù)扁瓢。
并不知道在說什么,JSON with Padding补君?也太抽象了引几。。挽铁。既然說“可以讓網(wǎng)頁從別的網(wǎng)域要數(shù)據(jù)”伟桅,那就是說JSONP的使用場景是需要跨域的,先試著碼一碼吧叽掘。
假設(shè)我有一個賬戶楣铁,每當(dāng)我點擊付款按鈕,就會從賬戶中扣款1元更扁。
發(fā)送請求有兩種方案:
通過<img>標(biāo)簽造get請求
? ? ? ? ?button.addEventListener('click', (e)=>{
? ? ? ? ?let image = document.createElement('img')
? ? ? ? ? image.src = '/pay'
? ? ? ? ? image.onload = function(){ // 狀態(tài)碼是 200~299 則表示成功
? ? ? ? ? alert('成功')
? ? ?}
? ? ? ? ?image.onload = function(){ // 狀態(tài)碼大于等于 400 則表示失敗
? ? ? ? ? alert('失敗')
? ? ?}
})
通過<script>標(biāo)簽造get請求
? ? ? ? ? ? button.addEventListener('click', (e)=>{
? ? ? ? ? ? let script = document.createElement('script')
? ? ? ? ? ? script.src = '/pay'
? ? ? ? ? ? ?document.body.appendChild(script)
? ? ? ? ? ? ?script.onload = function(e){ // 狀態(tài)碼是 200~299 則表示成功
? ? ? ? ? ? ? e.currentTarget.remove()
? ? ? }
? ? ? ? ? ? ? ? script.onload = function(e){ // 狀態(tài)碼大于等于 400 則表示失敗
? ? ? ? ? ? ? ? ?e.currentTarget.remove()
? ? ? ?}
})
顯然盖腕,<script>標(biāo)簽更具有可行性,畢竟你得需要有一張真的圖片來騙過瀏覽器浓镜,僅僅用來發(fā)送請求也太不合算了溃列。
先寫一個服務(wù)器來模擬一下所需環(huán)境,當(dāng)然我還需要一個數(shù)據(jù)庫膛薛,在當(dāng)前目錄下創(chuàng)建听隐。(這個數(shù)據(jù)庫先用來模擬第三方網(wǎng)址)
touch db
后端代碼長這樣:
...
? ? ? ? ? ? ? ?if(path === '/pay'){ //如果用戶點擊付款會向這個路徑發(fā)送請求
? ? ? ? ? ? ? ? let amount = fs.readFileSync('./db', 'utf8')
? ? ? ? ? ? ? ? amount -= 1
? ? ? ? ? ? ? ? ?fs.writeFileSync('./db', amount)
? ? ? ? ? ? ? ? ?response.setHeader('Content-Type', 'application/javascript')
? ? ? ? ? ? ? ? ?response.write('amount.innerText = ' + amount)
? ? ? ? ? ? ? ? ?response.end()
}
...
上面代碼,當(dāng)我點擊付款按鈕哄啄,會將我數(shù)據(jù)庫中記錄的余額減1雅任,然后再將當(dāng)前余額顯示在頁面。
這種技術(shù)叫做 SRJ - Server Rendered JavaScript
所以咨跌,利用<script>標(biāo)簽沒有跨域限制的“漏洞”沪么,可以與第三方網(wǎng)址進行通信。也就是維基百科所說的“讓網(wǎng)頁從別的網(wǎng)域要數(shù)據(jù)”虑润。標(biāo)簽沒有跨域限制的“漏洞”成玫,可以與第三方網(wǎng)址進行通信。也就是維基百科所說的“讓網(wǎng)頁從別的網(wǎng)域要數(shù)據(jù)”拳喻。
等等,通信剛剛實現(xiàn)了猪腕,“要數(shù)據(jù)”怎么實現(xiàn)冗澈?接著碼。陋葡。亚亲。
既然<script>標(biāo)簽沒有跨域限制,那么我只需指定src到任何我想要請求數(shù)據(jù)的路徑就可以了。標(biāo)簽沒有跨域限制捌归,那么我只需指定src到任何我想要請求數(shù)據(jù)的路徑就可以了肛响。
第三個方案:JSONP
先設(shè)定一個場景:
請求方:aaa.com 的前端程序員(瀏覽器)
響應(yīng)方:bbb.com 的后端程序員(服務(wù)器)
1.請求創(chuàng)建 script,src 指向響應(yīng)方惜索,同時傳一個查詢參數(shù)?callbackName=yyy
2.響應(yīng)方根據(jù)查詢參數(shù)callbackName特笋,構(gòu)造形如
i. yyy.call(undefied,'你要的數(shù)據(jù)')
ii. yyy('你要的數(shù)據(jù)')
這樣的響應(yīng)
3.瀏覽器接收到響應(yīng),就會執(zhí)行yyy.call(undefined,'你要的數(shù)據(jù)')
4.那么請求方就知道了他要的數(shù)據(jù)
這樣應(yīng)該就可以拿到我想要的數(shù)據(jù)了巾兆。
那么前端代碼長這樣:
? ? ? ? ?button.addEventListener('click', (e)=>{
? ? ? ? ?let script = document.createElement('script')
? ? ? ? ?let functionName = 'yyy'+ parseInt(Math.random()*10000000 ,10)
? ? ? ? ?window[functionName] = function(){? // 每次請求之前搞出一個隨機的函數(shù)
? ? ? ? ?amount.innerText = amount.innerText - 0 - 1
}
? ? ? ? ? script.src = '/pay?callback=' + functionName
? ? ? ? ? document.body.appendChild(script)
? ? ? ? ? script.onload = function(e){ // 狀態(tài)碼是 200~299 則表示成功
? ? ? ? ? ?e.currentTarget.remove()
? ? ? ? ? ?delete window[functionName] // 請求完了就干掉這個隨機函數(shù)
?}
? ? ? ? ? ? ?script.onload = function(e){ // 狀態(tài)碼大于等于 400 則表示失敗
? ? ? ? ? ? ? e.currentTarget.remove()
? ? ? ? ? ? ? delete window[functionName] // 請求完了就干掉這個隨機函數(shù)
? ? ? ?}
?})
后端代碼長這樣:
...
? ? ? ? if (path === '/pay'){
? ? ? ? let amount = fs.readFileSync('./db', 'utf8')
? ? ? ? amount -= 1
? ? ? ? fs.writeFileSync('./db', amount)
? ? ? ? ?let callbackName = query.callback
? ? ? ? ?response.setHeader('Content-Type', 'application/javascript')
? ? ? ? ?response.write(${callbackName}.call(undefined, 'success'))
? ? ? ? ? response.end()
}
程序員們約定這樣寫:
callbackName -> callback
(文中為了方便理解用了callbackName代替)
這樣的話就真正達(dá)到雙方的通訊目的了猎物,后端根據(jù)前端提供的callback參數(shù)構(gòu)造一個函數(shù)調(diào)用,把數(shù)據(jù)返回給第一個參數(shù)角塑,.call就拿到數(shù)據(jù)了蔫磨。
看來給<script>標(biāo)簽的src屬性加callback參數(shù)就是JSONP啦。標(biāo)簽的src屬性加callback參數(shù)就是JSONP啦圃伶。
如果使用jQuery堤如,JSONP的請求方式就更簡單啦:
$.ajax({
? ? ? ? ? ? ? ? ? url: "http://bbb.com:8002/pay",
? ? ? ? ? ? ? ? ? dataType: "jsonp",
? ? ? ? ? ? ? ? ? ?success: function( response ) {
? ? ? ? ? ? ? ? ? ?if(response === 'success'){
? ? ? ? ? ? ? ? ? ? amount.innerText = amount.innerText - 1
? ? ? ? ? ? ? ? }
? ? ? ? ? }
})
$.jsonp()
吐槽一下:和ajax又有個鬼的關(guān)系。窒朋。搀罢。
另外,JSONP是通過動態(tài)創(chuàng)建<script>標(biāo)簽實現(xiàn)的炼邀,動態(tài)創(chuàng)建的<script>標(biāo)簽只能用get魄揉,不能用post。標(biāo)簽實現(xiàn)的拭宁,動態(tài)創(chuàng)建的標(biāo)簽只能用get洛退,不能用post。
所以說杰标,通過動態(tài)創(chuàng)建<script>標(biāo)簽兵怯,并利用其src屬性提供一個callback參數(shù)構(gòu)造回調(diào)函標(biāo)簽,并利用其src屬性提供一個callback參數(shù)構(gòu)造回調(diào)函數(shù)來接收數(shù)據(jù)腔剂,從而達(dá)到與第三方網(wǎng)址建立通信的目的媒区,這就是JSONP啦。