zoukankan      html  css  js  c++  java
  • Ajax和跨域请求和倒计时抢购案例

    Ajax

    一.概述

    Web 程序最初的目的就是将信息(数据)放到公共的服务器,让所有网络用户都可以通过浏览器访问。

    在次之前,我们可以通过以下几种方式让浏览器发出对服务端的请求,获取服务端的数据:

    • 地址栏输入地址,回车,刷新
    • 特定元素的href或src属性
    • 标点提交

    这些方式如果通过代码的方式进行编程,可以通过js直接发送网络请求,那么web的可能就会更多,至少不是“单机游戏”

    AJAX(Asynchronous JavaScript and XML),最早出现在 2005 年的 Google Suggest,是在浏览器端进行网络编 程(发送请求、接收响应)的技术方案,它使我们可以通过 JavaScript 直接获取服务端最新的内容而不必重新加载 页面。让 Web 更能接近桌面应用的用户体验。

    二.初步使用

    使用Ajax可以类比我们访问页面的过程,Ajax其实就是一套API,核心类型:XMLHttpRequest。
    为什么是XML,传输的不是JSON吗?历史原因,在JSON还没有流行起来之前,之前不便修改
    //1.创建一个XMLHttpRequest类型对象,--安装浏览器(用户代理)  
    var xhr = new XMLHttpRequest()  
    //2.打开与一个网址之间的连接,  --相当于在地址栏输入访问地址
    xhr.open('GET','./time.php')
    //3.通过连接发送一次请求  ---相当于回车或者点击访问发送请求
    xhr.send(null)
    //4.指定xhr状态变化事件处理函数, --相当于处理网页呈现后的操作
    //因为客户端永远不知道服务端何时才能返回我们需要的响应
    //所以Ajax API采用事件的机制(通知的感觉)
    xhr.onreadystatechange = function(){
        //通过xhr的readyState判断此次请求的响应是否接收完成
        if(this.readyState ==4){
            //通过xhr的reponseTexth获取到响应的响应体
            console.log(this)
        }
    }
    
    //onreadystatechange,并不是只在响应时触发,时状态改变就触发
    
    

    2.1readyState

    ​ 由于 readystatechange 事件是在 xhr 对象状态变化时触发(不单是在得到响应时),也就意味着这个事件会被 触发多次,所以我们有必要了解每一个状态值代表的含义:

    readyState 状态描述 说明
    0 UNSENT 代理(XHR)被创建,但尚未调用open()方法
    1 OPENED open()方法已经被调用,建立了连接
    2 HEADERS_RECEIVED send()方法已经被调用,并且已经可以获取状态行和响应头
    3 LOADING 响应体下载中,responseText属性可能已经包含部分数据
    4 DONE 响应体下载完成,可以直接使用responseText

    2.2 时间轴

    graph LR 初始化-->建立连接 建立连接-->接收响应头 接收响应头-->响应体加载 响应体加载 -->加载完成
    var xhr =new XMLHttpRequest()
    console.log(xhr.readyState)  // 0  (初始化,请求代理对象)
    
    xhr.open('GET','time.php')
    console.log(xhr.readyState)  //1  (open()方法已经调用,建立一个与服务端特定接口的连接)
    
    xhr.send()
    xhr.addEventListener('readystatechange',function(){
        switch(this.readyState){
            case 2:  // 2  已经接收到了响应报文的响应头
             //可以拿到头 console.log(this.getAllResponseHeaders())
             console.log(this.getResponseHeader('server'))
             //但是还没有拿到体
             console.log(this.reponseText)
             break;
                
            case 3:  //3 正在下载响应报文的响应体,有可能响应体为空,也有可能不完整
             //在这里处理响应体不可靠
             console.log(this.responseText)
             break;
             
            case 4:   //4 ok(整个响应报文已经完整的下载下来了)
            //这里处理响应体
            console.log(this.responseText)
            break;  
        }
    })
    
    • 通过理解每一个状态值的含义得出一个结论:一般我们都是在 readyState 值为 4 时,执行响应的后续逻辑。

    • xhr.onreadystatechange = function(){
          if(this.readyState ===4){
              //后续逻辑
          }
      } 
      

    2,3遵循HTTP

    本质上 XMLHttpRequest 就是 JavaScript 在 Web 平台中发送 HTTP 请求的手段,所以我们发送出去的请求任然是HTTP 请求,同样符合 HTTP 约定的格式:

    //设置请求报文的请求行
    xhr.open('GET','./time.php')
    //设置请求头
    xhr.setRequestHeader('Accept','text/plain')
    //设置请求体
    xhr.send(null)
    
    xhr.onreadystatechange = function () {
       if (this.readyState === 4) {
          // 获取响应状态码 
          console.log(this.status) 
          // 获取响应状态描述 
          console.log(this.statusText) 
          // 获取响应头信息 
          console.log(this.getResponseHeader('Content‐Type'))// 指定响应头 
          console.log(this.getAllResponseHeader()) // 全部响应头 
          // 获取响应体 
          console.log(this.responseText)// 文本形式 
          console.log(this.responseXML) // XML 形式,了解即可不用了 
      } 
     }
    

    三.具体用法

    3.1 GET 系列请求 (获取内容)

    get

    delete 一般应用于告诉服务器,从服务器上删除一点东西

    head 只想获取响应头内容,告诉服务器响应主体内容不要了

    options 试探性请求,发个请求给服务器,看看服务器能不能接收到,能不能返回

    通常在一次 GET 请求过程中,参数传递都是通过 URL 地址中的 ? 参数传递。

    var xhr = new XMLHttpRequest()
    //GET请求传递参数通常使用的是问号传递
    //这里可以在请求地址后面加上参数,从而传递数据到服务端
    xhr.open('GET','./delete.php?id=1')
    //一般在GET请求时无需设置响应体,可以传null或者干脆不传
    xhr.send(null)
    xhr.onreadystatechange=function(){
        if(this.readyState===4){
            console.log(this.responseText)
        }
    }
    
    //一般情况下 URL 传递的都是参数性质的数据,而 POST 一般都是业务数据
    

    3.2 POST请求(推送内容,多用于表单,需要推送一些内容)

    post

    put 和delete对应,一般是想让服务器把我传递的信息存储到服务器上(一般应用于文件和大的数据,图片等)

    POST 请求过程中,都是采用请求体承载需要提交的数据。

    var xhr = new XMLHttpRequest()
    //open 方法的第一个参数的作用就是设置请求的method
    xhr.open('POST', './add.php')
    //设置请求头中的 Content‐Type 为 application/x‐www‐form‐urlencoded
    //标识此次请求的请求体格式为 urlencoded 以便于服务端接收数据
    xhr.setRequestHeader('Content‐Type', 'application/x‐www‐form‐urlencoded')
    // 需要提交到服务端的数据可以通过 send 方法的参数传递
    // 格式:key1=value1&key2=value2
    xhr.send('key1=value1&key2=value2')
    xhr.onreadystatechange = function () {
        if (this.readyState === 4) {
            console.log(this.responseText)
        }
    }
    
    

    3.3 同步和异步

    关于同步和异步的概念在生活中有很多常见的场景

    xhr.open() 方法第三个参数要求传入的是一个 bool 值,其作用就是设置此次请求是否采用异步方式执行,默认 为 true ,如果需要同步执行可以通过传递 false 实现:

    console.log('before ajax')
    var xhr = new XMLHttpRequest() 
    // 默认第三个参数为 true 意味着采用异步方式执行
    xhr.open('GET', './time.php', true) 
    xhr.send(null) 
    xhr.onreadystatechange = function () { 
    	if (this.readyState === 4) { 
    	// 这里的代码最后执行 
    	console.log('request done') 
    	} 
    }
    console.log('after ajax')
    

    如果采用同步方式执行,则代码会卡死在 xhr.send() 这一步:

    console.log('before ajax')
    var xhr = new XMLHttpRequest()
    // 同步方式 
    xhr.open('GET', './time.php', false) 
    // 同步方式 执行需要 先注册事件再调用 send,否则 readystatechange 无法触发
    xhr.onreadystatechange = function () { 
    	if (this.readyState === 4) { 
        // 这里的代码最后执行 
        console.log('request done') 
        } 
    }
    xhr.send(null) 
    console.log('after ajax')
    

    演示同步异步差异。

    一定在发送请求 send() 之前注册 readystatechange (不管同步或者异步)

    为了让这个事件可以更加可靠(一定触发),一定是先注册

    了解同步模式即可,切记不要使用同步模式。

    至此,我们已经大致了解了 AJAX 的基本 API 。

    3.4 响应数据格式

    提问:如果服务端返回一个复杂数据,该如何处理?

    我们关心的就是服务端发出何种格式的数据,这种格式如何在客户端用Javascript解析

    • 对于复杂的响应数据(如复杂的表格表单数据):

    • 首先我们需要进行很多的DOM操作
      //先创建行
      //在创建列
      //再将列添加到行
      //再将行添加再tbody
      //拼接字符串
      等等大量的操作
      这里也引出:Vue,React等响应式数据框架的好处
      

    3.5 处理响应数据渲染

    模板引擎

    模板引擎实际上就是一个 API,模板引擎有很多种,使用方式大同小异

    目的为了可以更容易的将数据渲染到 HTML中

    3.6 兼容方案

    XMLHttpRequest 在老版本浏览器(IE5/6)中有兼容问题,可以通过另外一种方式代替

    var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')
    

    四.封装

    4.1 AJAX请求封装

    /**
     * 发送一个 AJAX 请求 
     * @param {String} method 请求方法 
     * @param {String} url 请求地址 
     * @param {Object} params 请求参数 
     * @param {Function} done 请求完成过后需要做的事情(委托/回调) 
    */
    function ajax (method, url, params, done) { 
        // 统一转换为大写便于后续判断 
        method = method.toUpperCase()
        
        // 对象形式的参数转换为 urlencoded 格式 
        var pairs = [] 
        for (var key in params) { 
            pairs.push(key + '=' + params[key]) 
        }
        var querystring = pairs.join('&')
        
        var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')
        
        xhr.addEventListener('readystatechange', function () { if (this.readyState !== 4) return
        
        // 尝试通过 JSON 格式解析响应体 
        try { 
            done(JSON.parse(this.responseText)) 
        } catch (e) { 
            done(this.responseText) 
        } 
      })
        
        // 如果是 GET 请求就设置 URL 地址 问号参数 
        if (method === 'GET') { 
            url += '?' + querystring 
        }
        
        xhr.open(method, url)
        
        // 如果是 POST 请求就设置请求体 
        var data = null if (method === 'POST') { 
            xhr.setRequestHeader('Content‐Type', 'application/x‐www‐form‐urlencoded') 		   data = querystring 
        }
        xhr.send(data) 
    }
    ajax('get', './get.php', { id: 123 }, function (data) { 
        console.log(data) 
    })
    ajax('post', './post.php', { foo: 'posted data' }, function (data) { 	
        console.log(data) 
    })
    

    4.2 jQuery中的AJAX

    jQuery 中有一套专门针对 AJAX 的封装,功能十分完善,经常使用,需要着重注意。

    $.ajax
    $.ajax({ 
        url: './get.php', 
        type: 'get', 
        dataType: 'json', 
        data: { id: 1 }, 
        beforeSend: function (xhr) { 
            console.log('before send') 
        },
        success: function (data) {  
            console.log(data) 
        },
        error: function (err) { 
            console.log(err) 
        },
        complete: function () { 
            console.log('request completed') 
        } 
    })
    

    常用参数选项:

    • url:请求地址
    • type:请求方法,默认为 get
    • dataType:服务端响应数据类型
    • contentType:请求体内容类型,默认 application/x-www-form-urlencoded
    • data:需要传递到服务端的数据,如果 GET 则通过 URL 传递,如果 POST 则通过请求体传递
    • timeout:请求超时时间
    • beforeSend:请求发起之前触发
    • success:请求成功之后触发(响应状态码 200)
    • error:请求失败触发
    • complete:请求完成触发(不管成功与否都回进行的操作)
    $.get
    上面的$.ajax接近于底层API
    jquery中还有高度封装的API
    $.get('time.php',function(res){
        console.log(res)
    })
    
    $post
    $.post('time.php',function(res){
        console.log(res)
    })
    

    4.3 其他

    • $.getJSON() ----不管返回什么,都转成json格式
    • $(selector).load() ---可实现局部加载
    • $.getScript() ---
    • 还有很多,查阅官方文档(也可查看别人翻译的中文文档)

    五.跨域

    跨域可以说跟ajax一点关系没有,也可以说与ajax有着绝对性的联系

    5.1相关概念

    同源策略是浏览器的一种安全策略,所谓同源是指,域名,协议,接口,完全相同,只有同源的地址才可以相互通过AJAX的方式请求。

    同源或不同源说的是两个地址之间的关系,不同源地址之间请求我们称之为跨域请求

    同源的页面数据能够相互请求

    什么是同源?例如:http://www.example.com/detail.html 与一下地址对比

    对比地址 是否同源 原因
    http://api.example.com/detail.html 不同源 域名不同
    https://www.example.com/detail.html 不同源 协议不同
    http://www.example.com:8080/detail.html 不同源 端口不同
    http://api.example.com:8080/detail.html 不同源 域名,端口不同
    https://api.example.com/detail.html 不同源 协议、域名不同
    https://www.example.com:8080/detail.html 不同源 端口、协议不同
    http://www.example.com/other.html 同源 只是目录不同

    5.2 解决方案

    5.2.1 JSONP

    JSON with Padding,(padding:边距,补充)(jsonp可理解为json的补充)是一种借助于 script 标签发送跨域请求的技巧。

    其原理就是在客户端借助 script 标签请求服务端的一个动态网页(php 文件),服务端的这个动态网页返回一 段带有函数调用的 JavaScript 全局函数调用的脚本,将原本需要返回给客户端的数据传递进去。

    以后绝大多数情况都是采用 JSONP 的手段完成不同源地址之间的跨域请求

    客户端 http://www.zce.me/users-list.html

    <script src="http://api.zce.me/users.php?callback=foo"></script>
    

    服务端 http://api.zce.me/users.php?callback=foo 返回的结果

    foo(['我', '是', '你', '原', '本', '需', '要', '的', '数', '据'])
    

    总结一下:由于 XMLHttpRequest 无法发送不同源地址之间的跨域请求,所以我们必须要另寻他法,script 这种方 案就是我们最终选择的方式,我们把这种方式称之为 JSONP,如果你不了解原理,先记住怎么用,多用一段时间再 来看原理。

    问题:

    1. JSONP 需要服务端配合,服务端按照客户端的要求返回一段 JavaScript 调用客户端的函数

    2. 只能发送 GET 请求

    注意:JSONP 用的是 script 标签,更 AJAX 提供的 XMLHttpRequest 没有任何关系!!!

    jQuery 中使用 JSONP 就是将 dataType 设置为 jsonp 76

    其他常见的 AJAX 封装 库: Axios

    5.2.2 CORS

    Cross Origin Resource Share ,跨域资源共享 (直接请求跨域资源)

    但是因为版本比较新,存在一些兼容问题

    // 允许远端访问 
    header('Access‐Control‐Allow‐Origin: *');
    

    这种方案无需客户端作出任何变化(客户端不用改代码),只是在被请求的服务端响应的时候添加一个 Access- Control-Allow-Origin 的响应头,表示这个资源是否允许指定域请求。

    六.ajax倒计时抢购案例

    <script>
     //new Date()获取客户端本地当前时间(不能拿它做重要依据,因为用户可以随时更改)
     //倒计时抢购需要从服务器获取当前时间,而不是从本地获取  AJAX
     //问题:时间差(从服务器把时间给客户端,到客户端获取到这个信息,中间经历的时间就是时间差,而时间差是不可避免的,我们应尽可能减少这个误差)
        - 从响应头获取时间(AJAX异步)
        -基于HEAD请求(只获取响应头信息)
    
    let target = new Date('2019/09/14 18:00:00');
    	now = null ;
    	timer = null;
    //我们不可能每一秒都从服务器请求最新时间,应该基于第一次获取的时间,用这个时间来记时
    
    //=>从服务器获取时间,获取到时间后再做其他事情
    function func(callback){
        let xhr = new XMLHttpRequest;
        xhr.open('HEAD','json/data.json');
        xhr.onreadystatechange = function(){
            if(!/^(2|3)d{2}$).test(xhr.status) return ;
               if(xhr.readyState ===2){
                now = new Date(xhr.getResponseHeader('Date'))
                //如果传进来的是个函数,就把它执行
                callback && callback();
            }
        }   
        xhr.send(null)
    }
    
    //=>开启倒计时模式
    function computed (){
        let spanTime = target -now;
        if(spanTime <=0){
            //=>到抢购点了:结束定时器
           clearInterval(timer);
           timer = null;
           box.innerHTML = "开抢~";
          return
        }
        //还剩多少个小时
        let hours =  Math.floor(spanTime / (60*60*1000) ) 
        spanTime -= hours* 60*60*1000;
        let minutes = Math.floor(spanTime / (60*1000));
        spanTime -= minutes *60*1000
        let seconds =Math.floor( spanTime /1000)
        box.innerHTML = `距离抢购还剩:${hours}:${minutes}:${seconds}`
        
        //=>每一次计算完,我们需要让now再原来的基础上增加一秒(第一次从服务器获取到时间,后期直接基于这个时间自己减即可,不要每隔一秒从服务器拿一次,服务器压力会特别大)
        //每隔一秒,从服务器获取的时候,增加一
        // 错误:now=new Date(now + 1000); 原因:里面编程字符串拼接了,now是字符串
        now = new Date(now.getTime()+1000)
    }
    func(()=>{
        //=>这个函数能执行,说明已经从服务器获取时间了
        computed();
        timer =setInterval(computed ,1000);
        
    })
    
    <script>
        
    //除了完成这个功能外我们还应该收获到什么?再json传值,或者new Date()获取的时间的时候,很多时候都是字符串,这个时候我们不能直接使用它去做加法,因为可能会造成隐式转换,变成字符串拼接
    
  • 相关阅读:
    各种平衡树板子
    字符串板子
    数学公式/定理/板子整理
    线性筛 板子整理
    set乱搞时需注意的坑点
    可持久化数据结构板子整理(可持久化 线段树/字典树/可并堆)
    洛谷p2483 模板k短路 可持久化可并堆
    p4929 DLX舞蹈链
    百度ai 图像增强与特效
    百度ai php请求获取access_token返回false
  • 原文地址:https://www.cnblogs.com/JCDXH/p/11603416.html
Copyright © 2011-2022 走看看