Skip to content Skip to footer

头条面试官:一文彻底搞懂 JSONP

从环绕山峰的小径最高点看到的拉瓦莱多三峰山,意大利

(© AWL Images/Danita Delimont)

本题摘自于我 github 上的面试每日一题:https://github.com/shfshanyue/Daily-Question,并有大厂面经及内推信息,可在左下角打开本题原文链接

摘自山月的博客,原文地址: https://shanyue.tech/code/jsonp/

一个正常的请求: JSON正常发请求时,curl 示例:

代码语言:javascript代码运行次数:0运行复制$ curl https://shanyue.tech/api/user?id=100

{

"id": 100,

"name": "shanyue",

"wechat": "xxxxx",

"phone": "183xxxxxxxx"

}

使用 fetch 发送请求,示例:

代码语言:javascript代码运行次数:0运行复制const data = await fetch('https://shanyue.tech/api/user?id=100', {

headers: {

'content-type': 'application/json',

},

method: 'GET',

}).then(res => res.json())

请求数据后,使用一个函数来处理数据

代码语言:javascript代码运行次数:0运行复制handleData(data)

一个 JSONP 请求JSONP,全称 JSON with Padding,为了解决跨域的问题而出现。虽然它只能处理 GET 跨域,虽然现在基本上都使用 CORS 跨域,但仍然要知道它,毕竟面试会问。

curl 示例

代码语言:javascript代码运行次数:0运行复制$ curl https://shanyue.tech/api/user?id=100&callback=padding

padding({

"id": 100,

"name": "shanyue",

"wechat": "xxxxx",

"phone": "183xxxxxxxx"

})

对于正常的请求有何不同一目了然: 多了一个 callback=padding, 并且响应数据被 padding 包围,这就是 JSONP

那请求数据后,如何处理数据呢?此时的 padding 就是处理数据的函数

代码语言:javascript代码运行次数:0运行复制window.padding = handleData

这里实现一个 jsonp 函数

代码语言:javascript代码运行次数:0运行复制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 的回调函数名作为一个随机变量,代码如下

代码语言:javascript代码运行次数:0运行复制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 实现完整代码:

代码语言:javascript代码运行次数:0运行复制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 服务端适配相关代码:

代码语言:javascript代码运行次数:0运行复制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 页面调用相关代码

代码语言:javascript代码运行次数:0运行复制

JSONP 实现代码示例演示从中克隆代码: 山月博客的 github 仓库

文件结构

index.js: jsonp 的简单与复杂实现server.js: 服务器接口形式demo.html: 前端如何调用 JSONP快速演示代码语言:javascript代码运行次数:0运行复制// 开启服务端

$ node server.js

// 对 demo.html 起一个服务,并且按照提示在浏览器中打开地址,应该是 http://localhost:5000

// 观察控制台输出 JSONP 的回调结果

$ serve .