一篇文章完全搞懂 JSONP

一個正常的請求: JSON

正常發請求時,curl 示例:javascript

$ curl https://shanyue.tech/api/user?id=100

{
  "id": 100,
  "name": "shanyue",
  "wechat": "xxxxx",
  "phone": "183xxxxxxxx"
}

使用 fetch 發送請求,示例:html

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 跨域,但仍然要知道它,畢竟面試會問java

curl 示例node

$ curl https://shanyue.tech/api/user?id=100&callback=padding

padding({
  "id": 100,
  "name": "shanyue",
  "wechat": "xxxxx",
  "phone": "183xxxxxxxx"
})

對於正常的請求有何不一樣一目瞭然: 多了一個 callback=padding, 而且響應數據被 padding 包圍,這就是 JSONPgit

那請求數據後,如何處理數據呢?此時的 padding 就是處理數據的函數github

window.padding = handleData

這裏實現一個 jsonp 函數面試

function jsonp_simple ({ url, onData, params }) {
  const script = document.createElement('script')

  // 1、默認 callback 函數爲 padding
  script.src = `${url}?${stringify({ callback: 'padding', ...params })}`
  // 2、使用 onData 做爲 window.padding 函數,接收數據
  window['padding'] = onData

  document.body.appendChild(script)
}

此時會有一個問題: window.padding 函數會污染全局,若是有多個請求發送如何處理?json

使 jsonp 的回調函數名做爲一個隨機變量,代碼以下api

function jsonp ({ url, onData, params }) {
  const script = document.createElement('script')

  // 1、爲了不全局污染,使用一個隨機函數名
  const cbFnName = `JSONP_PADDING_${Math.random().toString().slice(2)}`
  // 2、默認 callback 函數爲 cbFnName
  script.src = `${url}?${stringify({ callback: cbFnName, ...params })}`
  // 3、使用 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...

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')

  // 1、爲了不全局污染,使用一個隨機函數名
  const cbFnName = `JSONP_PADDING_${Math.random().toString().slice(2)}`
  // 2、默認 callback 函數爲 cbFnName
  script.src = `${url}?${stringify({ callback: cbFnName, ...params })}`
  // 3、使用 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 .

本文收錄於 GitHub 日問: DailyQuestion,內含大廠內推機會、面經大全及若干面試題,天天學習五分鐘,一年進入大廠中。

相關文章
相關標籤/搜索