/** * 一个简单的 JSONP 实现 * @param {object} options - 配置对象 * @param {string} options.url - 请求的 URL 地址 * @param {object} [options.params] - 需要传递的参数 * @param {string} [options.callbackKey='callback'] - 与后端约定的回调函数参数名 * @param {function} options.callback - 成功时调用的回调函数 */ function jsonp({ url, params = {}, callbackKey = 'callback', callback }) { // 1. 生成一个独一无二的回调函数名,防止并发请求时冲突 const callbackName = `jsonp_callback_${Date.now()}_${Math.floor(Math.random() * 100000)}`; // 2. 将回调函数挂载到 window 上,使其成为全局函数 // 这是最关键的一步,因为服务器返回的脚本将直接调用这个函数 window[callbackName] = function(data) { // 成功接收到数据后,调用我们真正的回调函数 callback(data); // 3. 清理工作:用完即焚 // 移除挂载到 window 上的函数,避免内存泄漏和全局污染 delete window[callbackName]; // 移除注入的 script 标签 document.head.removeChild(scriptElement); }; // 4. 准备参数,并将其拼接到 URL 上 const paramsArray = []; for (let key in params) { paramsArray.push(`${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`); } // 加上与后端约定的回调函数名参数 paramsArray.push(`${callbackKey}=${callbackName}`); const paramsString = paramsArray.join('&'); const finalUrl = url.includes('?') ? `${url}&${paramsString}` : `${url}?${paramsString}`; // 5. 创建并注入 <script> 标签,发起请求 const scriptElement = document.createElement('script'); scriptElement.src = finalUrl; document.head.appendChild(scriptElement); // 堆代码 www.duidaima.com // (可选)错误处理 }
// 发起一次 JSONP 请求 jsonp({ url: 'https://api.fedjavascript.com/jsonp/city', params: { name: 'Beijing' }, // 后端接口约定的回调参数名,默认为 'callback' // callbackKey: 'cb', callback: function(data) { // 在这里,我们成功拿到了跨域数据! console.log('当前天气:', data); // 预期的 data 格式: { temperature: '35°C', condition: '晴' } } }); // 此时,浏览器网络面板会看到一个类似这样的请求: // https://api.fedjavascript.com/jsonp/city?name=Beijing&callback=jsonp_callback_1752844122915_12345 // 服务器会返回这样的内容(Content-Type: application/javascript): // jsonp_callback_1752844122915_12345({ "temperature": "35°C", "condition": "晴" });当浏览器加载并执行这段返回的脚本时,我们挂载在 window 上的 jsonp_callback_... 函数就被调用了,数据被成功捕获,而那个临时的 <script> 标签和全局函数也随之被销毁,整个过程干净利落。
3.简陋的错误处理:我们无法像 XMLHttpRequest 那样精确地捕获 HTTP 状态码(如 404, 500)。我们只能通过 onerror 事件或者设置一个计时器(timeout)来判断请求是否失败,但这并不能告诉我们失败的具体原因