二十四、用js写一个ajax的原生实现方法

161 阅读3分钟

一、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('<span>'+key+':'+data[key]+'</span><br/>');

})

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的兼容性,现代浏览器大部分还是支持的,可以放心使用,如下图所示:

24-1.png

Fetch API 的神奇,简化了许多原本较为复杂的用法,也让项目代码写起来更加干净易读好维护。更重要的是 JavaScript ES6 原生支持,你不需要安装任何依赖包,直接可以在项目中使用。