在前面,客户端向服务端发送请求存在以下问题:
以前通过a标签进行跳转,且在表单提交时也相当于页面刷新(可从浏览器的刷新按钮上看出);
而为了解决该问题本篇介绍ajax;
1、ajax基础介绍
ajax是浏览器提供的一套方法,可以无刷新的更新页面中的数据从而提高用户体验。
其在具体页面中主要有以下几个应用场景:
- 页面上拉加载更多数据;
- 列表数据无刷新分页;
- 表单离开焦点数据验证;
- 搜索框提示文字下拉列表
其运行环境是在网站环境中(即平时我们写代码直接双击打开在浏览器中测试时ajax是不会发送请求的,只有通过域名进行访问才是有效的),即在客户端中向服务端发送ajax请求;
2、ajax运行原理
ajax相当于浏览器发送请求与接收响应的代理人,以实现在不影响用户浏览页面的情况下,局部更新页面数据从而提高用户体验;
2.1ajax的实现步骤
2.2传递时的数据类型
在真实中服务器端一般会响应的是json对象格式的数据,当客户端拿到数据后需要将json数据与html字符串进行拼接后将结果展示在页面中;
而在http请求与响应过程中无论请求参数还是响应内容,只要是对象类型都会转换成对象字符串进行传输,所以我们拿到的响应数据如果是对象字符串类型还应将其转换成对象类型,如下:
下面是一个转换成json对象后拼接字符串进行展示到页面中的举例:
1、先将json字符串转成json对象:
2、拼接字符串并展示:
2.3请求参数传递
1、传统表单提交,数据会自动拼在地址后面,如下所示:
2、而在ajax请求中则需要自己拼接参数,其对于get和post两种不同请求有不同的方式:
注意在post请求中要先设置参数头,这里的urlencoded是下面以?&连接的数据格式的固定写法(下面会详细介绍):
这样在html中不用form,直接获取单个输入框的值再进行拼接后作为参数提交即可,get请求参数传递如下所示:
先介绍请求报文的定义:
如下图所示,该请求报文包括了请求方式、地址、数据等等:
则上面的post请求时需要在请求头中设置的数据如下:
1、application/www-form-urlencoded:
2、application/json:
但是在传递之前要先通过JSON.stringify方法先将json对象转成json字符串(上面介绍过无论是请求还是响应都是以json字符串进行传送的);
在服务端如果要将json字符串转成json对象就要使用到bodyParser.join()
函数;
最后还要注意,get请求是不能提交json格式数据的(其只能以拼接的形式跟在地址后面),传统表单提交也是不能提交json数据的(同get);
2.4ajax的状态码
可以通过ajax对象下的readyState
方法获取当时的状态码;
但readyState方法只能让我们获取当前状态码,如果我们要根据不同状态码作出不同处理则必须检测状态码的改变,这时要用到ajax对象下的onreadystatechange
函数,使用如下:
该函数要区别前面ajax对象的onload函数,onload函数是只有在响应完成后才会触发的,而onreadystatechange函数则是在每次ajax状态码发生改变时都会触发;
2.5ajax错误处理
首先要注意区分ajax的状态码以及下面介绍到的http错误状态码
如下:
2.6ajax在低版本ie浏览器中问题
解决方法是在每一次请求的请求地址中加上随机参数,使每次请求地址都发生改变,请求则能发到服务端中,如下面添加了一个参数t,其值是随机值确保每次都不同:
3、ajax异步编程
在之前我们已经介绍过同异步的具体内容了,这里的ajax请求其实是个异步操作;
此外,在代码中我们通常要在很多地方使用ajax发送请求,如果在每个函数中都按照步骤创建ajax对象…则比较麻烦,所以一般封装一个ajax函数,在需要发送ajax请求时调用即可,如下是封装以及调用:
如果我们想对操作结果进行操作,则可以在封装中写上onload函数,然后在调用时传入一个函数,如下形参传递了一个success函数,然后在onload函数中调用该函数:
最后我们在调用ajax函数时期望能够传入请求数据,首先封装ajax函数时需要考虑数据写入问题如下:
如果是post请求,则要考虑:
考虑以上问题后我们开始封装ajax函数中处理请求数据的部分,在调用ajax函数时传递了一个data的json类型数据,然后在ajax函数的定义中处理如下:
先判断请求方式,如果是get请求方式:
注意这里对象后面不能直接**.变量名来获取内容而是要使用[ ],通过.的方式只能对象.键值对名**;
但是上面的拼接方式会导致最候拼出来的字符串多个&,要裁剪:
如果是post方式中的url,也可以直接将上面拼接好的字符串作为参数进行请求,但注意ajax中的post请求时在send中传送的;
如果是post方式中的json,则直接将传入的json对象通过stringify方法转成json字符串直接在send中发送即可;
最后要在onload中通过判断返回状态码进行不同处理(不同的处理函数作为参数传入,如传多个函数success、error等分别在不同状态码时调用);
如果成功取回了数据,我们要根据数据类型的不同进行相应的处理,可以通过以下方法获取响应数据及其类型:
这是在调用函数时要传递的参数已经很多了,每次调用都写这么多很麻烦,应在函数中定义一些变量,如果传入的该参数不为空则传参将默认值覆盖否则使用默认值,初始值设置如下:
然后要将传入的参数对其进行覆盖,如下:
上面将传入的options覆盖defaults,如果defaults中哪项是options没写的则使用默认值;
4、模板引擎
之前使用模板引擎是在服务端使用的,而现在介绍在客户端中的模板引擎使用,其使用步骤如下:
1、搜索进入官网,下载对应的js文件,并将其放到项目文件夹在需要用到的html中引入:
2、写模板,注意必须有个id以区分不同的模板:
3、告诉模板引擎将模板与哪一个数据进行拼接,下面的template方法中参数一是要拼接的id,参数二是数据,并将拼接后的内容用一个变量来接收:
4、将拼接好的html字符串展示到页面中:
5、几个案例
在掌握了ajax以上相关知识后可以实现几个简单常用的案例:
案例1、如在注册页面中,一输入邮箱还没提交表单就首先验证邮箱地址唯一性(即该邮箱有没有被注册过):
其中第二部的检验是通过正则表达式对其进行检验,如下所示:
如果正则表达式的检验通过则直接到下一步通过之前封装好的ajax函数向服务器发送请求,验证邮箱是否被注册过(在数据库中是否已存在),对于验证成功、失败分别调用success以及error函数进行相应处理(主要是设置输入框样式):
案例2、在搜索框中输出文字后会在下面提示可能搜索的内容功能:
oninput函数是每次输入新内容都会触发,如果直接在其中调用ajax函数发送请求则在每输入时都会触发过于频繁造成服务器压力过大,为了解决该问题可以在oninput函数中写一个定时器函数,在定时器中再写ajax请求函数,则这样虽然会触发oninput函数,但是ajax请求会在定时时间到了以后才会触发,同时每次设置新的定时器前都将上一次的定时器取消掉,则不会造成重复发送多次请求:
然后在ajax的success函数中将请求回来的数据显示在搜索框下即可;
此时还存在一个问题,在我们有内容的情况下搜索框下面是有提示的,如果此时将搜索框中内容删掉但是下面的提示还是会存在,应在oninput函数中检测输入后的内容是否为空,若为空则将下面的提示全部隐藏起来(同时不再发送ajax请求):
案例3、常见省市县三级选择联动(比如在省一栏中选择了广东省则下面的市一栏会自动发送请求获取广东省下的所有市):
1、首先是一进入页面就获取所有省份的数据写入省份下拉框中:
其模板如下,在模板中遍历所有取回来的省份数据进行展示:
然后为省份下拉框添加onchange事件(在其每次选择省份后都会触发),在其中请求市的信息,并将其通过市的模板渲染到市的下拉框中,在省的下拉框onchange函数中:
最后在市的下拉框中也要设置其onchange函数并且将获取回来的数据渲染到县的下拉框中即可;
但此时仍存在一个问题,如果重新选择了省份后,若此时县已经有数据,其不会自动修改,我们想要的效果是当重新选择省份后,市下拉框中显示出相应的市数据,同时县下拉框恢复初始的请选择状态,则必须在省下拉框的onchange函数中加上:
6、FormData对象
下面介绍formdata对象及其使用:
1、formdata的作用:
下面分别介绍两种作用的具体实现:
1、在表单中的应用:
这里的form不用写提交地址,在后面的ajax请求中写即可。同时默认的submit提交按钮也被换成了普通按钮,将表单转换为formdata对象后直接将接受的变量在后面的ajax中作为参数发送即可,如下:
formidable模块使用如下:
上面方法中,fields是传递过来的普通参数数据、files则是传递过来的二进制文件(下面会介绍);
2、在上传文件中的使用:
在介绍通过formdata上传文件之前先介绍几个formdata实例对象方法,以下的key代表表单中的name属性以及对象中的属性名:
注意区别set与append:在属性名已存在的情况下,set会覆盖,而append则会保留两个值(此时虽然两个值,但如果提交到服务器服务器只会取后一个值);
在了解以上方法后下面介绍formdata上传数据相关:
注意上面的file input组件在没有加multi属性下默认只能上传一个文件,以上是上传文件的全部操作,只需在onload函数中作出相应显示即可;
在服务端中也是通过上面的formidable来解析数据,注意此时默认上传的文件是服务端不保留其后缀的,如果想保留后缀则要写上:
在了解了文件上传以后,下面介绍如何实现进度条效果,首先是页面中进度条html如下:
此外,在文件上传过程中会多次触发onprogress事件,在该事件中我们能获取文件总大小以及当前上传了多少,通过计算我们便可得知当前文件上传了多少:
最后介绍图片上传时的预览功能:
在服务端中将文件存储地址路径返回给客户端:
然后在html中图片显示位置有以下html,当图片加载完成后只需往div中添加img标签即可:
在js中,当请求服务器返回结果时从结果中取图片地址赋给img标签的src属性,最后当图片加载完成时把该标签添加到div标签中则完成操作:
7、同源政策
ajax存在请求限制,即其只能向自己服务器发送请求。即网站a中的html文件只能向网站a的服务器发送ajax请求,而不能向网站b的服务器发送ajax请求,同理网站b也只能向自己的服务器发送ajax请求。这本质上是同源政策的影响;
同源:
同源政策目的:
7.1 同源限制问题解决方案
7.1.1 jsonp解决方案
其使用如下:
本质上就是在客户端中不能通过ajax请求向非同源服务端发请求,而script标签则可以,则通过script标签向非同源服务端发送请求,然后在客户端中定义一个函数,在服务端返回函数的调用(同时附带回传的数据作为形参);
上面是一打开网页就会触发script的请求,但通常都需要在某些时刻才发送请求,这时把script标签放在js代码中动态创建即可,如下在点击按钮时创建script标签:
但在这种情况下每点击一次按钮就会创建一个script标签最后导致页面中script标签超多(虽然看不到),其在加载完内容后已经没有意义必须在每次其加载完后将其移除:
在当前情况下,客户端中定义的函数调用(如前面的fn)必须与服务器返回的保持一致,这样会增加前后端的沟通成本,实际上可以在客户端发送请求时一并将回调函数的名字传递过去(通常属性名为callback),然后服务端在获取该回调函数名字将其拼接返回即可,如下回调函数就是fn:
然后在服务器端获取该callback的值作为回调函数返回即可:
注意虽然返回的是拼接的字符串,但在script标签中会将其当做代码执行;
由于页面中很多地方都要使用该请求,所以将封装一个jsonp函数在需要使用的地方直接进行调用,如下:
此外,对于不同的调用处,我们对返回的数据是不同的,这需要我们定义很多个函数这实际上比较麻烦,应当在调用函数时将处理函数传递进去,如我们在调用jsonp函数时传入一个success函数作为回调函数:
接着我们把传经来的函数直接赋给window(为了将函数变成全局函数进行调用)下的fn2在服务器返回fn2即可调用:
但是这并不完美,如果页面中有很多该请求的话,每次都是调用fn2函数会造成冲突(否则写多个fnx也麻烦),所以函数名fn2应该改成随机的:
因为对象下通过变量名获取数据只能通过[ ],而不能通过.变量名的方式;
但此时并没有在请求时传递参数(callback参数不算),下面要拼接上参数(注意script请求时get请求,所以参数是跟在地址后面的),在调用函数时传入参数,如下:
然后在函数中要将参数取出并进行拼接:
最后服务端也要作出相应的修改,通过服务端的jsonp函数可以实现上面注释代码的功能(将参数放入客户端传来的回调函数,并将其返回)
最后介绍一种在代码常用到的知识点,在模板中开放外部变量,其中dateFormat是定义的外部函数:
然后在模板中可以直接调用该函数:
7.1.2 CORS跨域资源共享解决方案
对比其上面的jsonp解决方案(没有使用到ajax,模仿ajax绕过了同源限制),cors主要是在服务端做一些配置,客户端只要保持原有的ajax请求代码即可,服务端主动给我们的请求加上origin请求头,相应的服务器的返回也会加上响应头:
加了请求头与响应头后如下:
具体代码示例如下,先在客户端中发送一个正常的ajax请求:
然后在服务器端写,具体如其中注释所示,即在服务端路由设置响应头即可让该路由接收跨域访问:
但是上面这样在每个路由中都写重复的代码有点累赘,我们可以通过前面所学的中间件拦截所有请求,在所有路由的最上方:
相比第一种方法,本方法简单的多;
7.1.3 服务端解决方案
由于同源政策是浏览器给ajax的限制,而服务端则不存在此限制;
则当网页a想向服务器b请求数据时,可以通过网页a的服务器a向服务器b请求数据后再返回到网页a,如下图所示:
实际案例如下:
在客户端中先向自己的服务端a发送正常的ajax请求:
然后在服务端a中向服务端b发送请求,引入request模块:
然后发送请求:
7.2非同源中的cookie问题
在ajax的非同源跨域请求中,默认情况下是不会懈怠cookie信息的,则这样会出现较多问题(如在登录页面中如果无法携带cookie则每次登录在下一页面都会无效);
通过withCredentials属性可以改变这样默认情况,只需将其设置为true即可:
而在服务器中:
则可以实现跨域请求时携带cookie效果;
8、jquery中的ajax相关
8.1$.ajax
之前我们都是自己封装ajax函数,而jquery中给我们封装好了现成的ajax函数$.ajax(其可以发送ajax请求以及jsonp请求,通过datatype属性进行区分)。
其发送ajax请求时调用如下:
data中的数据可以和上面一样写对象形式的,也可以写字符串形式,如下:
注意这里的contentype是urlencoded形式,所以上面输入的data中对象数据会转成这种参数字符串(&)的形式,如果直接和上图一样写data时就写这种格式同理;
但碰上复杂数据类型是无法转换,或者服务端要求客户端传过来是json格式数据时,此时将contenttype设置成json形式后将json数据通过stringify转换后(如果不写stringify在方法内部还是会转成参数字符串类型)传入服务端,如下:
此外该函数还有一些如beforsend的方法,即在发送请求之前调用,我们可以利用其进行验证的功能或者是显示loading图案等等…
最后注意这里的url在当前html的协议、域名、端口均与当前ajax请求的协议域名、端口相同时,在ajax请求中可以不写,直接写地址即可,如下所示:
而发送jsonp请求时使用如下,通过datatype来指定发送jsonp请求,jsonp参数中写的cb相当于普通jsonp中的callback属性名,而jsoncallback中的内容则是回调函数的具体名字(如果规定此项则在请求成功后不会执行success函数而是此处指定的函数,必须在该函数外部定义一个相应的函数)
如上,如果写了jsonp为cb,则在服务端中应该这样写:
但正常情况下,我们需要提交的数据都是从表单中获取过来的而并非是自己在代码中写死的,这是我们要获取表单中的数据并将其作为参数传入data中,前面我们介绍了formdata可以实现该功能,但是formdata具有兼容性问题,这里再介绍一个serialize方法,其能够将表单的内容拼接成参数字符串类型,如下:
但拼接成这种参数字符串后,如皋我们要对输入进行验证是十分困难的,如果转换后的是对象则会很方便,下面封装一个方法直接将用户表单变为对象类型,其中serializeArray方法会将用户输入表单信息变为数组形式,如注释中所示(有多少个表单则数组中就有多少个对象),我们将该数组通过遍历组成对象即可:
注意这里的形参obj是表单元素form;
8.2 $.get/post()方法
也可以发送get与post请求:
8.3全局事件
之前介绍中提到,ajax函数中的beforesend可以用于在用户发送请求时进行提示,但是如果页面中有多次请求的话每次都写重复的提示代码则冗余,这时可通过全局事件来实现只写一处代码但每个ajax请求中都能使用的效果:
结合第三方模块NProgress则可实现相应效果:
具体案例代码如下,分别在ajax请求开始和结束时触发这两个不同内容即可:
9、一些补充知识点
9.1RESTful风格API
这里说的api指的是请求地址,即为一套地址的命名规范,其主要是通过不同的请求方式来实现不同效果,如下:
传统表单中是不支持后两种请求方式的,但是ajax请求中可以,其举例如下:
可以看出,其请求地址相同而方式不同最终实现了不同的功能;
注意对单个用户的操作是其后面写了/1这种形式,我们不可能对应一个id写一个路由,其正确写法如下,通过/:id来获取id名,从而进行相应的处理:
9.2xml
注意html也是标记语言,用来展示数据,该类型数据举例如下所示:
这种数据类型其实使用已经较少仅做了解即可,要知道服务端如果返回的xml数据应如何进行处理,这时要用到:xml DOM
如下,在服务端中返回xml数据:
在客户端中发送请求并对xml数据进行处理如下: