AJAX

名称

Asynchronous Javascript And XML:异步的Javascript与XML

异步的:非同步的

问题

以登录功能为例,假设是失败的:当用户点击“登录”按钮后,会向服务器提交请求,然后,服务器端进行登录验证,并返回结果,返回的结果由于是失败的,很可能还是登录页面,只不过此次登录界面中包括错误信息,例如在页面上会显示“用户名或密码错误”之类的提示。

在这样的案例中,存在的问题:

1 登录前和登录后的是同一个界面,却由服务器返回了2次,第2次仅仅只是多了错误提示,返回的却是整个界面,从必要性、流量方向考虑,都是没有必要的!

2 用户体验较差,特别是网络传输速度较差的情况下表现得更加明显,毕竟需要处理的数据中有较大比例的是不必要传输的数据!

3 用户体验较差,表现为当用户提交后,接下来将得到新的界面,在提交后、新界面显示之前,用户无法在界面中进行操作。

基本概念

AJAX是一种提交HTTP请求、获取结果的处理方式。

AJAX提交的请求是异步的,即开启另一个线程提交请求,与当前浏览器显示的页面、用户的使用并不发生冲突。

AJAX获取的响应结果通常不会是一个完整的网页,而是某些数据,可以是普通的字符串直接表示的数据,例如错误提示信息,也可能是有规则组织起来的数据,例如使用XML来组织这些数据。

AJAX并不是一门新的语言,只是一门技术而已。

AJAX在程序中的表现是Javascript代码。

XMLHttpRequest

在AJAX中,XMLHttpRequest就是用于发送异步请求,并获取响应结果的对象!

由于历史版本问题,在IE 5和IE 6中,并没有XMLHttpRequest,而是使用的ActiveXObject,所以,在这2个版本的浏览器,如果需要使用AJAX,必须是:

XMLHttpRequest xmlhttp = new ActiveXObject("Microsoft.XMLHttp");

为了保证各种浏览器都可以获取到正确的XMLHttpRequest对象:

function getXMLHttpRequest() {
	var xmlhttp = null;

	if (window.XMLHttpRequest) {
		// 适用于IE 7+、Chrome、Firefox、Safari、Opera……
		xmlhttp = new XMLHttpRequest();
	} else {
		// 适用于IE 5、IE 6
		xmlhttp = new ActiveXObject("Microsoft.XMLHttp");
	}

	return xmlhttp;
}

在XMLHttpRequest中的5个属性

– onreadystatechange:处理函数,即请求发出后,哪个函数对后续的操作进行处理,该属性的值应该是一个函数。

– readyState:状态,该属性的值为0~4之间的数值,即共5种状态:
– -- 0[不关心]:连接尚未初始化
– -- 1[不关心]:初始化连接
– -- 2[不关心]:发出请求
– -- 3[不关心]:服务器已经接收到请求
– -- 4:响应已经就绪

– status:响应码,例如200表示正确的响应

– responseText:响应的正文(字符串)

– responseXML:响应的XML,该属性和responseText选取其中一个来使用即可,取决于服务器响应的正文到底是字符串还是XML格式

发送请求

发送请求需要调用open()和send()这2个函数,例如:

xmlhttp.open("GET", "query.do", true);
xmlhttp.send();

在open()函数中,第1个参数表示请求类型,取值可以是"GET"或"POST",第2个参数表示请求资源的路径,可以是相对路径,也可以是绝对路径,路径对应的资源可以是一个动态页面,也可以是在服务器上的文本文件,或图片等任意资源,第3个参数表示此次请求是否是异步的,取值为true表示异步,取值为false表示同步。

在发出"GET"请求时,调用send()函数时,不需要添加参数,或添加null作为参数均可,如果发出的是"POST"请求,则应该把参数以"name1=value1&name2=value2&name3=value3"这样的形式组织起来,作为调用send()函数的参数。

由于"POST"请求的参数是需要配置请求头的,所以,在发出"POST"请求之前,还必须调用XMLHttpRequest对象的setRequestHeader()函数,以配置请求头:

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

Ps:在使用open()函数时将第3个参数设置为false表示将发出同步请求,一旦发出请求,当前网页将暂时处于不可用状态,例如网页上的按钮将无法点击,页面将无法滚动等,直至此次同步请求处理完毕。基于这样的特性,通常极少使用同步请求,即使使用,也只用于处理少量数据,保证同步请求不会占用太多时间,从而不影响用户的正常体验。

案例一

目标

服务器端有一组数据:BeiJing、ShangHai、GuangZhou、ShenZhen、TianJin、NanJing、ChangSha、ChengDu……

在网页中输入例如字母B,将在网页中提示以字母B作为首字母的结果:BeiJing,如果输入S,则提示:ShangHai、ShenZhen

开发步骤

1 设计功能:

public String query(String keyword) {
	String[] cities = "BeiJing,ShangHai,GuangZhou,ShenZhen,TianJin,NanJing,ChangSha,ChengDu".split(",");
	StringBuffer result = new StringBuffer();
	
	for (int i = 0; i < cities.length; i++) {
		if (cities[i].startsWith(keyword)) {
			if (result.length() > 0) {
				result.append("、");
			}
			result.append(cities[i]);
		}
	}

	return result.toString();
}

2 开发Web项目,提供功能

cn.tedu.ajax.servlet.QueryCityServlet

http://SERVER:PORT/PROJECT/query.do?keyword=S

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	// 1. 获取请求参数:关键字
	String keyword = req.getParameter("keyword");
	
	// 2. 调用query()方法,获取关键字对应的结果
	String result = query(keyword);
	
	// 3. 获取输出流,向客户端输出结果
	PrintWriter out = resp.getWriter();
	out.println(result);
	out.flush();
	out.close();
}

另:需注册Servlet

3 设计前端页面:页面中需要1个输入框用于输入关键字,另需要1个标签显示结果,其它部分没有要求

4 编写Javascript程序,每当输入框中的内容改变时,获取输入框中的内容

<script type="text/javascript">
function showSuggest(keyword) {
	// 测试获取输入框中的内容
	// alert(keyword);
}
</script>

<input type="text"
	onkeyup="showSuggest(this.value)" />

5 获取异步请求对象

var xmlhttp = new XMLHttpRequest();

6 发出请求

// 请求URL
var url = "query.do?keyword=" + keyword;
// 发出请求
xmlhttp.open("GET", url, true);
xmlhttp.send();

7 在发出请求之前,添加对结果处理的函数

xmlhttp.onreadystatechange = function() {
	if (xmlhttp.readyState == 4
			&& xmlhttp.status == 200) {
		document.getElementById("suggestResult")
			.innerHTML = xmlhttp.responseText;
	}
};

案例二

目标

模拟登录,需要输入用户名和密码来登录,假设正确的用户名是tomcat,匹配的密码是tomcat7,当用户登录后,如果用户名错误,则在用户名的输入框下方提示用户名错误,如果密码错误,则在密码的输入框下方提示密码错误。

开发步骤

1 创建项目:DAY09-AJAX-02-Login

2 创建Servlet:cn.tedu.ajax.servlet.LoginServlet,并在该LoginServlet中重写doPost()方法,在方法中接收名为username和password的参数,根据参数模拟判断登录,如果用户名错误,向客户端输出"Username not exists",如果密码错误,向客户端输出"Password not match",如果都正确,则向客户端输出"OK",完成后,在web.xml中注册该LoginServlet,映射地址为:handleLogin.do

3 在webapp目录下添加index.html,在该文件中添加2个输入框,分别用于输入用户名和密码,并为这2个输入框设置id属性,以便于通过Javascript获取输入框中的值,然后添加按钮,最后,在2个输入框的下方分别添加1个标签,用于显示登录错误的信息

4 添加Javascript函数:funtion $(id) …

5 添加Javascript函数:function getXMLHttpRequest() …

6 添加Javascript函数:function doLogin(),在该函数中,先获取用户输入的数据,验证数据基本有效性(本次练习可选操作),然后获取XMLHttpRequest对象,配置onreadystatechange属性,设计响应函数,然后依次调用open()、setRequestHeader()、send()方法发出请求,在响应函数中,通过XMLHttpRequest对象的responseText获取响应结果,对结果进行判断,然后通过标签对象的innerHTML来显示错误信息,或者通过window.location重定向

在Spring MVC案例中,控制器返回值

在Spring MVC的默认情况下,控制器返回的值是用于处理后续内容的View组件名称,只需要使用@ResponseBody对方法进行注解,则表示方法返回的就只是值!注意:使用@ResponseBody时,必须在Spring的配置文件中添加<mvc:annotation-driven />

	public class Controller {

		@RequestMapping()
		@ResponseBody
		public String handleLogin() {
			return "value";
		}

	}

在Spring MVC中使用@ResponseBody

基本使用

@ResponseBody用于注解Controller类中的方法。

一旦使用了这个注解,Controller中方法的返回值就应该是String,并且不再表示“返回View组件的名称”的意义,而是返回普通的字符串,最终,客户端得到的响应也就是这个字符串!

使用@ResponseBody时,推荐在Spring的配置文件中添加<mvc:annotation-driven />驱动注解。

乱码问题

一旦使用了@ResponseBody,在默认情况下,会通过StringHttpMessageConverter来处理结果,其默认的处理方式中,使用的是ISO-8859-1编码,并不会按照UTF-8来处理,所以,会产生中文乱码的问题

解决方案就是在Spring的配置文件中,在mvc:annotation-driven节点下,添加mvc:message-converters节点,显式的配置StringHttpMessageConverter,并设置字符编码,在这个类中,字符编码是通过父类的supportedMediaType属性来设置的,所以,可以配置为:

<mvc:annotation-driven>
	<mvc:message-converters>
		<bean	class="org.springframework.http.converter.StringHttpMessageConverter">
			<property name="supportedMediaTypes">
				<list>
					<value>text/html;charset=utf-8</value>
					<value>application/json;charset=utf-8</value>
					<value>*/*;charset=utf-8</value>
				</list>
			</property>
		</bean>
	</mvc:message-converters>
</mvc:annotation-driven>

由于StringHttpMessageConverter中supportedMediaTypes属性的类型是List集合,所以,推荐以上配置方式,当然,也可以简化为:

<mvc:annotation-driven>
	<mvc:message-converters>
		<bean	class="org.springframework.http.converter.StringHttpMessageConverter">
			<property name="supportedMediaTypes" 
				value="text/html;charset=utf-8" />
		</bean>
	</mvc:message-converters>
</mvc:annotation-driven>

JSON

为什么要使用JSON

在很多情况下,服务端程序作出的响应不再是显示某个HTML页面,而是一系列相关的数据,这样的话,更加利于服务端程序“提供数据服务”,即只提供数据,而不负责数据的呈现。

由服务端程序提供数据往往包含很多信息,比如自定义的响应代码(例如1表示成功,-1表示某种操作错误等),还包含提示信息、关键数据等等。

诸多的信息应该有规则的组织起来,便于发出请求的一方进行分析并且处理!早期的做法是使用XML进行处理:

<response-body>
	<status>1</status>
	<data>
		<username>tomcat</username>
		<age>18</age>
		<from>BeiJing</form>
	</data>
</response-body>

使用XML最大的不足在于使用的节点过多,解析过程相对麻烦,并且由于节点过多,导致传输的数据量较大!

用于取代XML这种组织数据的方式的就是JSON:

{
	"status":1,
	"data":{
		"username":"tomcat",
		"age":18,
		"from":"BeiJing"
	}
}

通过对比,可以发现,使用JSON表达的数据,体量更小(字节数更少),所以,最终将表现得传输更快,消耗的流量更少一些!事实上,数据解析过程也更加简单一些!

JSON的数据格式

1 JSON数据必须使用一对大括号框住后再开始编写,相当于每个XML都有且仅有1个根节点

2 JSON数据中的属性与值都是使用 “属性名”:属性值 这样的格式来组织,多对属性与值的配置之间使用逗号进行分隔,例如:

{
	"name":"tomcat",
	"age":18
}

3 JSON中也有数据类型的概念,如果属性的值是字符串类型的,必须使用双引号框住,如果是数值型的,直接写值即可,但是,由于JSON被多用于服务器端向客户端传输数据,所以,并不一定需要在意数据的类型,即使全部作为字符串类型来处理也是可以的:

{
	"name":"tomcat",
	"age":"18"
}

4 JSON中也可以表示对象,如果某个属性的值是对象,则该属性值应该使用大括号框住,在这对大括号中,继续使用 “属性名”:属性值 的格式表示对象内部的属性:

{
	"status":1,
	"data":{
		"username":"tomcat",
		"age":18,
		"from":"BeiJing",
		"department":{
			"id":9527,
			"name":"rd"
		}
	}
}

5 JSON中还可以表示数组或集合,对于JSON而言,严格的说只有数组,如果要表示List集合,语法上与数组是一样的,没有区别!表示数组的方式是使用 [] 将所有的数组成员框住,各成员之间使用逗号分隔:

{
	"users":[
		{ "username":"Alex", "age":18 },
		{ "username":"Billy", "age":19 }
	]
}

6 在Javascript中,也可以很方便的访问JSON数据,例如:

var jsonData = { "name":"tomcat", "age":"18" };
alert(jsonData.name);

jsonData = {
	"status":1,
	"data":{
		"username":"tomcat",
		"age":18,
		"from":"BeiJing"
	}
};
alert(jsonData.data.username);

jsonData = {
	"users":[
		{ "username":"Alex", "age":18 },
		{ "username":"Billy", "age":19 }
	]
};
alert(jsonData.users[1].username);

7 如果在Javascript中只能先得到JSON的字符串格式,需要转换为JSON对象,可以通过JSON.parse()函数来实现转换:

var text = '{ "status":1, "message":"ok" }';
var jsonObject = JSON.parse(text);

返回通用的JSON数据格式

为了便于前端页面及其它客户端更好的处理数据,通常会定义一个通用的响应数据类型,即无论此次是什么样的请求,或请求对应的操作成功与否,都会响应的同一种数据类型:

public class ResponseResult<T> {
	int state; // 状态码:成功?失败?哪种原因的失败?
	String message; // 错误描述
	T data; // 数据
}

通常,在使用了通用数据格式后,每个操作都会提供相关的文档说明,例如:

请求URL:handleLogin.do
请求方式:POST
请求参数:username=tomcat&password=tomcat7
响应:
	state:1,表示操作成功
	state:-1,表示用户名错误,描述文字可从message属性中获取
	state:-2,表示密码错误,描述文字可从message属性中获取

使用Jackson处理JSON

Jackson是一款第三方的用于处理JSON数据的库。

导入Jackson:

-- jackson-core
-- jackson-annotations
-- jackson-databind

当已经导入所需的包后,即可通过Jackson将对象转换为JSON格式:

ObjectMapper om = new ObjectMapper();
String jsonString = om.writeValueAsString(obj); // try...catch

练习一

目标

在HTML页面中存在id="time"的区域,用于显示时间,时间的值是由服务器端提供的,每间隔1秒钟更新一次时间。

开发步骤

1 创建项目:DAY10-03-AJAX-Timer,完成相对固定的操作:解决web.xml问题、添加spring-webmvc和jackson的依赖、配置Tomcat Runtime、复制Spring配置文件、配置web.xml

2 创建ResponseResult类,用于表示通用的响应结果

3 创建TimeController,添加public String showTime()方法,映射到"showTime.do"这个路径,在方法中,结合SimpleDateFormat和Date类得到时间的字符串,格式例如:2017-12-25 15:36:15,然后创建ResponseResult对象,将时间字符串封装到ResponseResult中的data中,最后,通过Jackson将ResponseResult对象转换为JSON格式,并作为showTime()方法的返回值

4 创建index.html文件,添加用于显示时间,添加按钮用于点击后显示时间,按钮绑定的Javascript函数为showTime()

5 在index.html中,添加 $(id) 和 getXMLHttpRequest() 这2个函数

6 在index.html中,添加showTime()函数,在函数中,通过XMLHttpRequest对象向服务器的showTime.do发出GET请求,并获取结果的JSON字符串,然后解析出其中的data的数据,并显示到id=timer的标签中

[问题] 在Javascript中的setTimeout和setInterval

这2个函数的本质并不相同,setTimeout用于设置多久以后执行什么任务,setInterval用于设置每间隔多久执行什么任务。

如果在setTimeout设置的函数中调用了自身,也可以实现“每间隔多久执行什么任务”的效果。

推荐使用setTimeout完成循环的任务,特别是某些执行时间可能略长的任务!

假设某个任务单次执行时间需要0.1秒,然后使用setInterval(“xxx”, 100)来循环运行,实际运行效果就是不停的在执行任务,其间并没有暂停过100毫秒!

练习二

在页面上设置一个下拉菜单( + ),表示省份,默认的值可自行添加2个以上,每当该菜单项的选中状态发生变化时,动态的显示第2个下拉菜单中的内容,第2个下拉菜单中应该显示某省份对应的城市列表。

练习 - 二级联动菜单

1 创建项目及准备工作

2 设计数据

----- 请选择 -----
1.广东省					1广州,2深圳,3珠海
2.河北省					4石家庄,5保定,6秦皇岛

3 设计服务端的功能:

URL:getCities.do
请求类型:GET
参数:provinceId
返回:ResponseResult,其中的数据部分是List<City>,City中至少封装id, name

a) 创建cn.tedu.spring.bean.ResponseResult,属性包括:int state,String message,Object data
b) 创建cn.tedu.spring.bean.City,属性包括:int id,String name
c) 创建cn.tedu.spring.controller.CityController,添加public String getCities(int provinceId)方法,映射到getCities.do路径,在方法内部,根据参数provinceId进行判断,然后得到List<City>,封装到ResponseResult中,最后转换为JSON字符串,作为方法的返回值
d) 通过浏览器测试

4 创建前端页面index.html,并添加2个下拉菜单,其中第1个下拉菜单中添加1广东省、2河北省这2个有效选项

5 设计AJAX请求

	function $(id) {
		return document.getElementById(id);
	}
	
	function getXMLHttpRequest() {
		var xmlhttp = null;
		if (window.XMLHttpRequest) {
			xmlhttp = new XMLHttpRequest();
		} else {
			xmlhttp = new ActiveXObject("Microsoft.XMLHttp");
		}
		return xmlhttp;
	}

	function getCities(provinceId) {
		// 无论选中哪个选项,先清空“城市”列表
		while ($("cities").firstChild) {
			$("cities").removeChild(
					$("cities").firstChild);
		}
		// 判断provinceId是否有效
		if (provinceId == 0) {
			return;
		}
		// 获取XMLHttpRequest
		var xmlhttp = getXMLHttpRequest();
		// 设计响应函数
		xmlhttp.onreadystatechange = function() {
			if (xmlhttp.readyState == 4 
					&& xmlhttp.status == 200) {
				// 获取响应的正文文本
				var text = xmlhttp.responseText;
				// 将响应的正文文本转换为JSON对象
				var jsonObject = JSON.parse(text);
				// 判断JSON对象中的state是否正确
				if (jsonObject.state == 1) {
					// 遍历JSON对象中的data对应的数组
					for (var i = 0; i < jsonObject.data.length; i++) {
						// 将数组中的每个数据设计为一个个<option>
						var opt = document.createElement("option");
						// <option value="3">珠海</option>
						// "data":[
						// 	    {"id":1,"name":"广州"},
						//      {"id":2,"name":"深圳"},
						//      {"id":3,"name":"珠海"}
						// ]
						opt.value = jsonObject.data[i].id;
						opt.innerHTML = jsonObject.data[i].name;
						// 将<option>添加到“城市”列表中
						$("cities").appendChild(opt);
					}
				}
			}
		};
		// 发出请求
		var url = "getCities.do?provinceId=" 
				+ provinceId;
		xmlhttp.open("GET", url, true);
		xmlhttp.send();
	}

AJAX的数据缓存问题

如果反复向同一个URL发出请求,并且请求参数没有发生变化的话,可能会获取到缓存的数据,即获取到的不是最新数据!

针对这个问题的解决方案:使得每次请求参数都不同!例如为每次的请求多添加1个参数,参数名可以自定义,参数值使用随机数即可!

jQuery AJAX - load()

在使用了jQuery后,关于AJAX的应用也得到了极大的简化,通过load()函数可以快速的提交AJAX的异步请求,并且将响应的结果填充到某个标签中。

注意:这种做法适用于(但不局限于)将结果直接填充到标签中!

这种做法的语法格式是:

$(选择器).load(URL,参数);

在以上语法中,$(选择器)表示需要将结果填充到哪个标签中,URL表示请求路径,参数表示发出请求时向服务器提交的参数,参数值的格式可以是字符串格式,例如:

username=tomcat&password=123456

参数值的格式还可以是JSON对象格式:

{ "username":"tomcat", "password":"123456" }

其实,load()函数的完整格式是:

$(选择器).load(URL,参数,处理响应结果的函数);

也就是说,load()函数还可以有第3个参数,第3个参数是一个函数,专门用于处理结果,类似于传统方式中的xmlhttp.onreadystatechange属性的值。

另外,其实在jQuery中,还有另一个也叫做load()的函数,也是被某个标签对象所调用的,但是,其参数是某个函数,用于表示标签加载完成时的自定义处理,这2个load()函数,至于执行的是哪个,取决于参数的设计。

jQuery AJAX - ajax()

在使用jQuery时,通过ajax()函数可以设置AJAX请求过程中所有的可设置项!

使用ajax()的基本语法是:

$.ajax({配置});

以上语法中,“配置”及左右两侧的{ }整体表现为一个JSON对象格式。

在“配置”中,通常会设置的属性有:

url:请求的路径

例如:

$.ajax({
	"url":"getCities.do",
	"success":function(obj) {
				}
});

还会设置的属性有:

type:请求的类型(请求方式),取值为"GET"或"POST"

data:请求的参数,参数值可以是字符串格式的,也可以是JSON对象格式的

dataType:响应结果的数据的类型,常用的取值有"text"、"json",当取值为"text"时,后续success属性对应的处理函数中的参数将是字符串类型的,当取值为"json"时,后续success属性对应的处理函数中的参数将是JSON对象

success:成功得到响应结果时的处理函数,即该属性的值是某个函数,该函数仅会在成功得到响应结果后被调用,所以,在函数内部,无须再判断状态为4或响应码为200,并且,在编写处理函数时,请为该函数添加参数,参数名称可自行定义,参数将是成功得到响应结果时的响应结果,类似于xmlhttp.responseText

在配置以上属性时,不区分先后顺序,即先配置哪个后配置哪个,并没有要求!

除此以外,ajax()函数还可以配置许多属性,如有需要,请参考相关文档。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值