← 返回题目列表

如何手写 AJAX 请求和 JSONP?它们有什么区别?

高频 中等 第 3 / 27 题 更新于 2026/07/29
手写代码AJAXJSONP跨域

简化版

手写 AJAX 通常用 XMLHttpRequest 封装 Promise,监听 readystatechangeload/error/timeout,处理状态码、超时、请求头和响应解析。JSONP 则是动态插入 script 标签,通过回调函数接收数据,只支持 GET,本质不是 XHR,也不能读取 HTTP 状态细节。

详细版

基础 AJAX:

function ajax({ url, method = 'GET', data, headers = {}, timeout = 0 }) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open(method, url, true)
    xhr.timeout = timeout
    Object.entries(headers).forEach(([key, value]) => xhr.setRequestHeader(key, value))
    xhr.onload = () => {
      if (xhr.status >= 200 && xhr.status < 300) resolve(xhr.responseText)
      else reject(new Error(`HTTP ${xhr.status}`))
    }
    xhr.onerror = () => reject(new Error('network error'))
    xhr.ontimeout = () => reject(new Error('timeout'))
    xhr.send(data ?? null)
  })
}

JSONP 通过约定回调名:服务端返回 callbackName({...}),前端提前把函数挂到 window 上。它能绕过同源限制加载脚本,但安全和能力边界都比 CORS 差。

完整版教学

一、AJAX 与 JSONP 的本质差异

对比项AJAX/XHRJSONP
底层能力浏览器网络请求 APIscript 标签加载脚本
跨域受同源策略与 CORS 控制利用脚本可跨域加载
方法支持多种 HTTP 方法通常只支持 GET
状态码可读取很难可靠读取
安全性可配合 CORS、凭证、头部执行远端脚本,风险更高

AJAX 是请求数据,JSONP 是执行代码。这个区别非常关键,因为 JSONP 返回内容一旦被加载,就会在当前页面上下文执行。

易错点:JSONP 不是“特殊 AJAX”,它没有使用 XHR,也没有真正绕过浏览器安全模型,只是利用了脚本资源加载规则。

二、手写 Promise 版 AJAX

function ajax(options) {
  const {
    url,
    method = 'GET',
    data = null,
    headers = {},
    timeout = 0,
    responseType = '',
  } = options

  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open(method, url, true)
    xhr.timeout = timeout
    xhr.responseType = responseType

    for (const [key, value] of Object.entries(headers)) {
      xhr.setRequestHeader(key, value)
    }

    xhr.onload = () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(xhr.responseType ? xhr.response : xhr.responseText)
      } else {
        reject(new Error(`HTTP ${xhr.status}`))
      }
    }

    xhr.onerror = () => reject(new Error('network error'))
    xhr.ontimeout = () => reject(new Error('timeout'))
    xhr.onabort = () => reject(new Error('aborted'))
    xhr.send(data)
  })
}

onload 表示请求完成,但不代表业务成功,所以还要检查状态码。onerror 常表示网络层失败、DNS 失败或跨域被拦截,很多时候拿不到精确原因。

三、GET 参数和 JSON 请求体

function buildQuery(params) {
  const search = new URLSearchParams()
  Object.entries(params).forEach(([key, value]) => {
    if (value !== undefined && value !== null) search.append(key, value)
  })
  return search.toString()
}

ajax({
  url: '/api/user',
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  data: JSON.stringify({ name: 'Tom' }),
})

GET 请求通常把参数拼到 URL,POST JSON 请求需要设置 Content-Type 并序列化对象。真实封装还要处理重复 key、数组参数、编码和后端约定。

四、手写 JSONP

function jsonp(url, params = {}, { callbackKey = 'callback', timeout = 5000 } = {}) {
  return new Promise((resolve, reject) => {
    const callbackName = `__jsonp_${Date.now()}_${Math.random().toString(16).slice(2)}`
    const script = document.createElement('script')
    const timer = setTimeout(() => cleanup(new Error('timeout')), timeout)

    function cleanup(error, data) {
      clearTimeout(timer)
      delete window[callbackName]
      script.remove()
      if (error) reject(error)
      else resolve(data)
    }

    window[callbackName] = data => cleanup(null, data)
    const query = buildQuery({ ...params, [callbackKey]: callbackName })
    script.src = `${url}${url.includes('?') ? '&' : '?'}${query}`
    script.onerror = () => cleanup(new Error('script load error'))
    document.head.appendChild(script)
  })
}

服务端需要返回类似 __jsonp_xxx({"name":"Tom"}) 的 JavaScript。前端函数名必须唯一,结束后要清理全局函数、定时器和 script 节点,否则会污染全局作用域。

五、状态码、错误和超时边界

AJAX 可以区分 HTTP 404HTTP 500、超时和主动 abort。JSONP 加载的是脚本,脚本是否执行回调才是主要信号,很多状态码细节浏览器不会暴露给脚本逻辑。

AJAX: open -> send -> load/error/timeout/abort -> inspect status
JSONP: create script -> load remote JS -> remote JS calls callback -> cleanup

如果 JSONP 的脚本加载成功但服务端返回的不是预期回调,Promise 会等到超时才失败。因此 JSONP 必须设置超时。

六、安全与现代替代方案

JSONP 会执行第三方返回的 JavaScript,一旦服务端被污染,攻击代码就能运行在当前页面权限下。现代跨域请求优先使用 CORS,服务端通过响应头授权来源、方法和请求头,前端继续使用 fetch 或 XHR。

场景推荐
新项目跨域 APICORS + fetch
老接口只支持 JSONP临时 JSONP 封装
携带凭证的跨域请求明确 CORS 凭证策略
不可信第三方数据源避免 JSONP

面试中提到 JSONP 时,要同时说出它的历史价值和安全局限,不能只写出动态 script。

七、常见误区与追问

  • 误区:JSONP 支持 POST。 它靠 script src 发起资源加载,通常只能 GET。
  • 误区:AJAX 的 onload 就代表成功。 还要检查 HTTP 状态码和业务响应。
  • 误区:JSONP 比 CORS 更安全。 JSONP 执行远端脚本,风险通常更高。
  • 追问:为什么 JSONP 要清理全局函数? 避免命名污染和内存泄漏。
  • 追问:跨域失败为什么拿不到详细错误? 浏览器为了安全会隐藏很多跨域失败细节。
  • 追问:如何取消 AJAX? 调用 xhr.abort(),并处理 onabort

八、加强记忆

AJAX 是“用浏览器请求 API 拿数据”,JSONP 是“把远端响应当脚本执行”。手写 AJAX 抓住 open、header、send、load/error/timeout/abort 和状态码;手写 JSONP 抓住唯一回调名、script 注入、超时、错误和清理。现代项目优先 CORS,JSONP 只作为历史兼容题理解。