Ajax基础知识详解

64 阅读9分钟

1.Ajax,Fetch与跨域请求

1. Ajax基础

1.1 初始Ajax

1.Ajax是什么?

Ajax是Asynchronous JavaScript and XML (异步JavaScript和XML) 的简写

Ajax中的异步:可以异步地向服务器发送请求,在等待响应的过程中,不会阻塞当前页面,浏览器可以做自己的事情,直到成功获取响应后,浏览器才开始处理响应数据

XML(可扩展标记语言) :是前后端数据通信时传输的一种格式

XML现在不怎么用了,现在比较常用的时JSON

Ajax其实就是浏览器与服务器之间的一种异步通信方式;使用Ajax可以在不重新加载整个页面的情况下,对页面的某个部分进行更新

举例子:1.注册时检测手机号 2.搜索提示...

2.搭建Ajax开发环境

Ajax需要服务器环境,非服务器环境下,很多浏览器无法正常使用Ajax

vscode插件:Live Server插件安装,右键Open with Live Server打开html文件

1.2 Ajax的基本用法

1.XMLHttpRequest

Ajax想要实现浏览器与服务器之间的异步通信,需要依靠XMLHttpRequest,它是一个构造函数

2.Ajax的使用步骤

2.1 创建xhr对象【new XMLHttpRequest()】
const xhr=new XMLHttpRequest()
​
2.2 准备发送请求【.open(http请求方法,请求地址,是否使用异步请求)】

调用open并不会真正发送请求,而只是做好发送请求前的准备工作

const xhr=new XMLHttpRequest()
//发起请求
xhr.open('HTTP方法:GTE,POST,PUT,DELETE',地址 url,是否使用异步方式true)
​
2.3 发送请求【.send()返回的是服务器响应的数据,传递的参数一般是字符串】

send()参数是通过请求体携带数据,get请求不需要传(一般传null),post需要传请求体

const xhr=new XMLHttpRequest()
//准备发起请求
xhr.open('HTTP方法:GTE,POST,PUT,DELETE',地址 url,是否使用异步方式true)
//发送请求
xhr.send()//get请求可以传null,post请求传入携带的数据
2.4 监听事件,来处理响应【xhr.readystatechange(){}监听xhr.readyState的值&判断xhr.status(http状态码)】

当获取到响应后,会触发xhr对象的readystatechange事件,可以在该事件中对响应进行处理

xhr.readystatechange事件监听xhr.readyState的状态变化,它的值有0~4,一共5个状态,每一次状态变化都会触发readystatechange这个事件

0:未初始化。尚未调用open()

1:启动。已经调用open(),但尚未调用send()

2:发送。已经调用send(),但是尚未接到响应

3:接收。已经接收到部分响应

4:完成。已经接收到全部响应数据,而且已经可以在浏览器中使用了

获取响应数据后,响应内容会自动填充到xhr对象的属性

xhr.status就是http code,状态码代表含义如下:

100~199 消息:代表请求已经被接受,需要继续处理

101:websocket Vscode的插件live Server

200~299 成功:请求响应成功

200

300~399 重定向

301:永久重定向,重定向的地址会被浏览器缓存,除非你手动清除缓存

302:临时重定向,每一次都会向服务器确定一下,接下来向哪里跳转

304:没有修改,本地有一份缓存,浏览器向服务器发送请求,确认过没过期,表示文件没有被修改还是以前的文件

400~499 请求错误【这个错误一般是在前端】

404没有找到

500~599 服务器错误

500

xhr.statusText:HTTP状态说明(OK ,Not Found)

xhr.responseText:得到响应的数据,数据的字符串形式

const xhr=new XMLHttpRequest()
​
//监听响应
xhr.addEventListener("readystatechange",()=>{})
或
xhr.onreadysatechange=()=>{
    //当readyState==4时,响应数据接收完毕
    if(xhr.readyState!==4)return;
    
    //判断请求服务器是否出现错误 HTTP CODE
    if((xhr.status>=200 & xhr.status<300)||xhr.status===304){
        console.log("正常使用响应数据了")
        console.log(xhr.responseText)
    }
}
​
​
//准备发起请求
xhr.open('HTTP方法:GTE,POST,PUT,DELETE',地址 url,是否使用异步方式true)
//发送请求
xhr.send()//get请求可以传null,post请求传入携带的数据

3.使用Ajax完成前后端通信

这里自己可以搭建一个后台服务,模拟前后端通信,我这里借用的时nodejs+express搭建的,下面的代码可能会报错,因为会有跨域的问题,我测试的使用是后端处理的跨域。

<script>
    const xhr=new XMLHttpRequest()
​
    xhr.onreadystatechange=()=>{
      if(xhr.readyState!==4)return;
​
      if((xhr.status>=200&&xhr.status<300)||xhr.status===304){
        console.log("拿到响应数据了",xhr.responseText)
        console.log("拿到响应数据的数据类型:",typeof xhr.responseText)//string
      }
    }
​
    xhr.open("get","http://localhost:3000/list",true)
    xhr.send(null)
</script>

1.3 GET请求

1.携带数据

GET请求不能通过请求体携带数据,但可以通过请求头携带

//表单提交 需要带name属性
<form action="http://localhost:3000/list" method="get">
    <input type="text" name="username">
    <input type="password" name="password">
    <input type="submit" value="提交">
</form>
  
<script>
    const xhr=new XMLHttpRequest()
​
    xhr.onreadystatechange=()=>{
      if(xhr.readyState!==4)return;
​
      if((xhr.status>=200&&xhr.status<300)||xhr.status===304){
        console.log("拿到响应数据了",xhr.responseText)
        console.log("拿到响应数据的数据类型:",typeof xhr.responseText)//string
      }
    }
​
    xhr.open("get","http://localhost:3000/list?id=1&name=lisi",true)
    xhr.send(null)
</script>

2.数据编码

如果携带的数据是非英文的话,比如说汉字,就需要编码之后再发送给后端了,不然会造成乱码问题,可以使用encodeURIComponent()编码

<script>
    const xhr=new XMLHttpRequest()
​
    xhr.onreadystatechange=()=>{
      if(xhr.readyState!==4)return;
​
      if((xhr.status>=200&&xhr.status<300)||xhr.status===304){
        console.log("拿到响应数据了",xhr.responseText)
        console.log("拿到响应数据的数据类型:",typeof xhr.responseText)
      }
    }
    //数据编码
    xhr.open("GET",`http://localhost:3000/list?id=1&name=${encodeURIComponent('前端')}`,true)
    
    xhr.send(null)
</script>

1.4 POST请求

1.携带数据【send(请求体携带的数据)】

POST请求主要通过请求体携带数据,同时也可以通过请求头写携带数据

如果想发送数据,直接写再send()参数位置,一般是字符串,不能直接传递对象,需要先将对象转换为字符串的形式【JSON.stringify()】

<script>
    const xhr=new XMLHttpRequest()
​
    xhr.onreadystatechange=()=>{
      if(xhr.readyState!==4)return;
​
      if((xhr.status>=200&&xhr.status<300)||xhr.status===304){
        console.log("拿到响应数据了",xhr.responseText)
        console.log("拿到响应数据的数据类型:",typeof xhr.responseText)
      }
    }
    //数据编码
    xhr.open("POST",`http://localhost:3000/list?id=1&name=${encodeURIComponent('前端')}`,true)
    
    //传二种格式--
    //xhr.send('username=lisi&age=18')//第一种:类似于form表单的POST请求
    xhr.send({
        username:'elen',
        age:18
    })//第二种:[object Object]字符串
</script>

2.数据编码

<script>
    const xhr=new XMLHttpRequest()

    xhr.onreadystatechange=()=>{
      if(xhr.readyState!==4)return;

      if((xhr.status>=200&&xhr.status<300)||xhr.status===304){
        console.log("拿到响应数据了",xhr.responseText)
        console.log("拿到响应数据的数据类型:",typeof xhr.responseText)
      }
    }
    
    xhr.open("POST",`http://localhost:3000/list?category=${encodeURIComponent('前端')}`,true)
    //数据编码
    xhr.send(`username=${encodeURIComponent('张三')}&age=18`)
</script>

2. JSON

JSON必须是双引号

2.1 初识JSON

1.JSON是什么

Ajax发送和接收数据的一种格式;XML,username=alen&age=18,JSON

JSON全称是JavaScript Object Notation

JSON格式--这一种长得像js的对象
{
	"code":200,
	"msg":'数据响应成功'
	"data":[
		{
			"age":18
		}
	]
}

2.为什么需要JSON

JSON有三种形式,每种形式的写法都和 JS 中的数据类型很像,可以很轻松的和 JS的数据类型互相转换

JS---->JSON---->PHP/Java

PHP/Java--->JSON----->JS

利于前后端之间相互通信

2.2 JSON的三种形式【简单值形式,对象形式,数组形式】

1.简单值形式【JSON的简单值形式就对应着JS中基础数据类型】

JSON的简单值形式就对应着JS中基础数据类型(数字,字符串,布尔值,null,undefined除外)

注意事项:

JSON中没有undefined值,

JSON中字符串必须使用双引号,

JSON中是不能有注释的

//index.json文件
5
或者
"str"
或者
true
或者
null
...

2.对象形式【JSON的对象形式就对应着JS的对象】

JSON的对象形式就对应着JS的对象

注意事项:

JSON对象的属性名必须用双引号,属性值如果是字符串也必须用双引号;

JSON中只要涉及到字符串,就必须是双引号

不支持undefined

//举例子
{
  "data":{
    "name":"张三",
    "age":18
  },
  "hobby":["足球","篮球"],
  "isMale":true
}

3.数组形式【JSON的数组形式就对应着JS的数组】

注意事项:

数组中字符串必须用双引号

JSON中只要设计到字符串,就必须使用双引号

不支持undefined

[
  {
    "name":"芜湖",
    "age":18
  },
  18,
  "你好",
  true,
  ["你好",99,null,false]
]

2.3 JSON的常用方法【JSON.parse()与JSON.stringify】

1.JSON.parse()【将JSON格式的字符串转化为JS中对应的数据类型】

将JSON格式的字符串转化为JS中对应的数据类型

const xhr=new XMLHttpRequest()

xhr.onreadystatechange=()=>{
  if(xhr.readyState!==4)return;

  if((xhr.status>=200&&xhr.status<300)||xhr.status===304){
    const data=JSON.parse(xhr.responseText)
    console.log("JSON格式的数据转换为对应的JS数据类型",data,typeof data)
  }
}

xhr.open("GET","./index.json",true)
xhr.send(null)

2.JSON.stringify()【将JS的数据类型转化为JSON格式的形式】

将JS的数据类型转化为JSON格式的形式

const xhr=new XMLHttpRequest()

xhr.onreadystatechange=()=>{
  if(xhr.readyState!==4)return;

  if((xhr.status>=200&&xhr.status<300)||xhr.status===304){
    const data=JSON.parse(xhr.responseText)
    console.log("JSON格式的数据转换为对应的JS数据类型",data,typeof data)
  }
}

xhr.open("GET","./index.json",true)

xhr.send(JSON.stringify({
	username:'alen',
	age:19
}))

3.JSON.parse() 和 JSON.stringify() 封装 localStorage(值需要是字符串)

//storage.js文件

const storage=window.localStorage
//设置
const set=(key,value)=>{
	storage.setItem(key,JSON.stringify(value))
}

//读取
const get=(key)=>{
	return storage.getItem(JSON.parse(key))
}

//删除
const remove=(key)=>{
	storage.removeItem(key)
}

//删除全部
const clear=()=>{
	storage.clear()
}

export {set,get,remove,clear}



//index.html
<script type='module'>
	import {get,set,remove,clear} from './storage.js'
	set("username","alen")
	console.log(get("username"))
	...
</script>

3. 跨域

3.1 初识跨域

1.跨域是什么?【不同协议||不同域名||不同端口号】

向一个域发送请求,如果要请求的域和当前域不是同域,就叫跨域;不同域之间请求,就是跨域请求

//同域,不是跨域
//search.html文件
<body>
	<h1>同域</h1>
</body>


//index.html文件
<script>
	const url='./search.html'
	const xhr=new XMLHttpRequest()
	xhr.onreadystatechange=()=>{
		if(xhr.readyState!==4)return;
		if((xhr.status>=200&&xhr.status<300)||xhr.status==304){
			console.log(xhr.responseText)
		}
	}
	xhr.open("GET",url,true)
	xhr.send(null)
</script>

2.什么是不同域,什么是同域?

举例子:https【协议】://www.rongming.top【域名或者主机IP】:443【端口号】/course/list【路径】

协议,域名,端口号,任何一个不一样,就是不同域; 与路径无关,路径不一样无所谓

3.跨域请求为什么会被阻止?

阻止跨域请求,其实是浏览器本身的一种安全策略---同源策略

其他客户端或者服务器都不存在跨域被阻止的问题

4.跨域解决方案

1.CORS跨域资源共享

2.JSONP (script标签)

优先使用CORS跨域资源共享,如果浏览器不支持CORS的话,在使用JSONP

3.2 CORS跨域资源共享

1.CORS是什么

跨域资源共享,后端解决

Access-Control-Allow-Origin: * ;表明允许所有的域名来跨域请求它,*是通配符,没有任何限制

只允许指定域名的跨域请求:Access-Control-Allow-Origin:http://127.0.0.1:3000

2.使用CORS跨域的过程【后端解决】

2.1 浏览器发送跨域请求

2.2 后端再响应头中添加 Access-Control-Allow-Origin 头信息

2.3 浏览器接收到响应

2.4 如果是同域下的请求,浏览器不会额外做什么,这次前后端通信就圆满完成了

2.5 如果是跨域请求,浏览器会从响应头中查找是否允许跨域访问

2.6 如果允许跨域,通信圆满完成

2.7 如果没找到或不包含想要跨域的域名,就丢弃响应结果

3.CORS的兼容性

IE10及以上版本的浏览器可以正常使用CORS

关于跨域推荐网址:caniuse.com/

3.3 JSONP

1.JSONP的原理

script标签跨域不会被浏览器阻止,JSONP主要就是利用script标签,加载跨域文件

2.使用JSONP实现跨域【前声明后调用】

前端script标签声明一个函数;后端写一个JSONP接口,是前端声明的函数调用,调用时传入参数即可;这样前端就可以获取到后端传过来的数据

<script>
//2.动态加载
const script=document.createElement("script")
script.src="http://static-d0fd619d-b424-42b7-9eb4-12d4070b76d1.bspapp.com/jsonp.js"
document.body.appendChild(script)


//1.声明函数-对应后端JSONP的函数调用
const handleResponse=data=>{
	console.log(data)
}
</script>

//1.手动加载
//后端JSONP接口
<script src="http://static-d0fd619d-b424-42b7-9eb4-12d4070b76d1.bspapp.com/jsonp.js"></script>

4. XHR对象

4.1 XHR的属性

1.responseType 和response属性【响应有关】

xhr.responseText:文本形式的响应内容,只能在没有responseType或者responseType=""或"text"的时候才能使用

xhr.response:可以替代responseText

xhr.responseType:响应的数据类型,准备发送请求之后,发送请求之前

responseType和responseIE6~9不支持,IE10开始支持

<script>
	const url='http://localhost:3000/list'
	
	const xhr=new XMLHttpRequest()
	
	xhr.onreadystatechange=()=>{
		if(xhr.readyState!==4)return;
		if((xhr.status>=200&&xhr.status<300)||xhr.status==304){
			//文本形式的响应内容
			//console.log(xhr.responseText)
			
			//响应数据类型不是文本使用xhr.response
			console.log(xhr.response)
		}
	}
	
	xhr.open("GET",url,true)
	
	//准备之后,发送之前
	//设置响应类型
	//xhr.responseType=""//默认为空
	//xhr.responseType="text"//字符串文本类型
	xhr.responseType="json"//直接要解析过的JSON类型
	
	xhr.send(null)
</script>

2.timeout属性【设置请求超时时间】

设置请求的超时时间(单位:ms);IE6~7不支持,IE8开始支持

<script>
	const url='https://www.imooc.com/api/http/search/suggest?words=js'
	
	const xhr=new XMLHttpRequest()
	
	xhr.onreadystatechange=()=>{
		if(xhr.readyState!==4)return;
		if((xhr.status>=200&&xhr.status<300)||xhr.status==304){
		
			console.log(xhr.response)
		}
	}
	
	xhr.open("GET",url,true)
	
	//准备之后,发送之前
	xhr.timeout=10000
	
	xhr.send(null)
</script>

3.withCredentials属性【请求Cookie有关】

指定使用Ajax发送请求的时候是否携带Cookie;使用Ajax发送请求,默认情况下,同域时,会携带Cookie,跨域时,不会,最终能否成功跨域携带Cookie,还要看服务器同不同意

xhr.withCredentials=true

后端:Access-Control-Allow-Origin:指定前端访问的域名

IE6~9不支持,IE10开始支持

<script>
	const url='https://www.imooc.com/api/http/search/suggest?words=js'
	
	const xhr=new XMLHttpRequest()
	
	xhr.onreadystatechange=()=>{
		if(xhr.readyState!==4)return;
		if((xhr.status>=200&&xhr.status<300)||xhr.status==304){
		
			console.log(xhr.response)
		}
	}
	
	xhr.open("GET",url,true)
	
	
	//准备之后,发送之前
	//响应类型
	//xhr.responseType=""//默认为空
	//请求超时时间
	xhr.timeout=10000
	//解决跨域携带Cookie
	xhr.withCredentials=true
	
	xhr.send(null)
</script>

4.2 XHR的方法

1.abort()【终止当前请求】

一般配合abort事件一起使用

<script>
	const url='https://www.imooc.com/api/http/search/suggest?words=js'
	
	const xhr=new XMLHttpRequest()
	
	xhr.onreadystatechange=()=>{
		if(xhr.readyState!==4)return;
		if((xhr.status>=200&&xhr.status<300)||xhr.status==304){
			console.log(xhr.response)
		}
	}
	
	xhr.open("GET",url,true)
	
	
	//准备之后,发送之前
	//响应类型
	//xhr.responseType=""//默认为空
	//请求超时时间
	xhr.timeout=10000
	//解决跨域携带Cookie
	xhr.withCredentials=true
	
	xhr.send(null)
	
	//发送完请求之后调用
	xhr.abort()//终止当前请求
</script>

2.setRequestHeader()【设置请求头信息】

可以设置请求头信息;xhr.setRequestHeader(头部字段名称,头部字段的值)

请求头中的Content-Type字段用来告诉服务器,浏览器发送的数据是什么格式的

xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),对应数据发送格式为:xhr.send("username=alen&age=18")类似于form表单POST提交

xhr.setRequestHeader("Content-Type","application/json"),对应数据发送格式为:xhr.send(JSON.stringify({username:'alen'}))

<form action="https://www.imooc.com/api/http/search/suggest?words=js" method="post" enctype="application/x-www-form-urlencoded">
      <input type="text" name="username" />
      <input type="password" name="password" />
      <input type="submit" value="提交" />
</form>



<script>
	const url='https://www.imooc.com/api/http/search/suggest?words=js'
	
	const xhr=new XMLHttpRequest()
	
	xhr.onreadystatechange=()=>{
		if(xhr.readyState!==4)return;
		if((xhr.status>=200&&xhr.status<300)||xhr.status==304){
		
			console.log(xhr.response)
		}
	}
	
	xhr.open("POST",url,true)
	
	
	//准备之后,发送之前
	//响应类型
	//xhr.responseType=""//默认为空
	//请求超时时间
	xhr.timeout=10000
	//解决跨域携带Cookie
	xhr.withCredentials=true
	
	//设置请求头信息
	xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded")
	xhr.send("username=alen&age=18")
	
	xhr.setRequestHeader("Content-Type","application/json")//
	xhr.send(JSON.stringify({
		username:"lisi"
	}))
	
	
</script>

4.3 XHR的事件

1.load事件【类似readystatechange监听事件】

响应数据可用后才会触发

IE6~8不支持load事件

<script>
	const url='https://www.imooc.com/api/http/search/suggest?words=js'
	
	const xhr=new XMLHttpRequest()
	
	//监听事件
	xhr.onload=()=>{
		if((xhr.status>=200&&xhr.status<300)||xhr.status==304){
			console.log(xhr.response)
		}
	}
	//或者
	xhr.addEventListener("load",()=>{
		if((xhr.status>=200&&xhr.status<300)||xhr.status==304){
			console.log(xhr.response)
		}
	})
	
	xhr.open("POST",url,true)
	//设置请求头信息
	xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded")
	xhr.send("username=alen&age=18")
	
</script>

2.error事件【请求发送错误触发】

请求发送错误触发,比如:域名错误

IE10开始支持

<script>
	const url='https://www.imooc.com/api/http/search/suggest?words=js'
	
	const xhr=new XMLHttpRequest()
	
	//监听事件
	xhr.addEventListener("load",()=>{
		if((xhr.status>=200&&xhr.status<300)||xhr.status==304){
			console.log(xhr.response)
		}
	})
	xhr.addEventListener("error",()=>{
		console.log("error")
	})
	
	
	xhr.open("POST",url,true)
	//设置请求头信息
	xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded")
	xhr.send("username=alen&age=18")
	
</script>

3.abort事件【终止请求触发】

调用abort()终止请求时触发

IE10开始支持

<script>
	const url='https://www.imooc.com/api/http/search/suggest?words=js'
	
	const xhr=new XMLHttpRequest()
	
	//监听事件
	xhr.addEventListener("load",()=>{
		if((xhr.status>=200&&xhr.status<300)||xhr.status==304){
			console.log(xhr.response)
		}
	})
	xhr.addEventListener("error",()=>{
		console.log("error")
	})
	xhr.addEventListener("abort",()=>{
		console.log("abort")
	})
	
	
	xhr.open("POST",url,true)
	//设置请求头信息
	xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded")
	xhr.send("username=alen&age=18")
	
	
	//请求终止--触发abort事件
	xhr.abort()
</script>

4.timeout事件【请求超时触发】

IE8开始支持

<script>
	const url='https://www.imooc.com/api/http/search/suggest?words=js'
	
	const xhr=new XMLHttpRequest()
	
	//监听事件
	xhr.addEventListener("load",()=>{
		if((xhr.status>=200&&xhr.status<300)||xhr.status==304){
			console.log(xhr.response)
		}
	})
	xhr.addEventListener("error",()=>{
		console.log("error")
	})
	xhr.addEventListener("abort",()=>{
		console.log("abort")
	})
	xhr.addEventListener("timeout",()=>{
		console.log("timeout")
	})
	
	
	xhr.open("POST",url,true)
	//设置请求头信息
	xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded")
	//设置请求超时事件
	xhr.timeout=30000
	//发送请求数据
	xhr.send("username=alen&age=18")
	
</script>

5. Ajax进阶

5.1 FormData

1.使用Ajax提交表单

FormData可用于发送表单数据

<body>
    <form
      id="login"
      action="https://www.imooc.com/api/http/search/suggest?words=js"
      method="POST"
      enctype="application/x-www-form-urlencoded"//常见形式
      //enctype="multipart/form-data"//也是表单提交的一种
    >
      <input type="text" name="username" placeholder="用户名" />
      <input type="password" name="password" placeholder="密码" />
      <input type="submit" id="submit" value="登陆" />
    </form>
    
    
    <script>
      //1.使用Ajax提交表单
      const login = document.getElementById("login");
      // console.log(login.username,login.password)
      const { username, password } = login;
      const btn=document.getElementById("submit")
      const url="https://www.imooc.com/api/http/search/suggest?words=js"


      btn.addEventListener("click",(e)=>{
        //阻止表单默认提交
        e.preventDefault()
        //表单数据验证
        //发送Ajax请求
        const xhr=new XMLHttpRequest()
        xhr.addEventListener("load",()=>{
          if((xhr.status>=200&&xhr.status<300)||xhr.status===304){
            console.log(xhr.response)
          }
        })
        xhr.open("POST",url,true)
        //组装一下数据
        // const data=`username=${username.value}&password=password.value`
        
        //formData用来封装form表单的数据
        const data=new FormData(login)
        
        // console.log(data)
        // for(const item of data){
        //   console.log(item)
        // }

        // xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded")

        xhr.send(data)
      },false)


    </script>
</body>

2.FormData的基本用法

通过HTML表单元素创建FormData对象

IE10开始支持

const fd=new FormData(表单元素)
//可以通过append()方法添加数据
fd.append(键,值)

//发送数据
xhr.send(fd)

5.2 封装Ajax

//index.html页面主要代码
<script type="module">
    import {ajax,get,getJSON,post} from "./index.js"
    const url="https://www.imooc.com/api/http/search/suggest?words=js"
    const xhr=ajax(url,{
      method:"POST",
      params:{username:'lisi'},
      data:{
        age:19
      },
      responseType:"json",
      success(response,xhr){
        console.log(response)
      },
      httpCodeError(err,xhr){
        console.log("Http code error:",err)
      },
      error(xhr){
        console.log("error",xhr)
      },
      abort(xhr){
        console.log("abort",xhr)
      },
      timeout(xhr){
        console.log("timeout",xhr)
      }
    })
</script>

//index.js文件
import Ajax from "./ajax.js"

const ajax=(url,options)=>{
  return new Ajax(url,options).getXHR()
}

const get=(url,options)=>{
  return ajax(url,{...options,method:"GET"})
}

const getJSON=(url,options)=>{
  return ajax(url,{...options,method:"GET",responseType:"json"})
}


const post=(url,options)=>{
  return ajax(url,{...options,method:"POST"})
}

export {ajax,get,getJSON,post}

//ajax.js文件
//引入默认参数模块
import DEFAULTS from "./defaults.js"
//引入工具函数模块,处理url
import { serialize, serializeJSON, addURLData } from "./utils.js"
//引入常量模块
import { HTTP_GET, CONTENT_TYPE_FORM_URLENCODED, CONTENT_TYPE_JSON } from "./constants.js"


//Ajax类
class Ajax {
  constructor(url, options) {
    //url保存到this上,这样这个属性,这个类的其他地方也可以访问
    this.url = url
    this.options = Object.assign({}, DEFAULTS, options)

    //初始化
    this.init()
  }
  init() {
    //创建xhr对象
    const xhr = new XMLHttpRequest()
    //希望别的方法也访问到它,所以这里添加到this上
    this.xhr = xhr

    //监听事件
    this.bindEvents()
    xhr.open(this.options.method, this.url + this.addParam(), true)

    //设置responseType
    this.setResponseType()
    //设置跨域是否携带Cookie
    this.setCookie()
    //设置超时
    this.setTimeout()

    //发送请求
    this.sendData()
  }

  //绑定响应事件处理程序
  bindEvents() {
    const xhr = this.xhr
    //对象解构赋值
    const { success, httpCodeError, error, abort, timeout } = this.options

    //拿到响应数据后调用load事件
    xhr.addEventListener("load", () => {
      if (this.ok()) {
        //响应成功
        success(xhr.response, xhr)
      } else {
        httpCodeError(xhr.status, xhr)
      }
    }, false)

    //请求出现出错时,触发error事件
    xhr.addEventListener("error", () => {
      error(xhr)
    }, false)


    //请求终止,触发abort事件
    xhr.addEventListener("abort", () => {
      abort(xhr)
    }, false)

    //请求超时,触发timeout事件
    xhr.addEventListener("timeout", () => {
      timeout(xhr)
    }, false)
  }

  //检测响应的HTTP状态码是否正常
  ok() {
    const xhr = this.xhr
    return (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304
  }

  //在地址上添加数据
  addParam() {
    const { params } = this.options
    if (!params) return "";
    return addURLData(this.url, serialize(params))
  }


  //设置响应类型的函数
  setResponseType() {
    this.xhr.responseType = this.options.responseType
  }


  //设置跨域是否携带Cookie
  setCookie() {
    if (this.options.withCredentials) {
      this.xhr.withCredentials = true
    }
  }


  //设置超时
  setTimeout() {
    const { timeoutTime } = this.options
    if (timeoutTime > 0) {
      this.xhr.timeout = timeoutTime
    }
  }


  //发送请求
  sendData() {
    const xhr = this.xhr
    if (!this.isSendData()) {
      return xhr.send(null)
    }

    //判断发送那种数据
    let resultData = null

    if (this.isFormData()) {
      //发送FormData格式的数据
      resultData = this.options.data
    } else if (this.isFormURLEncodeData()) {
      //设置Content-Type
      this.setContentType(CONTENT_TYPE_FORM_URLENCODED)
      //发送application/x-www-form-urlencoded格式的数据
      resultData = serialize(this.options.data)

    } else if (this.isJSONData()) {
      //设置Content-Type
      this.setContentType(CONTENT_TYPE_JSON)
      resultData = serializeJSON(this.options.data)

    }else{
      this.setContentType()
      //发送其他格式数据
      resultData=this.options.data
    }

    xhr.send(resultData)

  }


  //是否使用send发送数据
  isSendData() {
    const { data, method } = this.options
    if (!data) return false;
    // if(method==='GET'||method==='get') return false;
    if (method.toLowerCase() === HTTP_GET.toLowerCase()) return false;
    return true
  }


  //是否使用FormData的数据
  isFormData() {
    // 判断是不是FormData的实例
    return this.options.data instanceof FormData
  }


  //是否使用application/x-www-form-urlencoded格式的数据
  isFormURLEncodeData() {
    return this.options.contentType.toLowerCase().includes(CONTENT_TYPE_FORM_URLENCODED)
  }

  //是否使用application/json格式的数据
  isJSONData() {
    return this.options.contentType.toLowerCase().includes(CONTENT_TYPE_JSON)
  }

  //设置Content-Type
  setContentType(contentType=this.options.contentType){
    if(!contentType)return;
    this.xhr.setRequestHeader("Content-Type",contentType)
  }

  //获取xhr对象
  getXHR(){
    return this.xhr
  }
}


export default Ajax;

//utils.js文件
//工具函数
const serialize=param=>{
  const results=[]

  //遍历对象
  for(const {key,value} of Object.entries(param)){
    results.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
  }

  //['username=alen','age=18']

  //将数组转换为字符串
  return results.join("&")//'username=alen&age=18
}

//数据序列化成JSON格式的字符串
const serializeJSON=param=>{
  return JSON.stringify(param)
}



//给URL添加参数
//url:www.baidu.com?name=18&考虑加?还是 &
const addURLData=(url,data)=>{
  if(!data)return ""
  const mark=url.includes("?")?"&":"?"
  return `${mark}${data}`
}

//暴露出去
export {
  serialize,
  serializeJSON,
  addURLData
}

//defaults.js文件
//引入常量模块
import { HTTP_GET,CONTENT_TYPE_FORM_URLENCODED } from "./constants.js"

//默认参数文件
export const DEFAULTS={
  method:HTTP_GET,
  //请求头携带的数据
  params:null,
  // params:{
  //   username:'alen',
  //   age:18
  // },

  //请求体携带数据
  data:null,
  // data:{
  //   username:"lisi",
  //   sex:"female"
  // },
  // data:FormData数据,
  contentType:CONTENT_TYPE_FORM_URLENCODED,
  responseType:"",
  timeoutTime:0,
  withCredentials:false,


  //方法
  success(){},
  httpCodeError(){},
  error(){},
  abort(){},
  timeout(){}
}

export default DEFAULTS

//constants.js文件
//常量模块文件
export const HTTP_GET="GET"
export const CONTENT_TYPE_FORM_URLENCODED="application/x-www-form-urlencoded"
export const CONTENT_TYPE_JSON="application/json"

5.3 使用Promise改造封装好的Ajax

//封装Ajax的代码如上再次基础上改动代码
//index.html文件
<script type="module">
      import { ajax, get, getJSON, post } from "./index.js";
      const url = "https://www.imooc.com/api/http/search/suggest?words=js";
​
      const p = getJSON(url, {
        params: { username: "lisi" },
        data: { age: 19 },
        // timeoutTime:10,
​
      });
      // p.xhr.abort()
​
      const { ERROR_HTTP_CODE, ERROR_REQUEST, ERROR_TIMEOUT, ERROR_ABORT } = p;
​
      p.then((response) => {
        console.log(response);
      }).catch((err) => {
        // console.log(err);
        switch(err.type){
          case ERROR_HTTP_CODE:
            console.log("HTTP状态码错误"); 
            break;
          case ERROR_REQUEST:
            console.log("请求错误"); 
            break;
          case ERROR_TIMEOUT:
            console.log("请求超时"); 
            break;
          case ERROR_ABORT:
            console.log("请求终止"); 
            break;
        }
      });
​
     
</script>
​
​
//index.js文件
import Ajax from "./3.ajax.js"
//引入常量
import {
  ERROR_HTTP_CODE,
  ERROR_HTTP_CODE_TEXT,
  ERROR_REQUEST,
  ERROR_REQUEST_TEXT,
  ERROR_TIMEOUT,
  ERROR_TIMEOUT_TEXT,
  ERROR_ABORT,
  ERROR_ABORT_TEXT
} from "./constants.js"
​
​
const ajax = (url, options) => {
  let xhr
  const p = new Promise((resolve, reject) => {
    xhr = new Ajax(url, {
      ...options,
      ...{
        success(response, xhr) {
          resolve(response)
        },
        httpCodeError(status) {
          reject({
            type: ERROR_HTTP_CODE,
            text: `${ERROR_HTTP_CODE_TEXT}:${status}`
          })
        },
        error() {
          reject({
            type: ERROR_REQUEST,
            text: ERROR_REQUEST_TEXT
          })
        },
        abort(status) {
          reject({
            type: ERROR_ABORT,
            text: ERROR_ABORT_TEXT
          })
        },
        timeout(status) {
          reject({
            type: ERROR_TIMEOUT,
            text: ERROR_TIMEOUT_TEXT
          })
        },
      }
    }).getXHR()
  })
  p.xhr;
  p.ERROR_HTTP_CODE = ERROR_HTTP_CODE
  p.ERROR_REQUEST = ERROR_REQUEST
  p.ERROR_TIMEOUT = ERROR_TIMEOUT
  p.ERROR_ABORT=ERROR_ABORT
  return p;
}
​
const get = (url, options) => {
  return ajax(url, { ...options, method: "GET" })
}
​
const getJSON = (url, options) => {
  return ajax(url, { ...options, method: "GET", responseType: "json" })
}
​
​
const post = (url, options) => {
  return ajax(url, { ...options, method: "POST" })
}
​
export { ajax, get, getJSON, post }
​
//constants.js文件
//常量模块文件
export const HTTP_GET="GET"
export const CONTENT_TYPE_FORM_URLENCODED="application/x-www-form-urlencoded"
export const CONTENT_TYPE_JSON="application/json"export const ERROR_HTTP_CODE=1
export const ERROR_HTTP_CODE_TEXT="HTTP状态码异常"
export const ERROR_REQUEST=2
export const ERROR_REQUEST_TEXT="请求被阻止"
export const ERROR_TIMEOUT=3
export const ERROR_TIMEOUT_TEXT="请求超时"
export const ERROR_ABORT=4
export const ERROR_ABORT_TEXT="请求终止"

6. Ajax应用

6.1 搜索提示

//引入的还是上面封装的ajax代码,这里主要实现的是搜索所做的事情
<body>
    <input type="text" id="search" />
    <ul id="result"></ul>
    
    <script type="module">
      import { getJSON } from "../Promise改造ajax/index.js";
      const searchInput = document.getElementById("search");
      const resultList = document.getElementById("result");
      const url = "https://www.imooc.com/api/http/search/suggest?words=";
​
      //输入框输入触发发送请求事件
      function handleInputEvent() {
        if (searchInput.value.trim() !== "") {
          getJSON(`${url}${searchInput.value}`)
            .then((response) => {
              const data = response.data;
              let html = "";
              for (const item of data) {
                html += `<li>${item.word}</li>`;
              }
              resultList.innerHTML = html;
              resultList.style.display = "";
            })
            .catch((err) => {
              console.log(err);
            });
        } else {
          resultList.innerHTML = "";
          resultList.style.display = "none";
        }
      }
​
      //防抖函数
      const debounce = function (delay, callback) {
        let timer;
        return function () {
          clearTimeout(timer);
          timer = setTimeout(() => {
            callback();
          }, delay);
        };
      };
​
      const debounceFn = debounce(1000, handleInputEvent);
      searchInput.addEventListener("input", function () {
        debounceFn()
      });
​
      //或者写成下面这种
      // let timer
      // searchInput.addEventListener("input", function () {
      //   if(timer){
      //     clearTimeout(timer)
      //   }
      //   timer=setTimeout(handleInputEvent,3000)
      // });
    </script>
</body>

6.2 二级菜单

6.3 多个Ajax请求的并发执行

//引入的还是上面封装的ajax代码,这里主要实现的是ajax的并发执行所做的事情
<script type="module">
      import { getJSON } from "../Promise改造ajax/index.js";
      const menuURL = "https://www.imooc.com/api/mall-PC/index/menu";
      const adURL = "https://www.imooc.com/api/mall-PC/index/ad";
​
      const loadingPageEl = document.getElementById("loading-page");
      const adEl = document.getElementById("ad");
      const menuEl = document.getElementById("menu");
​
      const p1 = getJSON(menuURL)
        .then((response) => {
          console.log("菜单:", response);
        }).catch((err) => {
          console.log(err);
        });
​
      const p2 = getJSON(adURL)
        .then((response) => {
          // console.log("图片:", response);
          let html = "";
          for (const item of response.data) {
            html += `<img src="${item.url}"></img>`;
          }
          adEl.innerHTML = html;
        })
        .catch((err) => {
          console.log(err);
        });
​
      Promise.all([p1, p2]).then(() => {
        // loadingPageEl.style.display="none"
        //这里添加一个类来隐藏还没拿到数据的时候的页面
        loadingPageEl.classList.add("none"); //IE10开始支持
      });
</script>

7. Ajax扩展【axios,Fetch】

7.1 axios【返回是Promise对象】

1.axios是什么?

axios是一个基于Promise的HTTP库,可以用在浏览器和nodejs中;第三方Ajax库

推荐一个axios中文官方网址:www.axios-js.com/zh-cn/docs/

2.axios的基本用法

1.安装

npm install axios

使用cdn:

2.axios的使用方式

axios():第一个参数是url地址,第二个参数传的是配置参数

axios.get():第一个参数是url地址,第二个参数传的是配置参数

axios.post():第一个参数是url地址,第二个参数是要传的数据

axios.put()

axios.delete()

//1.引入axios
<script src="https://unpkg.com/axios@0.27.2/dist/axios.min.js"></script>
​
//2.使用
<script>
    const url="https://www.imooc.com/api/http/search/suggest?words=js"
    axios(url,{
      method:"post",//请求方式
      headers:{//请求头信息
        "Content-Type":"application/x-www-form-urlencoded",
        // "Content-Type":"application/json"
​
      },
      params:{//请求头携带的数据
        username:"lisi"
      },
      //"application/x-www-form-urlencoded"
      data:"age=18&sex=male",
​
      //"application/json"
      // data:{//请求体携带数据
      //   age:19,
      //   sex:"male"
      // }
      timeout:3000,//请求超时时间
      // withCredentials:true//跨域请求是否携带Cookie
    }).then(response=>{
      console.log(response.data)
    }).catch(err=>{
      console.log(err)
    })
</script>
​
​
​
//便捷方式
//get第二个参数传配置相关信息的
axios.get(url,{
  params:{
    username:'alen'
  }
}).then(response=>{
  console.log(response.data)
}).catch(err=>{
  console.log(err)
})
​
​
//post的第二个参数直接传数据
// 字符串
axios.post(url,"username=lis&age=19").then(response=>{
  console.log(response.data)
}).catch(err=>{
  console.log(err)
})
​
//json格式
axios.post("https://www.imooc.com/api/http/json/search/suggest?words=js",{
  username:'alen'
}).then(response=>{
  console.log(response.data)
}).catch(err=>{
  console.log(err)
})
​
//axios.put()
//axios.delete()

7.2 Fetch【返回是Promise对象】

1.Fetch是什么

Fetch也是前后端通信的一种方式;Fetch是Ajax(XMLHttpRequest)的一种替代方案,它是基于Promise的

Ajax的兼容性比Fetch好;Fetch没abort(),timeout()

2.Fetch的基本用法

fetch()第一个参数是请求地址的url,第二个参数是对象配置fetch

const url="https://www.imooc.com/api/http/search/suggest?words=js"
//FormData格式
const fd=new FormData()
fd.append("username",'haha')
fetch(url,{
    method:"post",
    //请求体携带数据
    // body:null,
​
    // 字符串格式
    // body:"username=lisi&age=19",
    // headers:{
    //   "COntent-Type":"application/x-www-form-urlencoded"
    // },
​
​
    //JSON格式
    // body:JSON.stringify({username:"zs"}),
    // headers:{
    //   "COntent-Type":"application/json"
    // }
​
    //FormData格式
    body:fd,
    //跨域资源共享
    mode:"cors",
    credentials:"include"//跨域请求是否携带Cookie
  })

fetch()调用之后返回Promise对象

body/bodyUsed:body只能读一次,读过之后不让在读了

ok:如果为true,表示可以读取数据,不用在去判断HTTP状态码了

response.json():返回值也是一个Promise对象

<script>
  console.log(fetch)
  const url="https://www.imooc.com/api/http/search/suggest?words=js"
  //FormData格式
  const fd=new FormData()
  fd.append("username",'haha')
  fetch(url,{
    method:"post",
    //请求体携带数据
    // body:null,
​
    // 字符串格式
    // body:"username=lisi&age=19",
    // headers:{
    //   "COntent-Type":"application/x-www-form-urlencoded"
    // },
​
​
    //JSON格式
    // body:JSON.stringify({username:"zs"}),
    // headers:{
    //   "COntent-Type":"application/json"
    // }
​
    //FormData格式
    body:fd,
    //跨域资源共享
    mode:"cors",
    credentials:"include"//跨域请求是否携带Cookie
  }).then(res=>{
    console.log(response)
    //body/bodyUsed
    //body只能读一次,读过之后不让在读了
    //ok
    if(response.ok){
      // console.log(res.json())
      //json格式
      return response.json()//返回给下一个then()接收数据
      // 文本格式
      // return response.text
    }else{
      throw new Error(`HTTP CODE 异常${response.status}`)
    }
    
  }).then(data=>{
    console.log(data)
  })
  .catch(err=>{
    console.log(err)
  })
</script>

\