原生JavaScript实现AJAX与JSONP跨域请求详解
本文详细讲解如何使用原生JavaScript实现AJAX请求及JSONP跨域方案。通过封装XMLHttpRequest对象,逐步演示实例化、发送请求及处理readyState状态的全过程。针对浏览器同源策略限制,深入解析JSONP利用script标签src属性实现跨域的原理、实现步骤及其安全性与局限性,并提供完整的原生代码示例。
原生JavaScript实现AJAX与JSONP跨域请求详解
在前端开发中,与后端进行数据交互时,许多开发者倾向于使用jQuery封装好的AJAX方法,因其便捷高效。然而,若项目仅需AJAX功能而无需jQuery的其他庞大特性,引入整个库显得冗余且不必要的增加负担。
实际上,使用原生JavaScript实现AJAX并不复杂。本文将深入讲解如何从零开始实现基础的AJAX功能,并进一步探讨如何处理跨域请求的JSONP方案。
AJAX基础原理与实现
一、AJAX基础原理
AJAX技术的核心在于XMLHttpRequest对象,它是实现异步数据交换的关键组件。
一个标准的AJAX请求流程通常包含以下关键步骤:
- 首先,需要实例化XMLHttpRequest对象,创建请求实例。
- 其次,配置并连接服务器,指定请求的URL及方法。
- 接着,发送构建好的请求数据至服务器端。
- 最后,监听并接收服务器返回的响应数据,进行后续处理。
为了简化调用,我将AJAX请求逻辑封装为一个名为ajax()的函数,该函数接收一个配置对象params作为参数,以支持灵活的请求配置。
function ajax(params) {
params = params || {};
params.data = params.data || {};
// 判断是ajax请求还是jsonp请求
var json = params.jsonp ? jsonp(params) : json(params);
// ajax请求
function json(params) {
// 请求方式,默认是GET
params.type = (params.type || 'GET').toUpperCase();
// 避免有特殊字符,必须格式化传输数据
params.data = formatParams(params.data);
var xhr = null;
// 实例化XMLHttpRequest对象
if(window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
// IE6及其以下版本
xhr = new ActiveXObjcet('Microsoft.XMLHTTP');
};
// 监听事件,只要 readyState 的值变化,就会调用 readystatechange 事件
xhr.onreadystatechange = function() {
// readyState属性表示请求/响应过程的当前活动阶段,4为完成,已经接收到全部响应数据
if(xhr.readyState == 4) {
var status = xhr.status;
// status:响应的HTTP状态码,以2开头的都是成功
if(status >= 200 && status < 300) {
var response = '';
// 判断接受数据的内容类型
var type = xhr.getResponseHeader('Content-type');
if(type.indexOf('xml') !== -1 && xhr.responseXML) {
response = xhr.responseXML; //Document对象响应
} else if(type === 'application/json') {
response = JSON.parse(xhr.responseText); //JSON响应
} else {
response = xhr.responseText; //字符串响应
};
// 成功回调函数
params.success && params.success(response);
} else {
params.error && params.error(status);
}
};
};
// 连接和传输数据
if(params.type == 'GET') {
// 三个参数:请求方式、请求地址(get方式时,传输数据是加在地址后的)、是否异步请求(同步请求的情况极少);
xhr.open(params.type, params.url + '?' + params.data, true);
xhr.send(null);
} else {
xhr.open(params.type, params.url, true);
//必须,设置提交时的内容类型
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
// 传输数据
xhr.send(params.data);
}
}
//格式化参数
function formatParams(data) {
var arr = [];
for(var name in data) {
// encodeURIComponent() :用于对 URI 中的某一部分进行编码
arr.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name]));
};
// 添加一个随机数参数,防止缓存
arr.push('v=' + random());
return arr.join('&');
}
// 获取随机数
function random() {
return Math.floor(Math.random() * 10000 + 500);
}
}
在上述实现代码中,readyState属性用于表示请求/响应过程所处的当前活动阶段,其不同数值代表不同的状态含义如下:
0:表示初始化状态,此时尚未调用open()方法。
1:表示已启动,open()方法已调用,但send()方法尚未执行。
2:表示请求已发送,send()方法已调用,但尚未接收到任何响应数据。
3:表示正在接收数据,已接收到部分响应数据,但尚未完成。
4:表示请求完成,已接收到全部响应数据,客户端可以安全地使用这些数据。
以下是AJAX请求的具体使用示例,展示了如何配置参数并处理回调。
ajax({
url: 'test.php', // 请求地址
type: 'POST', // 请求类型,默认"GET",还可以是"POST"
data: {'b': '异步请求'}, // 传输数据
success: function(res){ // 请求成功的回调函数
console.log(JSON.parse(res));
},
error: function(error) {} // 请求失败的回调函数
});
JSONP跨域解决方案与原理
二、JSONP跨域解决方案
理解同源策略是解决跨域问题的前提。
AJAX请求之所以面临“跨域”限制,根本原因在于浏览器的同源策略。该策略规定,一个页面的AJAX请求只能获取与该页面具有相同源(或同域)的数据。所谓“同源”,是指协议、域名和端口号三者必须完全一致。例如:
http://example.com 与 https://example.com 被视为不同源,因为协议不同(http与https);
http://localhost:8080 与 http://localhost:1000 被视为不同源,因为端口号不同(8080与1000);
http://localhost:8080 与 https://example.com 则因协议、域名和端口号均不同,完全不属于同一源,无法直接交互。
当尝试进行跨域请求时,浏览器通常会抛出如下错误信息:
XMLHttpRequest cannot load http://ghmagical.com/article/?intro=jsonp%E8%AF%B7%E6%B1%82&v=5520. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access.
面对跨域限制,JSONP(JSON with Padding)作为一种常用的解决方案应运而生。
JSONP全称JSON with Padding(填充式JSON或参数式JSON),是一种广泛使用的跨域请求技术。其核心原理是利用HTML中script标签的src属性不受同源策略限制的特性,通过src属性向服务器发送请求。服务器返回JavaScript代码,浏览器接收并直接执行这段代码,这与通过script标签引用外部脚本文件的原理一致。
JSONP由两部分组成:回调函数名和数据。回调函数是响应到达时页面中需要调用的函数,其名称通常在请求中指定。当服务器响应时,会将数据作为参数包裹在回调函数名中,形成类似“callbackName(data)”的字符串返回给客户端。
JSONP的具体请求过程如下:
- 请求阶段:浏览器动态创建一个script标签,并将其src属性设置为包含回调函数名的API地址(例如 http://example.com/api/?callback=jsonpCallback)。
- 发送请求:一旦script标签的src属性被赋值,浏览器会自动发起一个GET请求。
- 数据响应:服务器将待返回的数据与回调函数名拼接(格式如“jsonpCallback({name: 'abc'})”)后返回。浏览器接收到响应后,由于请求源是script标签,因此会直接执行该JavaScript代码,即调用jsonpCallback方法并传入数据参数。
接下来,我们将讲解如何使用原生JavaScript实现JSONP功能。
我们在之前封装的ajax()方法中增加对JSONP的支持,后续会将两者整合。JSONP的配置参数主要多出一个jsonp参数,用于指定回调函数的名称。
function ajax(params) {
params = params || {};
params.data = params.data || {};
var json = params.jsonp ? jsonp(params) : json(params);
// jsonp请求
function jsonp(params) {
//创建script标签并加入到页面中
var callbackName = params.jsonp;
var head = document.getElementsByTagName('head')[0];
// 设置传递给后台的回调参数名
params.data['callback'] = callbackName;
var data = formatParams(params.data);
var script = document.createElement('script');
head.appendChild(script);
//创建jsonp回调函数
window[callbackName] = function(json) {
head.removeChild(script);
clearTimeout(script.timer);
window[callbackName] = null;
params.success && params.success(json);
};
//发送请求
script.src = params.url + '?' + data;
//为了得知此次请求是否成功,设置超时处理
if(params.time) {
script.timer = setTimeout(function() {
window[callbackName] = null;
head.removeChild(script);
params.error && params.error({
message: '超时'
});
}, time);
}
};
//格式化参数
function formatParams(data) {
var arr = [];
for(var name in data) {
arr.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name]));
};
// 添加一个随机数,防止缓存
arr.push('v=' + random());
return arr.join('&');
}
// 获取随机数
function random() {
return Math.floor(Math.random() * 10000 + 500);
}
}
需要注意:由于script标签的src属性仅在首次设置时生效,导致script标签无法复用,因此每次请求完成后必须从DOM中移除该标签,以避免内存泄漏或重复请求。
以下是JSONP请求的使用示例,展示了如何配置回调函数名及处理响应。
ajax({
url: 'test', // 请求地址
jsonp: 'jsonpCallback', // 采用jsonp请求,且回调函数名为"jsonpCallbak",可以设置为合法的字符串
data: {'b': '异步请求'}, // 传输数据
success:function(res){ // 请求成功的回调函数
console.log(res);
},
error: function(error) {} // 请求失败的回调函数
});
尽管JSONP实现简单且易于使用,但它存在两个明显的不足之处:
- JSONP是从其他域加载并执行代码,如果该域不安全,可能会夹带恶意代码。此时,除了完全放弃使用JSONP外,开发者难以采取其他措施进行防范或追责。
- 确认JSONP请求是否失败较为困难,因为script标签通常不提供类似XMLHttpRequest的错误回调机制,难以准确捕获网络错误。
以上就是本文关于原生JavaScript实现AJAX及JSONP跨域请求的全部内容,希望这些内容能对您的学习或工作有所帮助,欢迎继续支持本站!
什么时候该用JS构造器:与工厂模式、字面量的选择对比
2026-11-05 16:18:22
推荐几款轻量级JS照片墙开源库及对比
2026-11-05 15:56:19
JavaScript递归写法有哪些常见陷阱及避免方法
2026-11-05 14:53:38
JS数组浅拷贝与深拷贝怎么选?场景与方法总结
2026-11-05 14:41:02
p5.js 中 setup() 和 draw() 函数的执行机制详解
2026-11-05 12:32:38
