vue jsonp 解决跨域 - 完整解决方案与实战教程
在前后端分离的开发模式下,前端 Vue 项目往往运行在 localhost:8080,而后端接口可能部署在另一个域名或端口上。此时浏览器出于同源策略的限制,会直接拦截跨域请求,控制台抛出 CORS 错误。很多同学第一反应是让后端加 Access-Control-Allow-Origin,但在对接第三方老接口、天气 API、或者是无法修改后端代码的场景下,这招根本行不通。这时候,JSONP 就成了绕过同源策略、解决跨域问题的经典“曲线救国”方案。本文将从实际踩坑出发,带你彻底搞懂 Vue 中如何使用 JSONP 并避开常见的坑。
问题现象
在 Vue 组件中通过 axios 或 fetch 请求外部接口时,控制台出现如下典型错误:
Access to XMLHttpRequest at 'https://api.example.com/data'
from origin 'http://localhost:8080' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
更让人头疼的是,即使你在 vue.config.js 里配置了 devServer.proxy 代理,开发环境看似正常,但一旦打包上线,代理失效,跨域问题再次出现。而如果后端接口是第三方服务,你连改配置的权限都没有。
原因分析
要理解 JSONP 为什么能解决跨域,必须先理解浏览器的同源策略:
- 同源策略限制的是 XHR / Fetch 请求:浏览器不允许脚本通过 XMLHttpRequest 或 Fetch 读取不同源响应的内容。
- 但 <script> 标签不受同源策略限制:这是历史遗留的“漏洞”,也是 JSONP 的核心原理。
- JSONP 的本质:动态创建一个
<script>标签,把请求地址作为 src,服务端返回一段“函数调用”的 JS 代码,浏览器执行这段代码时就相当于拿到了数据。 - 局限性:JSONP 只支持 GET 请求,不支持 POST;且需要服务端配合返回特定格式的回调函数。
解决方案(附完整代码)
在 Vue 项目中,我们有两种主流做法:一是使用现成的 vue-jsonp 插件(推荐),二是手写一个 JSONP 工具函数(适合理解原理)。下面分别给出完整实现。
方案一:使用 vue-jsonp 插件(推荐)
- 安装依赖:
npm install vue-jsonp --save - 在
main.js中全局注册插件。 - 在组件中通过
this.$jsonp调用接口。
// main.js
import Vue from 'vue'
import { VueJsonp } from 'vue-jsonp'
// 注册插件,可配置超时时间
Vue.use(VueJsonp, {
timeout: 5000 // 请求超时时间,单位毫秒
})
// -------------------------------------
// components/Weather.vue
<template>
<div>
<h3>今日天气</h3>
<p v-if="weather">{{ weather.city }}:{{ weather.temperature }}℃</p>
<p v-else>加载中...</p>
</div>
</template>
<script>
export default {
data() {
return {
weather: null
}
},
mounted() {
this.fetchWeather()
},
methods: {
async fetchWeather() {
try {
// $jsonp 会自动生成 callback 参数名,并处理回调
const res = await this.$jsonp('https://api.example.com/weather', {
// 传给服务端的查询参数
city: 'beijing',
// 指定服务端接收的回调参数名,默认为 callback
callbackQuery: 'callback',
// 指定服务端返回的回调函数名,默认为随机字符串
callbackName: 'jsonpCallback'
})
this.weather = res
} catch (err) {
console.error('JSONP 请求失败:', err)
}
}
}
}
</script>
方案二:手写 JSONP 工具函数(理解原理)
如果你不想引入额外依赖,可以自己封装一个 JSONP 函数。核心思路是动态插入 script 标签,并把回调函数挂到 window 上。
// utils/jsonp.js
/**
* 手写 JSONP 请求函数
* @param {string} url 请求地址
* @param {object} params 查询参数对象
* @param {object} options 配置项
* @returns {Promise} 返回 Promise,resolve 数据或 reject 错误
*/
export function jsonp(url, params = {}, options = {}) {
return new Promise((resolve, reject) => {
// 1. 生成唯一的回调函数名,避免多次请求冲突
const callbackName = `jsonp_${Date.now()}_${Math.floor(Math.random() * 10000)}`
const timeout = options.timeout || 5000
// 2. 拼接完整的 URL
const query = new URLSearchParams({
...params,
[options.callbackQuery || 'callback']: callbackName
})
const fullUrl = `${url}?${query.toString()}`
// 3. 创建 script 标签
const script = document.createElement('script')
script.src = fullUrl
script.async = true
// 4. 定义全局回调函数,服务端返回时会执行它
window[callbackName] = (data) => {
resolve(data)
cleanup()
}
// 5. 超时处理
const timer = setTimeout(() => {
reject(new Error('JSONP 请求超时'))
cleanup()
}, timeout)
// 6. 加载失败处理
script.onerror = () => {
reject(new Error('JSONP 脚本加载失败'))
cleanup()
}
// 7. 清理函数:移除 script 和全局变量
function cleanup() {
clearTimeout(timer)
if (script.parentNode) {
script.parentNode.removeChild(script)
}
delete window[callbackName]
}
// 8. 插入 DOM 触发请求
document.head.appendChild(script)
})
}
在组件中使用:
// components/Weather.vue
import { jsonp } from '@/utils/jsonp'
export default {
methods: {
async fetchWeather() {
try {
const data = await jsonp('https://api.example.com/weather', {
city: 'shanghai'
}, {
callbackQuery: 'callback', // 服务端约定的回调参数名
timeout: 8000
})
this.weather = data
} catch (e) {
console.error(e)
}
}
}
}
排查步骤与常见坑
- 坑一:callback 参数名不匹配。不同服务端约定的回调参数名不同,有的是
callback,有的是cb,还有的是jsonpCallback。必须查看接口文档或抓包确认。 - 坑二:服务端返回的不是 JSONP 格式。如果服务端返回纯 JSON,浏览器会报语法错误。可以用浏览器直接访问拼接好的 URL 验证返回内容是否为
callbackName({...})形式。 - 坑三:只支持 GET。JSONP 无法发送 POST 请求,如果业务必须 POST,只能让后端支持 CORS 或使用代理。
- 坑四:内存泄漏。手写函数时务必在回调执行后移除 script 标签并删除 window 上的回调函数,否则多次请求会残留大量全局变量。
- 坑五:HTTPS 页面请求 HTTP 接口。浏览器会拦截混合内容,JSONP 同样受此限制,必须保证协议一致。
总结一下:JSONP 是一把双刃剑,它在无法修改后端、无法使用代理的场景下非常有效,但只支持 GET、依赖服务端配合、安全性较低(容易被 XSS 利用)。在现代项目中,优先推荐 CORS 或 Nginx 反向代理;只有当对接第三方老接口且对方只提供 JSONP 时,再使用本文的方案。掌握 vue-jsonp 插件和手写原理,你就能在 Vue 项目中从容应对这类跨域难题。