跨域那些事儿:从同源策略到JSONP的奇妙冒险

120 阅读5分钟

大家好,我是你们的老朋友FogLetter,今天我们来聊聊前端开发中绕不开的话题——跨域问题。我将带大家深入了解这个让无数前端开发者头疼的问题,并重点介绍JSONP这种经典的跨域解决方案。

一、为什么会有跨域问题?

1.1 大前端时代的开发模式

如今的前端早已不是简单的页面切图,而是进入了"大前端"时代:

  • React/Vue 构建的现代化前端应用
  • Node.js 开发的后端服务
  • 移动端(iOS/Android)
  • 桌面端应用(如VSCode就是用TypeScript开发的)
  • 甚至嵌入式设备和AI领域

在这种背景下,前后端分离成为了主流的开发模式。前端在开发时通常会运行在localhost:5173(Vite默认端口),而后端可能在8080端口。这就带来了一个关键问题——跨域

1.2 同源策略:浏览器的安全卫士

跨域问题的根源在于浏览器的同源策略(Same-Origin Policy)。什么是"同源"?简单来说,就是协议、域名、端口三者完全相同:

http://www.example.com:80
  ↑       ↑         ↑
协议     域名       端口

以下都是不同源的例子:

  1. 域名不同:

    • http://localhost:5173http://www.baidu.com/api/user
  2. 协议不同:

    • http://localhost:5173https://localhost:5173/api/user
  3. 端口不同:

    • http://localhost:5173http://localhost:8080/api/test

1.3 跨域请求到底发生了什么?

很多同学误以为跨域请求直接被浏览器拦截了,其实不然:

  1. 请求确实发送到了服务端(你可以在后端日志中看到)
  2. 服务端也返回了响应
  3. 浏览器检查响应头后发现不符合CORS规则,于是拦截了响应

这就像你点了一份外卖,外卖小哥确实送到了你家门口,但物业保安不让进!

二、JSONP:曲线救国的跨域方案

2.1 为什么会有JSONP?

既然浏览器对fetch/XMLHttpRequest有严格的跨域限制,那有没有不受限制的标签呢?有!那就是:

  • <img>
  • <script>
  • <link>

特别是<script>标签,它天生就具备跨域能力(不然怎么使用CDN引入各种库呢?)。JSONP(JSON with Padding)就是利用这一特性实现的跨域方案。

2.2 JSONP的工作原理

JSONP的巧妙之处在于:

  1. 前端动态创建<script>标签,其src指向跨域API
  2. 后端返回的不是纯JSON,而是一段JavaScript代码
  3. 这段代码会调用前端预先定义好的回调函数,并将数据作为参数传入
// 前端定义回调函数
function handleResponse(data) {
  console.log('收到数据:', data);
}

// 动态添加script标签
const script = document.createElement('script');
script.src = 'http://api.example.com/data?callback=handleResponse';
document.body.appendChild(script);

后端返回的内容类似于:

handleResponse({
  "name": "张三",
  "age": 25
});

2.3 手写一个JSONP实现

让我们实现一个更完善的JSONP工具函数:

function jsonp({ url, params, callback }) {
  return new Promise((resolve, reject) => {
    // 创建script标签
    const script = document.createElement('script');
    
    // 处理参数
    params = { ...params, callback }
    let arr = [];
    for (let key in params) {
        arr.push(`${key}=${params[key]}`)
    }
    
    script.src = `${url}?${arr.join('&')}`;
    
    // 定义全局回调函数
    window[callback] = function(data) {
      resolve(data);
    };
    document.body.appendChild(script)
  });
}

// 使用示例
jsonp({
  url: 'http://api.example.com/data',
  params: { id: 123 },
  callback: 'handleData'
}).then(data => {
  console.log('获取数据:', data);
});

2.4 后端如何配合JSONP?

后端需要识别callback参数,并将数据包裹在回调函数中返回。以Node.js为例:

const http = require('http');
const url = require('url');

const server = http.createServer((req, res) => {
  const { pathname, query } = url.parse(req.url, true);
  
  if (pathname === '/api/data') {
    const callback = query.callback || 'callback';
    const data = JSON.stringify({ message: 'Hello JSONP!' });
    
    res.writeHead(200, { 'Content-Type': 'application/javascript' });
    res.end(`${callback}(${data})`);
  } else {
    res.writeHead(404);
    res.end();
  }
});

server.listen(3000, () => {
  console.log('Server running at http://localhost:3000');
});

三、JSONP的优缺点分析

3.1 优点

  1. 兼容性极好:支持所有浏览器,包括老式IE
  2. 不需要特殊服务器配置:不像CORS需要设置Access-Control-Allow-Origin
  3. 实现简单:前端后端都不需要复杂配置

3.2 缺点

  1. 仅支持GET请求:因为<script>标签只能发起GET请求
  2. 安全性问题
    • 容易遭受XSS攻击
    • 回调函数挂载在全局,可能被恶意利用
  3. 错误处理困难:难以像fetch那样精确捕获网络错误
  4. 缺乏标准化:每个API可能实现方式略有不同

四、JSONP在实际项目中的应用

虽然现代项目中更推荐使用CORS,但在某些场景下JSONP仍然有用武之地:

4.1 第三方数据获取

比如获取天气数据:

function getWeather(city) {
  return jsonp({
    url: 'https://weather-api.com/data',
    params: { city },
    callback: 'weatherCallback'
  });
}

getWeather('北京').then(data => {
  console.log('北京天气:', data);
});

4.2 跨域用户认证

某些老系统可能使用JSONP进行跨域认证:

jsonp({
  url: 'https://auth.example.com/checkLogin',
  callback: 'authCallback'
}).then(userInfo => {
  if (userInfo.loggedIn) {
    showUserProfile(userInfo);
  } else {
    showLoginModal();
  }
});

五、JSONP的安全注意事项

使用JSONP时务必注意以下安全问题:

  1. 验证数据来源:确保只从可信源加载JSONP
  2. 避免敏感操作:不要在JSONP回调中执行敏感操作
  3. 设置超时:防止请求挂起
function safeJsonp(options) {
  return Promise.race([
    jsonp(options),
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('Timeout')), 5000)
    )
  ]);
}

六、总结

JSONP作为一种经典的跨域解决方案,虽然在现代前端开发中逐渐被CORS取代,但理解它的原理仍然很有价值:

  1. 它利用了<script>标签不受同源策略限制的特性
  2. 需要前后端配合,后端返回JavaScript而非纯JSON
  3. 实现简单但存在安全性和功能限制

正如前端大佬Douglas Crockford所说:"JSONP是黑客精神的体现——它不是标准,但解决了实际问题。" 在新技术层出不穷的今天,了解这些"老派"解决方案能让我们更好地理解Web的发展脉络。

希望这篇笔记能帮你彻底理解JSONP!如果你有更多关于跨域的问题,欢迎在评论区留言讨论。

掘金小贴士:在实际项目中,如果是自己的前后端项目,建议优先使用CORS;如果是调用第三方API且只支持JSONP,记得做好安全防护哦!