Ajax之从头分析

      不管你有没有接触过ajax开发,它的大名你一定听说过。可以说,没有ajax技术,就没有现在欣欣向荣的web开发的景象。那么什么是ajax呢。ajax的全称为Asynchronous JavaScript and XML,中文翻译为异步的JavaScript和XML。ajax并不是某种语言,而是一种技术。即无需重新加载整个网页的情况之下更够更新部分网页的技术。正是因为ajax技术,网页才可以实现异步更新,ajax改变了web开发的格局。

1-概念介绍

1.1-同步

       比如你正在填写一张有100多个字段的表单,你花了两个小时填写完毕,点击提交,等了5分钟,服务器告诉你你少填了一个字段,然后你找到了这个字段,提交,服务器又告诉你少填写了一个字段...如此往复几遍之后,你终于确定所有的字段都已经填写,忐忑的点击提交,服务器又告诉你邮箱地址这一项重复了,估计你已经想砸电脑了。

       这就是ajax出现之前的世界,只要稍微复杂一点的表单,填写起来都是噩梦。这就是我们说到的同步。

       所谓同步,就是客户端发起请求,服务器端进行处理及响应,而这个过程客户端完全是在等待。当服务器端响应完成之后,客户端会重新载入页面,如果有错误,只能再次发起请求,再次等待。这就让人很无奈了...

1.2-异步

       同样是上面的复杂的表单,当你填写到邮箱地址的时候,页面当时就把邮箱地址发送到服务器,服务器处理之后发现邮箱地址重复,将这个结果发送的页面。但是在这个过程中,你还是可以填写其他的内容,你可以给邮箱地址一栏添加样式,并不会重新刷新整个页面。这样一来,你所有的填写错误,都会在页面实时的显示出来,也可以实时的更正。这整合过程中,不会有整个页面的刷新、提交和等待。

       这就是ajax技术出现之后的异步的世界,所谓异步,客户端发起请求,服务端进行处理及相应,而这时客户端完全可以进行其他操作,不需等待,页面的操作和服务器之间的操作不会造成堵塞。

       那为什么之前不采用异步的方式呢?异步是怎么实现的?是因为同步的世界少了一个对象。

1.3-XMLHttpRequest

       有了这个对象,就可以将同步变成异步,它可以用于后台和服务器交换数据,而且数据的交换不需加载整个页面。所以说,有了这个对象,才有了使用ajax实现异步请求,进行局部刷新。那么,如何实现ajax技术呢?需要以下内容:

  • 运用HTML和CSS来实现页面,表达信息;
  • 运用XMLHttpRequest和web服务器进行数据的异步交换;
  • 运用JavaScript操作DOM,实现动态局部刷新。

       我们主要来看第二点:运用XMLHttpRequest和web服务器进行数据的异步交换。XMLHttpRequest(以下简称XHR)究竟如何来使用呢?首先需要实例化一个XHR对象:       

var request = new XMLHttpRequest();

       目前来说,绝大多数浏览器都支持标准的XHR对象及其构造函数。但是对于早期的IE5及IE6浏览器,是不能直接new的,可以使用如下方法,只需要判断有没有XMLHttpRequest这种定义,然后采取不同的方法即可。(当然现在已经基本不考虑IE5和IE6了)

var request;
if(window.XMLHttpRequest){
   request = new XMLHttpRequest();//other broswer;
}else{
   request = new ActiveXObject("Microsoft.XMLHTTP");//IE5,IE6
}

2-HTTP请求

       在学习XHR的相关知识之前,我们需要先来了解一下HTTP请求。

2.1-HTTP概念

       HTTP仿佛一个最熟悉的陌生人。经常听到它的名字,但是对于它是什么以及它的原理却不是很清楚。HTTP是计算机通过网络进行通信的规则,是浏览器能够从web服务器请求信息和服务。它是一种无状态协议,也就是说服务端不保留连接的相关信息,当发起请求、返回响应之后,连接就被关闭了,整个处理过程是没有记忆的;如果后续的处理需要之前连接中传递的一些信息,那么只能重新传递。

       一个完整的HTTP请求过程,通常有下面7个步骤:

  1. 建立TCP连接;          
  2. Web浏览器向Web服务器发送请求命令;
  3. Web浏览器发送请求头信息;
  4. Web服务器应答;
  5. Web服务器发送应答头信息;
  6. Web服务器向浏览器发送数据;
  7. Web服务器关闭TCP连接

2.2-HTTP请求 

       我们先来看一下请求的过程,一般来说,一个HTTP请求由四部分组成:

  1. HTTP请求的方法,比如是GET请求还是POST请求;
  2. 正在请求的URL,也就是请求的服务器的地址;
  3. 请求头,包含一些客户端环境信息,身份验证信息等;
  4. 请求体,也就是请求正文,请求正文中可以包含客户端提交的查询字符串信息,表单信息等

       一般请求头和请求体之间会有一个空行,用于区分二者。下图是一个典型的HTTP请求:

       

       可以看出,这个示例是一个登录操作,发送的是用户名和密码,而请求方式却是GET,这是非常不安全的,为什么呢?

2.3-GET和POST

       

       从字面意思可以看出,GET(得到)请求一般用于信息的获取,而POST(发送)请求一般用于修改服务器上的资源。其实可以理解为GET请求是安全的,因为它一般用于获取信息,而不是修改信息。也就是说,GET请求一般用于查,而POST请求一般用于增、删、改。但是需要注意,GET发送的信息对任何人都是可见的,因为它所有的变量名和值都显示在URL中,使用URL发送参数,所以对发送信息的树梁也有限制,一般在2000个字符。

       POST(发送)请求一般用于想服务器发送一些信息,一般用于修改服务器上的资源。通过POST方法,一般用来从表单发送一些数据,这些数据并不在URL中显示,对其他人不可见,所有的值都会嵌入HTTP请求体中。它对于发送信息的数量没有限制。

       大家可能听过这样一种说法:GET请求是幂等的。一个GET请求执行一次和执行一万次的效果是一样的,影像是相同的。比如从数据库中查询用户信息,并不会因为查询次数的多少而改变查询到的值。

2.4-HTTP响应

      一个HTTP响应一般由三部分组成:

  1. 一个数字和文字组成的状态码,用于显示请求是否成功;
  2. 响应头,和请求头一样包含许多信息,例如服务器类型,日期时间,内容类型和长度等;
  3. 响应体,即响应正文

       同样,响应头和响应体之间也会有一个空行,下图是一个没有响应体的HTTP响应:

        

        这里需要着重说一下响应的状态码,HTTP状态码由3位数字构成,其中首位数字定义了状态码的类型:

  • 1XX:信息类,表示收到Web浏览器请求,正在进一步处理中;
  • 2XX:成功,表示用户请求被正确接收和处理,例如:200 OK;
  • 3XX:重定向,表示请求没有成功,客户必须采取进一步的动作;
  • 4XX:客户端错误,表示客户端提交的请求有错误,例如:404 Not Found;
  • 5XX:服务器错误,表示服务器不能完成对请求的处理,例如:500     

3-深入了解XMLHttpRequest

       了解了HTTP请求,我们回到XHR中来。

3.1-XMLHttpRequest发送请求

       XHR对象中有如下方法,可以将请求发送到服务器:

open(method,url,async)
send(string)

       open方法中:method参数规定了请求的方式,如GET;url参数规定了请求的地址;async规定本次请求是否使用异步方式,默认true。通过open方法就可以调用HTTP请求,调用之后需要将请求发送到服务器,这时就需要使用send方法,它的参数string规定了本次请求参数的主体。在GET请求时其实是没有主体的,所有的参数都写在URL中,所以send方法可以不传参,或者传null;而使用POST的话send方法就一定要传参了,要不然没有什么意义。下面是一些例子:

request.open("GET","localhost/get",true);
request.send();

request.open("POST","localhost/post",true);
request.send();//无意义

request.open("POST","localhost/create",true);
request.setRequestHeader("Content-type","application/x-www-form-urlencoded");//发送表单
request.send("name=haozz&num=0611");

       我们重点来看第三段代码,中间多了setRequestHeader方法,是用于设置HTTP头信息,它告诉web服务器我们是在发送一个表单,所以设置了Content-type这个键的值为application/x-www-form-urlencoded,它有两个值,一般不发送文件都采用这个值。需要注意的是setRequestHeader方法一定要写在open和send之间。

3.2-XMLHttpRequest取得响应

       我们的请求发送到服务器之后,服务器总需要给出点响应,那么服务器如何获取响应呢,采用如下方法:    

  • responseText:获得字符串形式的响应数据
  • responseXML:获得XML形式的响应数据
  • status和statusText:以数字和文本形式返回HTTP状态码
  • getAllResponseHeader():获取所有的响应报头
  • getResponseHeader(name):查询响应头中某个字段的值

       XHR通常都是异步使用的,我们发送一个请求,然后send方法就立刻返回了,那么怎么知道服务器响应是否正确了呢。上述的几个方法,只是用来获取响应值。而readyState属性是为了在响应返回成功的时候得到通知。readyState属性的值:

  • 0:请求未初始化,open还未调用;
  • 1:服务器连接已建立,open已经调用了;
  • 2:请求以接收,也就是接收到头信息了;
  • 3:请求处理中,也就是接收到响应主体了:
  • 4:请求已完成,且响应已就绪,也就是相应完成了

       如果想知道一个服务器的响应有没有完成,只需要监听readyState属性值的变化:

var request = new XMLHttpRequest();
request.open("GET","localhost/get",true);
request.send();
request.onreadystatechange = function(){
    if(request.readState === 4 && request.status === 200){
       //do something...
    }
}

       我们可以看到,上面的例子非常简单,但是它也涵盖了ajax大部分的内容。

4-Ajax的简单例子

       了解了基本概念和实现方式之后,我们可以通过一个简单的例子,来巩固一下刚才的知识。需要实现以下需求:

  1. 查询员工信息,可以通过输入员工编号查询员工基本信息;
  2. 新建员工信息,包含员工姓名,编号,性别,职位。

4.1-服务端实现

       服务端的代码我们采用SpringBoot+Mybatis为主要框架,来实现上述需求。MySql数据库中插入以下数据以备查询:

        

        Controller层代码:

package com.csdn.myboot.controller;

import com.csdn.myboot.domain.EmpDomain;
import com.csdn.myboot.service.EmpService;
import com.csdn.myboot.utils.CommonRspVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @Auther: haozz
 * @Date: 2018/6/29 16:18
 * @Description: 员工管理控制器
 */
@RestController
@RequestMapping(value = "/emp")
public class EmpCtrl {

    @Autowired
    private EmpService empService;

    /**
     * Description: 新建员工信息
     *
     * @auther: haozz
     * @param:
     * @return: 
     * @date: 2018/6/28 17:16
     */
    @PostMapping(value = "/createEmp")
    public CommonRspVo createEmp(@RequestBody EmpDomain empDomain){
        CommonRspVo vo = new CommonRspVo();
        int i = empService.createEmp(empDomain);
        return vo;
    }
    
    /**
     * Description: 根据员工id查找员工信息
     *
     * @auther: haozz
     * @param: 
     * @return: 
     * @date: 2018/6/29 17:17
     */
    @GetMapping(value = "/findEmp")
    public CommonRspVo findEmp(Integer id){
        CommonRspVo vo = new CommonRspVo();
        EmpDomain emp = empService.findEmp(id);
        vo.setData(emp);
        return vo;
    }
}

       上述Controller层代码中,@RequestBody标注接收的是一个Json对象的字符串,在ajax请求中,使用JSON.stringify(data)的方式就能将对象变为Json字符串,同时也需要指定dataType:"json",contentType:"application/json",这样就可以将一个对象或者List传导Java端,使用@RequestBody即可绑定对象或者List。其余代码非常简单,这里不多赘述,大家可以使用Postman等工具自行测试。

4.2-客户端实现

       接下来就是最为重要的客户端的实现,编写一个简单的jsp页面:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>EmpMana</title>
    <script src="../../js/jquery.min.js"></script>
    <script src="../../js/layui/layui.js"></script>
    <script src="../../js/vue.js"></script>
    <link rel="stylesheet" href="../../js/layui/css/layui.css">
</head>
<body>
<div id="root">
    添加员工:<br/>
    姓名:<input type="text" v-model="name"/><br/>
    编号:<input type="text" v-model="num"/><br/>
    性别:<input type="text" v-model="sex"/><br/>
    职位:<input type="text" v-model="job"/><br/>
    <input type="button" @Click="addEmp" value="提交"/>
    <input type="button" @Click="clearForm" value="刷新"/>
</div>
<br/>
<div id="find">
    输入需要查找的员工编号:<br/>
    员工编号:<input type="text" v-model="empNum"/><br/>
    <input type="button" @Click="findEmp" value="点我查找"/>
</div>
<br/><br/>
<p id="result"></p>
<script>
    var findVue = new Vue({
        el:"#find",
        data:{
            empNum:""
        },
        methods:{
            findEmp:function(){
                var num = this.empNum;
                if(/\D/.test(num)){
                    alert("只能输入数字");
                    this.empNum = "";
                    return;
                }
                $.ajax({
                    url:"/emp/findEmp",
                    type:"get",
                    data:{num:num},
                    contentType:"application/json",
                    success:function(data){
                        debugger;
                        alert(JSON.stringify(data.data));
                    }
                })
            }
        }
    })
    var myVue = new Vue({
        el:"#root",
        data:{
            name:"",
            num:"",
            sex:"",
            job:""
        },
        methods:{
            addEmp:function(){
                debugger;
                var empDomain = {
                    name: this.name,
                    num:this.num,
                    sex:this.sex,
                    job:this.job
                };
                $.ajax({
                    url:"/emp/createEmp",
                    type:"post",
                    contentType:"application/json",
                    data:JSON.stringify(empDomain),
                    success:function(data){
                        alert(data.message);
                        myVue.clearForm();
                    }
                })
            },
            clearForm:function(){
                this.name = "";
                this.num = "";
                this.sex = "";
                this.job = "";
            }
        }
    })
</script>
</body>
</html>

       这里使用JQuery形式的Ajax提交,也可以使用原生JS进行提交,代码如下:

    //查询员工
     var request = new XMLHttpRequest();
     request.open("GET","/emp/findEmp?num="+num);
     request.send();
    //添加员工
    var request = new XMLHttpRequest();
    request.open("POST","/emp/createEmp");
    var data = "name="+name+"&num="+num+"&sex="+sex+"&job="+job;
    //设置为表单提交
    request.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
    request.send(data);

       也可以使用上述的监听readyState的方法,在Ajax请求完成之后进行其他操作。这里我们不在讲述原生的Ajax请求方式,重点关注JQuery方式。

       我们可以来做一个测试,查询编号为3的员工,请求之后查看浏览器F12:

          新增员工与此类似,大家可以自行测试。

5-JQuery中的Ajax

         上面已经提到,通常我们不会直接使用原生的JS来实现Ajax请求,一般会用第三方JS库,比如JQuery之类。而这些JS库,已经封装了Ajax请求的方法。这样我们在实际使用时,不用再关注浏览器的兼容性和不同的实现,也可以很方便的实现第三方库中的方法,大大简化操作。我们关注一下JQuery实现Ajax。

jQuery.ajax([settings])

       利用JQuery提供的这个方法,就可以很方便的实现Ajax请求。其中有很多设定值:

  • type:类型,默认为"GET"
  • url:请求发送的地址
  • data:是一个对象,指请求发送到服务器的数据
  • contentType:发送到服务器的数据的编码类型,默认值"application/x-www-form-urlencoded"适用于大多数场合。另外"application/json"指json类型,"multipart/form-data"用于上传下载
  • dataType:预期服务器返回的数据类型。如果不指定,jQuery将自动根据HTTP包MIME信息来智能判断,一般采用json格式,可以设置为"json"
  • success:是一个方法,请求成功后的回调函数,传入返回后的数据,以及包含成功代码的字符串
  • error:是一个方法,请求失败时的回调函数。传入XMLHttpRequest对象

       此外,还有cache、beforeSend、complete等其他设定值,这里不再赘述。

6-跨域

6.1-什么是跨域

        先来看一下一个域名地址的组成:

        一个域名地址一般由协议子域名主域名端口号(一般默认80)以及请求资源地址组成。

  • 当协议、子域名、主域名和端口号中任意一个不相同时,两个域名地址之间就算做不同域
  • 不同域之间相互请求资源,就算作“跨域”

        我们可以尝试将上面查询员工的ajax请求中的url稍作修改,变成:

url:"http://localhost:8888/emp/findEmp"

        发起查询员工的请求,这时一切正常,但是如果改成下面这样:

url:"http://127.0.0.1:8888/emp/findEmp",

在js中,会认为localhost和127.0.0.1不是同一个域名。再次发起,会得到浏览器的报错:

        可以看到控制台提示“Failed to load ...”,并且给出了403响应码。可是我们将鼠标放在上图红框中的链接上,选择在新标签页中打开,却可以得到返回的数据,这是为什么呢?因为没有访问的权限。

  • JavaScript处于安全方面的考虑,不允许跨域调用其他页面的对象。跨域就是因为JavaScript  同源策略的限制,a域名下的js无法操作b或c域名下的对象

        比如下面的例子:

  • www.abc.com/index.html调用www.abc.com/service.php-非跨域
  • www.abc.com/index.html调用www.efg.com/service.php-跨域(主域名不同)
  • www.abc.com/index.html调用bbs.abc.com/service.php-跨域(子域名不同)
  • www.abc.com/index.html调用www.abc.com:81/service.php-跨域(端口号不同)
  • www.abc.com/index.html调用https:www.abc.com/service.php-跨域(协议不同)

6.2-跨域方式-代理

        跨域是经常遇到的问题。一种比较常见的方法,就是在同域名的web服务器上创建一个代理。比如现在有两个服务器:北京(www.beijing.com)和上海(www.shanghai.com),北京服务器想要调用上海服务器的服务,我们不从北京前端直接调用上海,而是用北京前端调用北京代理服务(这样就不涉及跨域了),而这个代理服务其实是从后端去访问上海服务,然后把服务的响应值返回给北京前端。当然这种代理属于后台技术,这里不详细叙述。

6.2-跨域方式-jsonp

         jsonp全程是JSON with Padding,是为了解决跨域请求资源而产生的解决方案,是一种依靠开发人员创造出的一种非官方跨域数据交互协议。jsonp可以用于解决主流浏览器的get请求跨域数据访问的问题。

        凡是拥有src这个属性的标签都可以直接跨域请求资源,例如<script>、<img>、<iframe>。

        比如我在http://www.aaa.com/jsonp.html页面下要请求www.bbb.com/message.js中的资源:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>

<script type="text/javascript">
     var message = function(data) {
        alert(data[1].title);
     };
</script>
 
<script type="text/javascript" src="http://bbb.com/message.js"></script>
</head>
<body>
<div id='mydiv'></div>
</body>
</html>

         而www.bbb.com/message.js是这样的:

message([
      {"id":"1", "title":"张三丰"},
      {"id":"2", "title":"张翠山"},
      {"id":"3", "title":"张无忌"}
]);

        我们在aaa中定义了messgae方法,并且载入了bbb上的目标js,也就是说script标签是可以向其他域名提交http请求的(实际上img,iframe也可以)。而载入了bbb的js之后,他会直接去调用上面定义的message方法。因为服务器返回数据时会将这个callback参数(上面例子中的message)作为函数名来包裹住JSON数据,这样客户端就可以定制自己的函数来处理返回数据了。(大家只要记住aaa中定义的方法名(message)和bbb中的json数据(message)命名一致就好了。)

         jQuery的Ajax方法本身就支持jsonp跨域方式。使用起来特别方便:

$.ajax({
         type : 'get',
         url:'http://www.bbb.com/ajaxjsonp',
         data : {
             name : name,
             sex : sex
         },
         cache :false,
         jsonp: "callback",
         jsonpCallback:"success",
         dataType : 'jsonp',
         success:function(data){
             alert(data);
         },
         error:function(data){
             alert('error');
         }        
     });

         其中,jsonp:“callback”是传递给请求处理程序的,以获得jsonp回调函数的参数名;jsonpCallback是自定义的jsonp回调函数名称,并且dataType必须指定为‘jsonp’。在上面这个例子中,当我们发起请求时,请求头中会自动带入callback参数,并指定为“callback=success”,并且服务端在获取到这个参数后,会返回一个json数据,这个数据命名就是“success”。(当我们不指定jsonCallback时,会默认为jQuery自动生成的随机函数名)

         上面的例子都只是对get请求实现。因为jsonp的原理只能对get请求实现跨域,jsonp不支持post请求。

6.3-跨域方式-XHR2

        如果我们要实现POST请求的跨域,该怎么办呢?HTML5中的XMLHttpRequest Level2(XHR2)已经实现了跨域访问以及一些其他的新功能,但是可惜的是IE10并不支持XHR2。这个方法非常简单,在客户端基本不需要做任何改造。只需要服务器端做出一些改造,服务器端以Java为例:

// some code
response.setHeader("Access-Control-Allow-Origin","*");
response.setHeader("Access-Control-Allow-Methods","POST");
// some code

        其中的response是HttpServletResponse的实例。Allow-Origin的*是指任何域都可以请求到,当然可以设为特定的域,比如www.aaa.com。Allow-Methods指的是支持的跨域方式,这里指定为POST方式。     

猜你喜欢

转载自blog.csdn.net/hz_940611/article/details/80850075
今日推荐