前言
有时候会刷一些面试题
今天刷到一题,让手写ajax:
我当时的第一反应是:老子不会😂
...
回想一下,为什么会有这个反应?主要有两点
-
工作中都是直接使用
jquery
、axios
或者fetch
等等 -
像这种默写
API
的题,觉得没什么意义
就像手写ajax
,只要记清楚原生xhr
的几个判断,send
/open
/abort
和readyState
就可以了
类似的题其实很多很多,很多面试都会考察API的使用,但我就是不喜欢
老问这样的问题,我就不相信,出这些题的公司的工程师都能把所有的原生 API
默写出来,而不上网查
非常赞同尤大之前说过一个观点:
技术面试如果足够有诚意就让人拿出笔记本当场写,随便查,你才能看到面试者在真实的写代码的时候是什么状态,什么思路。就看面试者去哪里查,怎么查,你就能得到比让人默写 API 要有价值得多的信息
个人也比较喜欢这种方式
...
但是到这,发现还不够500
字
那今天就来细说ajax
吧,就当重新复习复习,当以后如果真被面,而且还记不太清楚了,那就直接翻开此文!
文中作者给了答案,Promise
版本的,如下:
const getJSON = function (url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url, false);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) return;
if (xhr.status === 200 || xhr.status === 304) {
resolve(xhr.responseText);
} else {
reject(new Error(xhr.responseText));
}
};
xhr.send();
});
};
那我们来实现一个更详细的、更原始的版本,一次性彻底搞清楚AJAX
手写实现
AJAX是Asynchronous JavaScript And XML的简称,它允许我们在不刷新整个页面的情况下,就可以异步获取数据,并更新页面的部分内容。具体的实现步骤如下:
创建一个异步对象
var xmlhttp;
if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}else {// code for IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
通过XMLHttpRequest
构造函数创建一个异步对象xmlhttp
, IE6, IE5 使用ActiveXObject
创建,创建的这个异步对象上有很多属性和方法,常用的有:
onreadystatechange
监听异步对象请求状态码readyState
的改变,每当readyState
改变时,就会触发onreadystatechange
事件
readyState
:请求状态码
readyState
表示异步对象目前的状态,状态码从0到4:
0
: 表示请求未初始化,还没有调用 open()
1
: 服务器连接已建立,但是还没有调用 send()
2
: 请求已接收,正在处理中(通常现在可以从响应中获取内容头)
3
: 请求处理中,通常响应中已有部分数据可用了,没有全部完成
4
: 当readyState状态码为4时,表示请求已完成;此阶段确认全部数据都已经解析完毕,可以通过异步对象的属性获取对应数据
status
:http状态码
http状态码表示成功的http状态码有xmlhttp.status >= 200 && xmlhttp.status < 300 || xmlhttp.status == 304
-
responseText
:后台返回的字符串形式的响应数据 -
responseXML
:后台返回的XML
形式的响应数据
设置请求方式和请求地址
创建异步对象之后,通过open()
方法设置ajax
请求方式和请求地址
格式:xmlhttp.open("GET/POST","ajax-get.txt",true);
第一个参数:请求的类型;GET
还是 POST
第二个参数:表示请求的文件的地址url
第三个参数:设置请求方法是不是异步async
,true
为异步, false
为同步。AJAX
存在的意义就是发异步请求,所以第三个参数永远传true
这里有个问题,就是IE
中的缓存问题
在IE
浏览器中,如果通过Ajax
发送GET
请求,那么IE
浏览器认为,同一个URL只有一个结果,如果地址没有发生变化,它就会把上一次返回的结果,直接返回
。这样我们不能实时的拿到变化后的数据。如果要想我们拿到实时数据,必须保证每次的URL都是不一样的,有两种方式:
-
Math.random()
-
new Date().getTime()
即在请求地址后面拼接上?t=随机数或者1970.01.01至当前的毫秒数
所以在IE
中通过ajax
发送get
请求时,可以设置请求地址为:
xmlhttp.open("GET","ajax-get.txt?t=" + (new Date().getTime()),true);
//或
xmlhttp.open("GET","ajax-get.txt?t=" + Math.random(),true);
发送请求
直接通过异步对象的send()发送请求
xmlhttp.send();
特别注意的是: 如果发送POST
请求,请使用setRequestHeader()
来添加 HTTP
请求头,并在send()
方法中传递要发送的数据:
xmlhttp.open("POST","ajax_test.html",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("fname=Henry&lname=Ford");
通过onreadystatechange监听状态变化
每当异步对象的readyState
发生改变,就会触发onreadystatechange
函数,当readyState
变成为4
时,表示当前状态是请求完毕的状态,同时当http
的响应码status
为200
到300
之间(包括200
和300
)或为304
时,表示ajax
请求成功;当http状态码不是200
到300
之间的数也不是304
时,表示请求不成功
//4.监听状态变化
xmlhttp.onreadystatechange = function(){
// 判断当前状态改变是请求完毕的状态吗
if (xmlhttp.readyState === 4) {
if (xmlhttp.status >= 200 && xmlhttp.status < 300 || xmlhttp.status == 304) {
console.log("成功的接收到服务器返回的数据");
}else{
console.log("不成功!");
}
}
}
处理返回的结果
如果成功,可通过异步对象的responseText
属性来获取服务器返回的字符串
console.log(xmlhttp.responseText);
根据上述情况,如果每次请求都要手写以上五个步骤,那很麻烦,接下来,我们来封装一个方法ajax()
用于发送请求
封装的时候,需要注意
-
URL
当中只能出现字母 数字 下划线和ASCII
码,不能出现中文,可以使用encodeURIComponent()
转码 -
当我们利用我们的ajax放的发送一个请求到远处服务器时,我们需要等待远程服务器去响应我们的请求,等待远程服务器将响应的结果返回给我们,但是这个响应的速度是不确定的,因为响应的速度是由本地网络和远程服务器的网速等共同决定的,所以我们不可能一直等待服务器的响应。那么这里我们需要新增另外一个功能:设置超时时间的功能,即告诉它我的请求会等待多长的时间,如果这么长的时间内都没有响应我们发送的请求,那我就在这里自动的终止这次请求;
function ajax(type, url, obj, timeout, success, error) {
// 0.将对象转换成字符串
var str = objToString(obj);
// 1.创建一个异步对象xmlhttp;
var xmlhttp, timer;
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
} else {// code for IE6, IE5
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
// 2.设置请求方式和请求地址;
// 判断请求的类型是POST还是GET
if (type === 'GET') {
xmlhttp.open(type, url + "?t=" + str, true);
// 3.发送请求;
xmlhttp.send();
} else {
//如果是post请求,需要设置请求头
xmlhttp.open(type, url, true);
// 注意:在post请求中,必须在open和send之间添加HTTP请求头:setRequestHeader(header,value);
xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
// 3.发送请求;
xmlhttp.send(str);
}
// 4.监听状态的变化;
xmlhttp.onreadystatechange = function () {
clearInterval(timer);
if (xmlhttp.readyState === 4) {
if (xmlhttp.status >= 200 && xmlhttp.status < 300 || xmlhttp.status == 304) {
// 5.处理返回的结果;
success(xmlhttp);//成功后回调;
} else {
error(xmlhttp);//失败后回调;
}
}
}
//处理obj
function objToString(obj) {
obj.t = new Date().getTime();
var res = [];
for (var key in obj) {
//需要将key和value转成非中文的形式,因为url不能有中文。使用encodeURIComponent();
res.push(encodeURIComponent(key) + " = " + encodeURIComponent(obj[key]));
}
return res.join("&");
}
//判断外界是否传入了超时时间
if (timeout) {
timer = setInterval(function () {
xmlhttp.abort();//中断请求
clearInterval(timer);
}, timeout);
}
}
完善问题
在使用自己封装的代码的时候,感觉跟jQuery
官方的ajax
还是有一定的差异,所以还需要进一步完善
首先看第一个问题,传递多个参数,需要保持传递顺序。解决方案是可以改写成传递的是一个对象;因为对象里面的值,传递的是一个对象就不用考虑先后顺序,里面用的参数通过对象名.属性名
的形式获取
第二,传递请求类型的区分大小写,jQuery
官方的是大小写都可以;解决方案是可以使用toLowerCase()
或者toUpperCase()
将类型转成大写或小写再对比;
第三,我们传递的数据用的名字是obj
,jQuery
官方用的是data
,语义更适合。所以将传递数据obj
改成data
完善如下:
function ajax(option) {//type,url,obj,timeout,success,error将所有参数换成一个对象{}
// 0.将对象转换成字符串
var str = objToString(option.data);
// 1.创建一个异步对象xmlhttp;
var xmlhttp, timer;
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
} else {// code for IE6, IE5
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
// 2.设置请求方式和请求地址;
// 判断请求的类型是POST还是GET
if (option.type.toLowerCase() === 'get') {
xmlhttp.open(option.type, option.url + "?t=" + str, true);
// 3.发送请求;
xmlhttp.send();
} else {
xmlhttp.open(option.type, option.url, true);
// 注意:在post请求中,必须在open和send之间添加HTTP请求头:setRequestHeader(header,value);
xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
// 3.发送请求;
xmlhttp.send(str);
}
// 4.监听状态的变化;
xmlhttp.onreadystatechange = function () {
clearInterval(timer);
if (xmlhttp.readyState === 4) {
if (xmlhttp.status >= 200 && xmlhttp.status < 300 || xmlhttp.status == 304) {
// 5.处理返回的结果;
option.success(xmlhttp);//成功后回调;
} else {
option.error(xmlhttp);//失败后回调;
}
}
}
//处理obj
function objToString(data) {
data.t = new Date().getTime();
var res = [];
for (var key in data) {
//需要将key和value转成非中文的形式,因为url不能有中文。使用encodeURIComponent();
res.push(encodeURIComponent(key) + " = " + encodeURIComponent(data[key]));
}
return res.join("&");
}
//判断外界是否传入了超时时间
if (option.timeout) {
timer = setInterval(function () {
xmlhttp.abort();//中断请求
clearInterval(timer);
}, timeout);
}
}
以上就是ajax
基本实现,到这面试应该问题不大了吧!实际工作中请用人家封装好的~
最后,希望:以后如果遇到需要默写API的,希望大家允许我上网,因为我记不住😂
好了,本次内容到这结束!
如果有问题或者你也有相同经历,欢迎留言分享~
END~