一、JS原生Ajax
1、基本概念
- ajax:一种请求数据的方式,不需要刷新整个页面;
- 核心:ajax的技术核心是 XMLHttpRequest 对象;
- ajax 请求过程:创建 XMLHttpRequest 对象、连接服务器、发送请求、接收响应数据;
2、函数封装
前端代码:
ajax({
url: "http://localhost:3000/api/userinfo", //请求地址
type: "POST", //请求方式
data: { name: "super", age: 20 }, //请求参数
dataType: "json",
success: function (response, xml) {
console.log(response)
response = JSON.parse(response) || {}
var temp = [];
if (response.status === 'success') {
var data = response.data;
Object.keys(data).forEach(function(key){
temp.push(''+key+':'+data[key]+'
');
})
document.write(temp.join(''));
}
},
fail: function (status) {
console.error(status);
}
});
function ajax(options) {
options = options || {};
options.type = (options.type || "GET").toUpperCase();
options.dataType = options.dataType || "json";
var params = formatParams(options.data);
//创建 - 非IE6 - 第一步
if (window.XMLHttpRequest) {
var xhr = new XMLHttpRequest();
} else { //IE6及其以下版本浏览器
var xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
//接收 - 第三步
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
var status = xhr.status;
if (status >= 200 && status < 300) {
options.success && options.success(xhr.responseText, xhr.responseXML);
} else {
options.fail && options.fail(status);
}
}
}
//连接 和 发送 - 第二步
if (options.type == "GET") {
xhr.open("GET", options.url + "?" + params, true);
xhr.send(null);
} else if (options.type == "POST") {
xhr.open("POST", options.url, true);
//设置表单提交时的内容类型
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(params);
}
}
//格式化参数
function formatParams(data) {
var arr = [];
for (var name in data) {
arr.push(encodeURIComponent(name) + "=" + encodeURIComponent(data[name]));
}
arr.push(("v=" + Math.random()).replace(".",""));
return arr.join("&");
}
后端逻辑:
//server.js
var http = require('http');
var fs = require('fs');
var querystring = require('querystring');
var server = http.createServer(function(req, res) {
// 前端页面请求路由
if (req.url == "/index") {
fs.readFile('index.html', function(err, data) {
res.writeHead(200, { "Content-type": "text/html;charset=utf-8" });
res.end(data);
});
} else {
fs.readFile('404.html', function(err, data) {
res.writeHead(200, { "Content-type": "text/html;charset=utf-8" });
res.end(data);
});
}
// 请求方法
if (req.method.toLowerCase() === 'post') {
// 接口路由
if(req.url == "/api/userinfo") {
var reqData = '';
req.on('data', function (chunk) {
reqData += chunk;
});
req.on('end', function () {
//将字符串转换位一个对象
console.log(reqData); //name=super&age=20&v=0865843628884519
var dataString = reqData.toString();
//将接收到的字符串转换位为json对象
var dataObj = querystring.parse(dataString);
dataObj.sex = '男'
//输出数据
var data={
status:'success',
message:'获取数据成功',
data:dataObj
};
res.writeHead(200, { "Content-Type": "application/json;charset=UTF-8" });
res.end(JSON.stringify(data));
});
}
};
});
server.listen(3000, '127.0.0.1');
console.log('启动服务,监听 127.0.0.1:3000');
3、函数解析
(1)创建
- IE7及其以上版本中支持原生的 XHR 对象,因此可以直接用: var oAjax = new XMLHttpRequest()
- IE6及其之前的版本中,XHR对象是通过MSXML库中的一个ActiveX对象实现的。有的书中细化了IE中此类对象的三种不同版本,即MSXML2.XMLHttp、MSXML2.XMLHttp.3.0和MSXML2.XMLHttp.6.0;若感觉太麻烦,可以直接使用下面的语句创建: var oAjax=new ActiveXObject('Microsoft.XMLHTTP')
(2)连接和发送
- open()函数的三个参数:请求方式、请求地址、是否异步请求(同步请求的情况极少)
- GET 请求方式是通过URL参数将数据提交到服务器的,POST则是通过将数据作为 send 的参数提交到服务器
- POST 请求中,在发送数据之前,要设置表单提交的内容类型
- 提交到服务器的参数必须经过 encodeURIComponent() 方法进行编码,实际上在参数列表”key=value”的形式中,key 和 value 都需要进行编码,因为会包含特殊字符。每次请求的时候都会在参数列表中拼入一个 “v=xx” 的字符串,这样是为了拒绝缓存,每次都直接请求到服务器上
要点:
- encodeURI() :用于整个 URI 的编码,不会对本身属于 URI 的特殊字符进行编码,如冒号、正斜杠、问号和井号;其对应的解码函数 decodeURI()
- encodeURIComponent():用于对 URI 中的某一部分进行编码,会对它发现的任何非标准字符进行编码;其对应的解码函数 decodeURIComponent()
(3)接收
-
接收到响应后,响应的数据会自动填充XHR对象,相关属性如下
- responseText:响应返回的主体内容,为字符串类型;
- responseXML:如果响应的内容类型是 "text/xml" 或 "application/xml",这个属性中将保存着相应的xml 数据,是 XML 对应的 document 类型;
- status:响应的HTTP状态码;
- statusText:HTTP状态的说明;
-
XHR对象的readyState属性表示请求/响应过程的当前活动阶段,这个属性的值如下
- 0-未初始化,尚未调用open()方法;
- 1-启动,调用了open()方法,未调用send()方法;
- 2-发送,已经调用了send()方法,未接收到响应;
- 3-接收,已经接收到部分响应数据;
- 4-完成,已经接收到全部响应数据;
在readystatechange事件中,先判断响应是否接收完成,然后判断服务器是否成功处理请求,xhr.status 是状态码,状态码以2开头的都是成功,304表示从缓存中获取,上面的代码在每次请求的时候都加入了随机数,所以不会从缓存中取值,故该状态不需判断
要点
- ajax请求是不能跨域的
- 只要 readyState 的值变化,就会调用 readystatechange 事件,(其实为了逻辑上通顺,可以把readystatechange放到send之后,因为send时请求服务器,会进行网络通信,需要时间,在send之后指定readystatechange事件处理程序也是可以的,但为了规范和跨浏览器兼容性,建议在open之前进行指定)
二、Fetch API
Fetch API提供了一个fetch()方法,它被定义在BOM的window对象中,你可以用它来发起对远程资源的请求。 该方法返回的是一个Promise对象,让你能够对请求的返回结果进行检索。
1、Fetch 基本用法
fetch()方法,包含了需要fetch 的网址和对应的属性设定( 例如method、headers、mode、body...等,最基本的写法属性不一定要填),执行之后会送出Request,如果得到回应就会回传带有Response 的Promise 内容,使用then 将回传值传递下去。
fetch('网址')
.then(function(response) {
// 处理 response
}).catch(function(err) {
// 错误处理
});
2、Fetch 的 Request 属性
以下列出Fetch常用的的Request属性。
属性 | 设定值 |
---|---|
url | 第一个参数,必填项,代表需要fetch对象的网址 |
method | GET、POST、PUT、DELETE、HEAD ( 默认GET ) |
headers | 设置相关的Headers 内容( 预设{} ) |
mode | cors、no-cors、same-origin、navigate ( 默认cors ) |
referrer | no-referrer、client 或某个网址( 默认client ) |
credentials | omit、same-origin、include ( 默认omit ) |
redirect | follow、error、manual ( 默认manual ) |
cache | default、no-store、reload、no-cache、force-cache ( 默认default ) |
3、Fetch 的Response 属性
以下列出Fetch常用的Response属性。
属性 | 设定值 |
---|---|
headers | 包含与response 相关的Headers 内容 |
ok | 成功返回true,不成功返回false |
status | 状态代码,成功为200 |
statusText | 状态信息,成功为ok |
type | response 的类型,例如basic、cors...等 |
url | response 的url |
4、Fetch 的Response 方法
以下列出Fetch常用的Response方法。
属性 | 设定值 |
---|---|
json() | 返回Promise,resolves 是JSON 对象 |
text() | 返回Promise,resolves 是text string |
blob() | 返回Promise,resolves 是blob ( 非结构化对象,例如文字或二进制信息) |
arrayBuffer() | 返回Promise,resolves 是ArrayBuffer ( 有多少bytes ) |
formData() | 返回Promise,resolves 是formData ( 表单资料对应的的Key 或Value ) |
clone() | 创建一个Response对象的克隆。 |
error() | 返回Response 的错误内容 |
5、Fetch 的Get 用法
Get 是Fetch 最简单的方法,使用Get 必须要将fetch 第二个参数里的method 设定为get,如果遇到跨域问题,就搭配其他属性例如mode、credentials 来进行细部设定(但针对非跨域的就没用了),下方的示例做了一个简单的后端请求,通过fetch 传递姓名和年纪的参数,就会看到后端回应一串文字。
前端代码:
const name = 'ooxx';
const age = 18;
const uri = `http://127.0.0.1:3000/api/fetch/userinfo?name=${name}&age=${age}`;
fetch(uri, {method:'GET'})
.then(res => {
return res.text();// 使用 text() 可以得到纯文字 String
}).then(result => {
console.log(result); // 得到「你的名字是:ooxx,年纪:18 岁」
});
后端逻辑:
var pathname = url.parse(req.url).pathname;
if (pathname == "/api/fetch/userinfo") {
var reqData = url.parse(req.url).query;
var dataStr = querystring.parse(reqData);
res.writeHead(200, { "Content-Type": "application/json;charset=UTF-8" });
res.end('你的名字是:'+dataStr.name +',年纪:'+dataStr.age+' 岁');
}
6、Fetch 的Post 用法
使用POST方法可以搭配body属性设定传递参数,比如我的接口地址,可以接收name和age所组成的JSON请求,当网址接收到要求后,就会回应一个json对象,需要注意的是,如果是传递「中文」可能会出现乱码,这时可以使用encodeURI来做转码,且要通过JSON.stringify来转换成string方式传递。
前端代码:
fetch(uri, {
method:'POST',
body:encodeURI(JSON.stringify({
name:'ooxx',
age:18
})),
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
}
})
.then(res => {
return res.json(); // 使用 json() 可以得到 json 对象
}).then(result => {
console.log(result);
// 得到 {name: "ooxx", age: 18, text: "你的名字是 ooxx,年纪18岁~"}
});
后端逻辑:
if(req.url == "/api/fetch/userinfo") {
var reqData = '';
req.on('data', function (chunk) {
reqData += chunk;
});
req.on('end', function () {
//将字符串转换位一个对象
var dataObj = JSON.parse(decodeURI(reqData.toString()));
var data = {
name: dataObj.name,
age: dataObj.age,
text: '你的名字是 '+dataObj.name+',年纪'+dataObj.age+'岁~'
}
res.writeHead(200, { "Content-Type": "application/json;charset=UTF-8" });
res.end(JSON.stringify(data));
});
}
7、Fetch 搭配async、await、promise.all
过去在XMLHttpRequest 或jQuery AJAX 的全盛时期,如果要确保每个GET 或POST 的要求,都要按照指定的顺序进行,往往会用上一连串的callback 辅助,但是当callback 越来越多,代码也就越来越难管理,然而fetch 返回的是一个Promise,我们也就能直接利用await 或promise.all 的作法,轻松掌握同步与非同步之间的转换。
下方的例子是一个非同步的示例,因为没有进行任何的同步处理,所以执行之后,会先出现hello的文字,接着才是通过fetch 得到的结果。
const postURL = (name,age) => {
return fetch(uri, {
method:'POST',
body:encodeURI(JSON.stringify({
name:name,
age:age
})),
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
}
})
.then(res => {
return res.json();
}).then(result =>{
console.log(result);
});
};
postURL('Lily',18);
console.log('hello!!!');
postURL('Tom',18);
因为fetch 的特性,可以改成async 和await 的写法,执行后也就能按照我们要的顺序进行。
async function fetchAll () {
await Promise.all([postURL('Lily',18), postURL('Tom',18)]);
console.log('hello!!!');
}
fetchAll()
8、兼容性
关于Fetch API的兼容性,现代浏览器大部分还是支持的,可以放心使用,如下图所示:
Fetch API 的神奇,简化了许多原本较为复杂的用法,也让项目代码写起来更加干净易读好维护。更重要的是 JavaScript ES6 原生支持,你不需要安装任何依赖包,直接可以在项目中使用。