頭條面試官:一文徹底搞懂 JSONP
一個正常的請求: JSON
正常發請求時,curl 示例:
- $ curl https://shanyue.tech/api/user?id=100
- {
- "id": 100,
- "name": "shanyue",
- "wechat": "xxxxx",
- "phone": "183xxxxxxxx"
- }
使用 fetch 發送請求,示例:
- const data = await fetch('https://shanyue.tech/api/user?id=100', {
- headers: {
- 'content-type': 'application/json',
- },
- method: 'GET',
- }).then(res => res.json())
請求數據后,使用一個函數來處理數據
- handleData(data)
一個 JSONP 請求
JSONP,全稱 JSON with Padding,為了解決跨域的問題而出現。雖然它只能處理 GET 跨域,雖然現在基本上都使用 CORS 跨域,但仍然要知道它,畢竟面試會問。
curl 示例
- $ curl https://shanyue.tech/api/user?id=100&callback=padding
- padding({
- "id": 100,
- "name": "shanyue",
- "wechat": "xxxxx",
- "phone": "183xxxxxxxx"
- })
對于正常的請求有何不同一目了然: 多了一個 callback=padding, 并且響應數據被 padding 包圍,這就是 JSONP
那請求數據后,如何處理數據呢?此時的 padding 就是處理數據的函數
- window.padding = handleData
這里實現一個 jsonp 函數
- function jsonp_simple ({ url, onData, params }) {
- const script = document.createElement('script')
- // 一、默認 callback 函數為 padding
- script.src = `${url}?${stringify({ callback: 'padding', ...params })}`
- // 二、使用 onData 作為 window.padding 函數,接收數據
- window['padding'] = onData
- document.body.appendChild(script)
- }
此時會有一個問題: window.padding 函數會污染全局,如果有多個請求發送如何處理?
使 jsonp 的回調函數名作為一個隨機變量,代碼如下
- function jsonp ({ url, onData, params }) {
- const script = document.createElement('script')
- // 一、為了避免全局污染,使用一個隨機函數名
- const cbFnName = `JSONP_PADDING_${Math.random().toString().slice(2)}`
- // 二、默認 callback 函數為 cbFnName
- script.src = `${url}?${stringify({ callback: cbFnName, ...params })}`
- // 三、使用 onData 作為 cbFnName 回調函數,接收數據
- window[cbFnName] = onData;
- document.body.appendChild(script)
- }
- // 發送 JSONP 請求
- jsonp({
- url: 'http://localhost:10010',
- params: { id: 10000 },
- onData (data) {
- console.log('Data:', data)
- }
- })
代碼附錄
完整代碼可見山月博客的 github 倉庫: https://github.com/shfshanyue/blog/tree/master/code/jsonp/
JSONP 實現完整代碼:
- function stringify (data) {
- const pairs = Object.entries(data)
- const qs = pairs.map(([k, v]) => {
- let noValue = false
- if (v === null || v === undefined || typeof v === 'object') {
- noValue = true
- }
- return `${encodeURIComponent(k)}=${noValue ? '' : encodeURIComponent(v)}`
- }).join('&')
- return qs
- }
- function jsonp ({ url, onData, params }) {
- const script = document.createElement('script')
- // 一、為了避免全局污染,使用一個隨機函數名
- const cbFnName = `JSONP_PADDING_${Math.random().toString().slice(2)}`
- // 二、默認 callback 函數為 cbFnName
- script.src = `${url}?${stringify({ callback: cbFnName, ...params })}`
- // 三、使用 onData 作為 cbFnName 回調函數,接收數據
- window[cbFnName] = onData;
- document.body.appendChild(script)
- }
JSONP 服務端適配相關代碼:
- const http = require('http')
- const url = require('url')
- const qs = require('querystring')
- const server = http.createServer((req, res) => {
- const { pathname, query } = url.parse(req.url)
- const params = qs.parse(query)
- const data = { name: 'shanyue', id: params.id }
- if (params.callback) {
- str = `${params.callback}(${JSON.stringify(data)})`
- res.end(str)
- } else {
- res.end()
- }
- })
- server.listen(10010, () => console.log('Done'))
JSONP 頁面調用相關代碼
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title></title>
- </head>
- <body>
- <script src="./index.js" type="text/javascript"></script>
- <script type="text/javascript">
- jsonp({
- url: 'http://localhost:10010',
- params: { id: 10000 },
- onData (data) {
- console.log('Data:', data)
- }
- })
- </script>
- </body>
- </html>
JSONP 實現代碼示例演示
從中克隆代碼: 山月博客的 github 倉庫
文件結構
- index.js: jsonp 的簡單與復雜實現
- server.js: 服務器接口形式
- demo.html: 前端如何調用 JSONP
快速演示
- // 開啟服務端
- $ node server.js
- // 對 demo.html 起一個服務,并且按照提示在瀏覽器中打開地址,應該是 http://localhost:5000
- // 觀察控制臺輸出 JSONP 的回調結果
- $ serve .
本文轉載自微信公眾號「全棧成長之路」,可以通過以下二維碼關注。轉載本文請聯系全棧成長之路公眾號。