簡單理解JSONP的定義及其實現

clancysong發表於2019-03-03

前戲

  • 寫的很詳細了,應該很好懂。
  • 注:文中程式碼均是擷取的程式碼片段,僅供參考和理解,酌情複製。

正文

同源策略

同源策略規定只在協議相同、域名相同、埠相同的情況下,也就是兩個網頁同源時,才能讀寫對方的資源。這是為了保證使用者的資訊保安做出的限制,然而同源策略有時也會對合理的用途造成影響,那麼就需要想辦法規避同源策略帶來的影響。

用script標籤發請求

瀏覽器解析html頁面時,如果看到有如link、img、script等標籤時,就會自動向標籤對應路徑發起一個get請求以獲取想要得到的資源。而用標籤發起的請求是不受同源策略限制的,利用這種特性,就可以達到規避同源策略向目標網站發起請求的目的。

例如,頁面中有一個script標籤:

<script src="http://demo.com/test"></script>
複製程式碼

這個標籤就會向 demo.com 下的 test 路徑發起請求,而當伺服器接收到請求後就會做出響應。當然一般這種標籤是通過JavaScript來動態建立的:

const script = document.createElement(`script`)
script.src = `http://demo.com/test`
document.body.appendChild(script)
複製程式碼

這樣就能直接用js動態地傳送請求了。需要注意的是,因為請求是通過script傳送的,所以瀏覽器接收到響應時會立即執行請求到的結果。例如:

...
if (pathNoQuery === `/test`) {
    response.setHeader(`Content-Type`, `application/javascript`)
    response.write(`alert(`這是 test 路徑返回的結果。`)`)
} else {
    response.statusCode = 404
}
response.end()
...
複製程式碼

這裡伺服器接收到請求後返回了 alert(...) 的程式碼,瀏覽器接收到結果後就會執行 alert(...),也就會彈出一個提示框。利用這一點,就可以直接在接收到的script標籤中使用得到的資料。假設頁面中就一個id為balanceText的元素用來顯示使用者的餘額,伺服器通過getResultFromDb函式來獲取資料庫中的資料:

...
if (pathNoQuery === `/test`) {
    const rs = getResultFromDb()
    response.setHeader(`Content-Type`, `application/javascript`)
    response.write(`balanceText.textContent = `你還有${rs.balance}塊錢``)
} else {
    response.statusCode = 404
}
response.end()
...
複製程式碼

這樣每次向伺服器請求時,伺服器就會取得資料庫中的資料並返回更改對應元素文字的執行語句,瀏覽器接收響應後執行語句,即可在頁面中顯示出接收到的資料。

然而這種方法很令人困惑,展示資料明明應該是前端應該做的事,卻被後端做了。而且後端為了展示資料還需要了解前端的頁面結構。而這也導致後端與前端的耦合度太高,後端的響應幾乎只能為這一個頁面服務。

那麼解決這個問題的思路自然是降低前後端的耦合度,也就是解耦。前端的程式碼就應該放到前端去寫,而後端只需要將資料交給前端就好,不需要做額外的事情。

回撥函式

解決的辦法是在前端程式碼中定義一個函式,也就是所謂的回撥函式。伺服器返回的程式碼中只需要執行這個函式,而前端想要獲取的資料只需要通過引數傳遞到這個函式中即可。這裡就可以修改一下之前的程式碼:

前端:

const cbFunc = (result) => {
    balanceText.textContent = `你還有${result}塊錢`
}
const jsonpScript = document.createElement(`script`)
jsonpScript.src = `http://demo.com/test?callback=cbFunc`
document.body.appendChild(jsonpScript)
複製程式碼

後端:

...
if (pathNoQuery === `/test`) {
    const rs = getResultFromDb()
    response.setHeader(`Content-Type`, `application/javascript`)
    response.write(`${queryObject.callback}(${rs.balance})`)
} else {
    response.statusCode = 404
}
response.end()
...
複製程式碼

這裡前端請求時在查詢引數中傳遞了一個callback引數,記錄回撥函式的函式名。後端只需要使用這個函式名返回一個呼叫回撥函式的語句就好了。與此同時請求的資料也作為引數被傳遞迴給回撥函式,接著只需要在回撥函式中使用這個引數就可以得到想要的資料了。這樣後端就只需要專注於返回資料而無需操心前端的程式碼,前端拿到資料也就可以放心地為所欲為了。

JSONP

那麼至此為止,上文說了這麼多,和JSONP有什麼關係?

在這裡總結一下:

  1. 動態建立地建立script標籤以發起請求,在src中填寫請求的目標路徑,並傳入查詢引數callback也就是回撥函式的函式名。
  2. 伺服器接收到請求時,會根據查詢引數callback返回執行回撥函式的語句,並在引數傳入請求方想要的資料。
  3. 請求方接收到響應後就會執行這個語句也就是執行回撥函式,這樣請求方就能在回撥函式中取得想要的資料。

這個請求的過程就是JSONP。

結語

  • 所以說JSONP和JSON其實沒啥關係,只是JSONP一般用來傳輸JSON。
  • JSONP和AJAX也沒什麼關係。
  • JSONP不能發起POST請求,因為script標籤本身無法發POST請求。

相關文章