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 为什么能解决跨域,必须先理解浏览器的同源策略:

解决方案(附完整代码)

在 Vue 项目中,我们有两种主流做法:一是使用现成的 vue-jsonp 插件(推荐),二是手写一个 JSONP 工具函数(适合理解原理)。下面分别给出完整实现。

方案一:使用 vue-jsonp 插件(推荐)

  1. 安装依赖:npm install vue-jsonp --save
  2. main.js 中全局注册插件。
  3. 在组件中通过 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)
      }
    }
  }
}

排查步骤与常见坑

总结一下:JSONP 是一把双刃剑,它在无法修改后端、无法使用代理的场景下非常有效,但只支持 GET、依赖服务端配合、安全性较低(容易被 XSS 利用)。在现代项目中,优先推荐 CORS 或 Nginx 反向代理;只有当对接第三方老接口且对方只提供 JSONP 时,再使用本文的方案。掌握 vue-jsonp 插件和手写原理,你就能在 Vue 项目中从容应对这类跨域难题。