Ajax(一)-网络编程

Ajax简介

  1. 什么是Ajax?
    1. AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。
    2. AJAX 不是新的编程语言,而是一种使用现有标准的新方法。
    3. AJAX 是与服务器交换数据并更新部分网页的艺术,在不重新加载整个页面的情况下。
  2. 传统网站中存在的问题
    1. 网速慢的情况下,页面加载时间长,用户只能等待
    2. 表单提交后,如果一项内容不合格,需要重新填写所有表单内容
    3. 页面跳转,重新加载页面,造成资源浪费,增加用户等待时间
  3. Ajax 概述
    1. Ajax:标准读音 [ˈeɪˌdʒæks] ,中文音译:阿贾克斯
    2. 它是浏览器提供的一套方法,可以实现页面无刷新更新数据,提高用户浏览网站应用的体验。

Ajax编程基础

Ajax 运行原理及实现

  1. Ajax 运行原理
    1. Ajax 相当于浏览器发送请求与接收响应的代理人,以实现在不影响用户浏览页面的情况下,局部更新页面数据,从而提高用户体验。
  2. Ajax 的实现步骤

     //1.创建 Ajax 对象
     var xhr = new XMLHttpRequest();
     //2. 告诉 Ajax 请求地址以及请求方式
     xhr.open('get', 'http://www.example.com');
     //发送请求
     xhr.send();
     //3. 获取服务器端给与客户端的响应数据
     xhr.onload = function () { 
         console.log(xhr.responseText);
     }
    

Ajax 对象

if(window.XMLHttpRequest){ // >= IE8
    var xhr=new XMLHttpRequest();
}else{ //<IE8
    var xhr=new ActiveXObject("Microsoft.XMLHttp");
}

请求头

  1. 常见的请求头字段

     Host:localhost/127.0.0.1 主机名
     Connection:keep-alive 让服务器和客户端保持一段时间连接
     User-Agent:用户代理,是用户使用的浏览器的类型,版本和一些系统基本信息
     Accept-Language:zh-cn 浏览器接收的语言
     Accept-Encoding:gzip  浏览器接受的编码种类
     Referer: 通知服务器请求来自哪一个地址
     Accept: text/javascript, application/javascript,*/*; 浏览器接受的媒体类型
     Content-Type: application/json;charset=UTF-8   请求参数格式的类型(post请求必须要设置,get不用设置)
    
  2. 设置请求头

     xhr.setRequestHeader('Content-Type', 'application/json');
    

设置请求参数的格式

  1. application/x-www-form-urlencoded

     name=zhangsan&age=20&sex=男
    
  2. application/json

     {name: 'zhangsan', age: '20', sex: '男'}
    
    1. 在请求头中指定 Content-Type 属性的值是 application/json,告诉服务器端当前请求参数的格式是 json
    2. 还需要将json对象转化为json字符串,然后传递给服务器

       JSON.stringify() // 将json对象转换为json字符串
      
  3. 注意: get 请求是不能提交 json 对象数据格式的,传统网站的表单提交也是不支持 json 对象数据格式的。
  4. 代码举例:

     // 1.创建ajax对象
     var xhr = new XMLHttpRequest();
     // 2.告诉Ajax对象要向哪发送请求,以什么方式发送请求
     // 1)请求方式 2)请求地址
     xhr.open('post', 'http://localhost:3000/json');
     // 通过请求头告诉服务器端客户端向服务器端传递的请求参数的格式是什么
     xhr.setRequestHeader('Content-Type', 'application/json');
     // JSON.stringify() 将json对象转换为json字符串
     // 3.发送请求
     //将json对象转化为json对象,才能传递参数
     xhr.send(JSON.stringify({name: 'lisi', age:50}));
     // 4.获取服务器端响应到客户端的数据
     xhr.onload = function (){
     	console.log(xhr.responseText)
     }
    

XHR常用API

  1. xhr.open(method,url,isAsyn);
    1. 建立请求
    2. method:请求类型,get/post
    3. url:请求地址
    4. isAsyn:指定采纳同步(false)或者异步(true)的方式发送请求
  2. xhr.send(body)
    1. body:请求体
    2. 没有请求体,body位置处为空(get请求)

获取服务器端的响应

  1. readyState 属性
    1. 在创建ajax对象,配置ajax对象,发送请求,以及接收完服务器端响应数据,这个过程中的每一个步骤都会对应一个数值, 这个数值就是ajax状态码。
     0:请求未初始化(还没有调用open())
     1:请求已经建立,但是还没有发送(还没有调用send())
     2:请求已经发送
     3:请求正在处理中,通常响应中已经有部分数据可以用了
     4:响应已经完成,可以获取并使用服务器的响应了
    
  2. status 属性
    1. 获取服务端返回的状态结果,状态码:200 成功
  3. onreadystatechange 事件
    1. 当 Ajax 状态码readyState属性发生变化时将自动触发该事件。
    2. 在事件处理函数中可以获取 Ajax 状态码并对其进行判断,当状态码为 4 时就可以通过 xhr.responseText 获取服务器端的响应数据了。

       // 当Ajax状态码发生变化时调用
       xhr.onreadystatechange = function () {
       // 判断当Ajax状态码为4时 
           if (xhr.readyState == 4) {
               // 获取服务器端的响应数据 
               console.log(xhr.responseText);
           }
       }
      
  4. 服务端响应的另一种方式:xhr.onload

     xhr.onload = function (){
         // console.log(typeof xhr.responseText)
     }
    
  5. 服务器端响应的数据格式
    1. 真实的项目中,服务器端大多数情况下会以 JSON 对象作为响应数据的格式。当客户端拿到响应数据时,要将 JSON 数据和 HTML 字符串进行拼接,然后将拼接的结果展示在页面中。
    2. 在 http 请求与响应的过程中,无论是请求参数还是响应内容,如果是对象类型,最终都会被转换为对象字符串进行传输。

       服务端返回json字符串,然后我们将json串转化为json对象
       JSON.parse() // 将 json 字符串转换为json对象
      
    3. 举例:

       // 获取服务器端响应到客户端的数据
       xhr.onload = function (){
           // console.log(typeof xhr.responseText)
           // 将JSON字符串转换为JSON对象
           var responseText = JSON.parse(xhr.responseText);
           // 测试:在控制台输出处理结果
           console.log(responseText)
           // 将数据和html字符串进行拼接
           var str = '<h2>'+ responseText.name +'</h2>';
           // 将拼接的结果追加到页面中
           document.body.innerHTML = str;
       }
      
  6. 两种获取服务器端响应方式的区别

     区别                      onload事件            onreadystatechange事件
     是否兼容IE低版本            不兼容                 兼容
     是否需要判断Ajax状态码       不需要                 需要
     被调用次数                  一次                  多次
    

常见的请求形式

  1. 传统网站表单提交

     <form method="get" action="http://www.example.com">
         <input type="text" name="username"/>
         <input type="password" name="password">
     </form>
     <!– http://www.example.com?username=zhangsan&password=123456 -->
    
  2. GET 请求方式

     xhr.open('get', 'http://www.example.com?name=zhangsan&age=20');
    
  3. POST 请求方式

     xhr.open('post', 'http://www.example.com');
     xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
     xhr.send('name=zhangsan&age=20');
    
  4. 代码举例:

     <input type="text" id="username"><br>
     <input type="text" id="age"><br>
     <input type="button" value="提交" id="btn"><br>
        
     <script type="text/javascript">
         // 获取按钮元素
         var btn = document.getElementById('btn');
         // 获取姓名文本框
         var username = document.getElementById('username');
         // 获取年龄文本框
         var age = document.getElementById('age');
         // 为按钮添加点击事件
         btn.onclick = function () {
             // 创建ajax对象
             var xhr = new XMLHttpRequest();
             // 获取用户在文本框中输入的值
             var nameValue = username.value;
             var ageValue = age.value;
             // 拼接请求参数
             var params = 'username='+ nameValue +'&age=' + ageValue;
    	            	
             /*****get请求*******/
             // 配置ajax对象
             xhr.open('get', 'http://localhost:3000/get?'+params);
             // 发送请求
             xhr.send();
                	
             /*****post请求*******/
             // 配置ajax对象
             xhr.open('post', 'http://localhost:3000/post');
             // 设置请求参数格式的类型(post请求必须要设置)
             xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
             // 发送请求
             xhr.send(params);
                
             // 获取服务器端响应的数据
             xhr.onload = function () {
                console.log(xhr.responseText)
             }
         }
     </script>
    

低版本 IE 浏览器的缓存问题

  1. 问题:在低版本的 IE 浏览器中,Ajax 请求有严重的缓存问题,即在请求地址不发生变化的情况下,只有第一次请 求会真正发送到服务器端,后续的请求都会从浏览器的缓存中获取结果。即使服务器端的数据更新了,客户端依然 拿到的是缓存中的旧数据。
  2. 解决方案:在请求地址的后面加请求参数,保证每一次请求中的请求参数的值不相同。

     xhr.open('get', 'http://www.example.com?t=' + Math.random());
    

浏览器调试Ajax网络调用

  1. F12打开代码调试
  2. 点击Network
  3. 点击XHR,专门用于调试Ajax网络请求

Ajax封装

function ajax (options) {
    // 存储的是默认值
    var defaults = {
        type: 'get',
        url: '',
        data: {},
        header: {
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        success: function () {},
        error: function () {}
    };

    // 使用options对象中的属性覆盖defaults对象中的属性
    Object.assign(defaults, options);
    
    // 创建ajax对象
    var xhr = new XMLHttpRequest();
    // 拼接请求参数的变量
    var params = '';
    // 循环用户传递进来的对象格式参数
    for (var attr in defaults.data) {
        // 将参数转换为字符串格式
        params += attr + '=' + defaults.data[attr] + '&';
    }
    // 将参数最后面的&截取掉 
    // 将截取的结果重新赋值给params变量
    params = params.substr(0, params.length - 1);

    // 判断请求方式
    if (defaults.type == 'get') {
        defaults.url = defaults.url + '?' + params;
    }
    // 配置ajax对象
    xhr.open(defaults.type, defaults.url);
    // 如果请求方式为post
    if (defaults.type == 'post') {
        // 用户希望的向服务器端传递的请求参数的类型
        var contentType = defaults.header['Content-Type']
        // 设置请求参数格式的类型
        xhr.setRequestHeader('Content-Type', contentType);
        // 判断用户希望的请求参数格式的类型
        // 如果类型为json
        if (contentType == 'application/json') {
            // 向服务器端传递json数据格式的参数
            xhr.send(JSON.stringify(defaults.data))
        }else {
            // 向服务器端传递普通类型的请求参数
            xhr.send(params);
        }
    }else {
        // 发送请求
        xhr.send();
    }
    // 监听xhr对象下面的onload事件
    // 当xhr对象接收完响应数据后触发
    xhr.onload = function () {
        // xhr.getResponseHeader()
        // 获取响应头中的数据
        var contentType = xhr.getResponseHeader('Content-Type');
        // 服务器端返回的数据
        var responseText = xhr.responseText;
        
        // 如果响应类型中包含applicaition/json
        //includes判断字符串中是否包含某个字符串
        if (contentType.includes('application/json')) {
            // 将json字符串转换为json对象
            responseText = JSON.parse(responseText)
        }
        
        // 当http状态码等于200的时候
        if (xhr.status == 200) {
            // 请求成功 调用处理成功情况的函数
            defaults.success(responseText, xhr);
        }else {
            // 请求失败 调用处理失败情况的函数
            defaults.error(responseText, xhr);
        }
    }
}

//函数调用
ajax({
    type: 'post',
    // 请求地址
    url: 'http://localhost:3000/responseData',
    data:{
        name:'zhangsan',
        age:20
    },
    header: {
        'Content-Type': 'application/json'
    },
    success: function (data) {
        console.log('这里是success函数');
        console.log(data)
    }
})

Ajax 扩展编程

模板引擎

  1. 模板引擎概述
    1. 客户端给服务端发送请求,服务端将数据和HTML拼接好,将拼接好的html字符串返回给客户端。
    2. 使用ajax给服务端发送请求,服务端将返回json格式的数据,数据和html的拼接将在客户端来完成
    3. 客户端进行数据拼接时就需要模板引擎
  2. 模板引擎的作用:
    1. 将数据和HTML拼接起来
    2. 即使用模板引擎提供的模板语法,可以将数据和 HTML 拼接起来。
  3. 官方地址: https://aui.github.io/art-template/zh-cn/index.html
  4. 使用步骤
    1. 下载 art-template 模板引擎库文件并在 HTML 页面中引入库文件
      1. 打开官网->点击Docs->点击安装->在浏览器中实时编译(下载:template-web.js)
      2. 右击”连接存储为”得到template-web.js文件
       <script src="./js/template-web.js"></script>
      
    2. 准备 art-template 模板

       <script id="tpl" type="text/html">
           <div class="box"></div>
       </script>
      
    3. 告诉模板引擎将哪一个模板和哪个数据进行拼接

       var html = template('tpl', {username: 'zhangsan', age: '20'});
      
    4. 将拼接好的html字符串添加到页面中

       document.getElementById('container').innerHTML = html;
      
    5. 通过模板语法告诉模板引擎,数据和html字符串要如何拼接

       <script id="tpl" type="text/html">
           <div class="box"> {* username }} </div>
       </script>
      
  5. 常用模板语法
    1. 将数据显示在模板中。

       <h2></h2>
       <h2></h2>
       <h2></h2>
      
    2. 原文输出

       //如果数据中携带HTML标签,默认情况下,模板引擎不会解析标签,会将其转义后原文输出。
       <h2></h2>
      
    3. 条件判断

        ... 
        ...  ... 
              
           <div>条件成立 显示我</div>
              
           <div>条件不成立 显示我</div>
              
      
    4. 循环

              
                   
              
      
    5. 导入模板变量

      1. 作用:模板中访问js中的变量、模板中需要使用第三方的插件函数处理数据
      2. 场景:
        1. 服务器返回原始时间格式,我们可以在js中定义处理时间格式的函数,然后导入到模板中使用
      3. 如果函数定义在全局作用域下,则模板中直接可以:$imports.函数名称(参数)使用

         <div>$imports.dataFormat(time)</div>
        
      4. 如果没有定义在全局作用域下:

         //js导入
         template.defaults.imports.变量名 = 变量值;
        
      5. 举例:

         function dateFormat(未格式化的原始时间){
             return '已经格式化好的当前时间'
         }
         template.defaults.imports.dateFormat = dateFormat;
        
  6. 代码举例:

     <!DOCTYPE html>
     <html lang="en">
     <head>
         <meta charset="UTF-8">
         <title>Document</title>
         <!-- 1. 将模板引擎的库文件引入到当前页面 -->
         <script src="/js/template-web.js"></script>
     </head>
     <body>
     <div id="container"></div>
     <!-- 2.准备art-template模板 -->
     <script type="text/html" id="tpl">
         <h1>{*username}} {*age}}</h1>
     </script>
     <script type="text/javascript">
         // 3.告诉模板引擎将那个数据和哪个模板进行拼接
         // 1) 模板id 2)数据 对象类型
         // 方法的返回值就是拼接好的html字符串
         var html = template('tpl', {username: 'zhangsan', age: 30});
         //4. 将绑定数据后的模板(html标签)显示给相应的标签
         document.getElementById('container').innerHTML = html;
     </script>
     </body>
     </html>
    
  7. 案例:搜索框输入展示历史记录

     <script src="/js/ajax.js"></script>
     <script src="/js/template-web.js"></script>
     <!-- 模板-->
     <script type="text/html" id="tpl">
         //循环语法
         {*each result}}
             <li class="list-group-item">{*$value}}</li>
         {*/each}}
     </script>
     <script>
         // 获取搜索框
         var searchInp = document.getElementById('search');
         // 获取提示文字的存放容器
         var listBox = document.getElementById('list-box');
         // 存储定时器的变量
         var timer = null;
         // 当用户在文本框中输入的时候触发
         searchInp.oninput = function () {
             // 清除上一次开启的定时器
             clearTimeout(timer);
             // 获取用户输入的内容
             var key = this.value;
             // 如果用户没有在搜索框中输入内容
             if (key.trim().length == 0) {
                 // 将提示下拉框隐藏掉
                 listBox.style.display = 'none';
                 // 阻止程序向下执行
                 return;
             }
                
             // 开启定时器 让请求延迟发送
             timer = setTimeout(function () {
                 // 向服务器端发送请求
                 // 向服务器端索取和用户输入关键字相关的内容
                 ajax({
                     type: 'get',
                     url: 'http://localhost:3000/searchAutoPrompt',
                     data: {
                         key: key
                     },
                     success: function (result) {
                         // 使用模板引擎拼接字符串
                         var html = template('tpl', {result: result});
                         // 将拼接好的字符串显示在页面中
                         listBox.innerHTML = html;
                         // 显示ul容器
                         listBox.style.display = 'block';
                     }
                 })
             }, 800);
         }
     </script>
    

FormData

  1. FormData 对象的作用
    1. 模拟HTML表单,相当于将HTML表单映射成表单对象,自动将表单对象中的数据拼接成请求参数的格式。
    2. 异步上传二进制文件
  2. FormData 对象的使用
    1. 准备 HTML 表单

       <form id="form">
           <input type="text" name="username" />
           <input type="password" name="password" />
           <input type="button"/>
       </form>
      
    2. 将 HTML 表单转化为 formData 对象

       var form = document.getElementById('form');
       var formData = new FormData(form);
      
    3. 提交表单对象

       // 创建ajax对象
       var xhr = new XMLHttpRequest();
       // 对ajax对象进行配置
       xhr.open('post', 'http://localhost:3000/formData');
       // 发送ajax请求
       xhr.send(formData);
       // 监听xhr对象下面的onload事件
       xhr.onload = function () {
           // 对象http状态码进行判断
           if (xhr.status == 200) {
               console.log(xhr.responseText);
           }
       }
      
    4. 注意:

      1. Formdata 对象不能用于 get 请求,因为对象需要被传递到 send 方法中,而 get 请求方式的请求参数只能放在请 求地址的后面。
      2. 服务器端 bodyParser 模块不能解析 formData 对象表单数据,我们需要使用 formidable 模块进行解析。
  3. FormData 对象的实例方法

     //1. 获取表单对象中属性的值
     formData.get('key');
     //2. 设置表单对象中属性的值
     formData.set('key', 'value');
     //3. 删除表单对象中属性的值
     formData.delete('key');
     //4. 向表单对象中追加属性值
     formData.append('key', 'value');
    
    1. 注意: set 方法与 append 方法的区别是,在属性名已存在的情况下,set 会覆盖已有键名的值,append会保留 两个值。
  4. FormData 二进制文件上传

     <input type="file" id="file"/>
     var file = document.getElementById('file')
     // 当用户选择文件的时候 
     file.onchange = function () {
         // 创建空表单对象 
         var formData = new FormData();
         // 将用户选择的二进制文件追加到表单对象中 
         formData.append('attrName', this.files[0]); 
         // 配置ajax对象,请求方式必须为post 
         xhr.open('post', 'www.example.com'); 
         xhr.send(formData);
     }
    
  5. FormData 文件上传进度展示

     // 当用户选择文件的时候 
     file.onchange = function () {
         // 文件上传过程中持续触发onprogress事件 
         xhr.upload.onprogress = function (ev) {
             // 当前上传文件大小/文件总大小 再将结果转换为百分数 
             // 将结果赋值给进度条的宽度属性 
             bar.style.width = (ev.loaded / ev.total) * 100 + '%';
         }
     }
    
  6. FormData 文件上传图片即时预览
    1. 在我们将图片上传到服务器端以后,服务器端通常都会将图片地址做为响应数据传递到客户端,客户端可以从响应数 据中获取图片地址,然后将图片再显示在页面中。

       xhr.onload = function () {
           var result = JSON.parse(xhr.responseText); 
           var img = document.createElement('img'); 
           //图片地址result.src
           img.src = result.src; 
           //img.onload图片加载完成
           img.onload = function () { 
               document.body.appendChild(this); 
           }
       }
      

同源政策

更多详见当前本地目录下【离线资料】《详解:浏览器的同源策略和跨域请求》文档

  1. Ajax请求限制
    1. Ajax 只能向自己的服务器发送请求。比如现在有一个A网站、有一个B网站,A网站中的 HTML 文件只能向A网站服务器中发送Ajax请求,B网站中的HTML文件只能向B网站中发送 Ajax 请求,但是A网站是不能向B网站发送Ajax请求的,同理,B 网站也不能向A网站发送 Ajax请求。
  2. 什么是同源
    1. 如果两个页面拥有相同的协议、域名和端口,那么这两个页面就属于同一个源,其中只要有一个不相同,就是不同源。

       http://www.example.com/dir/page.html
       http://www.example.com/dir2/other.html:同源 
       http://example.com/dir/other.html:不同源(域名不同) 
       http://v2.www.example.com/dir/other.html:不同源(域名不同) 
       http://www.example.com:81/dir/other.html:不同源(端口不同) 
       https://www.example.com/dir/page.html:不同源(协议不同)
      
  3. 同源政策的目的
    1. 同源政策是为了保证用户信息的安全,防止恶意的网站窃取数据。最初的同源政策是指 A 网站在客户端设置的 Cookie,B网站是不能访问的
    2. 随着互联网的发展,同源政策也越来越严格;目前,如果非同源,共有三种行为受到限制。
      1. Cookie、LocalStorage 和 IndexDB 无法读取
      2. DOM 无法获得。
      3. AJAX 请求不能发送。

使用JSONP解决同源限制问题-方案一

  1. jsonp 是 json with padding 的缩写,它不属于 Ajax 请求,但它可以模拟 Ajax 请求。
    1. 将不同源的服务器端请求地址写在 script 标签的 src 属性中

       //浏览器加载完会返回一个函数调用,然后执行对应的函数
       <script src="www.example.com"></script>
       <script src=“https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
      
    2. 服务器端响应数据必须是一个函数的调用,真正要发送给客户端的数据需要作为函数调用的参数。

       //服务端代码
       const data = 'fn({name: "张三", age: "20"})'; 
       res.send(data);
      
    3. 在客户端全局作用域下定义函数 fn,并且这个函数要定义在scipt标签前面

       function fn (data) { }
      
    4. 在fn函数内部对服务器端返回的数据进行处理

       function fn (data) { console.log(data); }
      
  2. 代码举例:

     <script>
         function fn (data) {
             console.log('客户端的fn函数被调用了')
             console.log(data);
         }
     </script>
     <!-- 1.将非同源服务器端的请求地址写在script标签的src属性中 -->
     <!--客户端一旦加载完成,就会调用fn函数-->
     <script src="http://localhost:3001/test">
     </script>
    
  3. JSONP 代码优化
    1. 客户端需要将函数名称传递到服务器端。
    2. 将 script 请求的发送变成动态请求。
    3. 封装 jsonp 函数,方便请求发送。
    4. 服务器端代码优化之 res.jsonp 方法。
    5. 代码举例:

       <button id="btn">点我发送请求</button>
       <script>
       function fn2 (data) {
           console.log('客户端的fn函数被调用了')
           console.log(data);
       }
       </script>
       <script type="text/javascript">
           // 获取按钮
           var btn = document.getElementById('btn');
           // 为按钮添加点击事件
           btn.onclick = function () {
               //1.动态创建添加script标签
               // 创建script标签
               var script = document.createElement('script');
               //2.将函数名称传递到服务器端
               // 设置src属性
               script.src = 'http://localhost:3001/better?callback=fn2';
               //3.将script标签追加到页面中
               document.body.appendChild(script);
               // 为script标签添加onload事件
               script.onload = function () {
                   //4. 请求完毕,删除script标签
                   // 将body中的script标签删除掉,防止多次创建script标签
                   document.body.removeChild(script);
               }
           }
       </script>
      
  4. jsonp函数封装(重点!!!

     <button id="btn1">点我发送请求</button>
     <script type="text/javascript">
         // 获取按钮
         var btn1 = document.getElementById('btn1');
         // 为按钮添加点击事件
         btn1.onclick = function () {
             jsonp({
                 // 请求地址
                 url: 'http://localhost:3001/better',
                 data: {
                     name: 'lisi',
                     age: 30
                 },
                 success: function (data) {
                     console.log(123)
                     console.log(data)
                 }
             })
         }
        	
         function jsonp (options) {
             // 动态创建script标签
             var script = document.createElement('script');
             // 拼接字符串的变量
             var params = '';
             for (var attr in options.data) {
                 params += '&' + attr + '=' + options.data[attr];
             }
                
             // myJsonp0124741
             //函数名不能一样,因为多个请求是异步的,如果函数名一样可能多个请求同一时刻调用同一个函数,就会出现问题
             var fnName = 'myJsonp' + Math.random().toString().replace('.', '');
             // 它已经不是一个全局函数了
             // 我们要想办法将它变成全局函数
             //将函数挂载到window对象下面,给window添加一个属性fnName,把这个函数赋值给这个属性
             window[fnName] = options.success;
             // 为script标签添加src属性
             script.src = options.url + '?callback=' + fnName + params;
             // 将script标签追加到页面中
             document.body.appendChild(script);
             // 为script标签添加onload事件
             script.onload = function () {
                 document.body.removeChild(script);
             }
         }
     </script>
    
  5. 获取腾讯天气数据举例

     <head>
         <meta charset="UTF-8">
         <title>使用jsonp获取腾讯天气信息</title>
         <link rel="stylesheet" href="/assets/bootstrap/dist/css/bootstrap.min.css">
         <style type="text/css">
             .container {
                 padding-top: 60px;
             }
         </style>
     </head>
     <body>
     <div class="container">
         <table class="table table-striped table-hover" align="center" id="box"></table>
     </div>
     <script src="/js/jsonp.js"></script>
     <script src="/js/template-web.js"></script>
     <script type="text/html" id="tpl">
         <tr>
             <th>时间</th>
             <th>温度</th>
             <th>天气</th>
             <th>风向</th>
             <th>风力</th>
         </tr>
         {*each info}}
         <tr>
             <td>{*dateFormat($value.update_time)}}</td>
             <td>{*$value.degree}}</td>
             <td>{*$value.weather}}</td>
             <td>{*$value.wind_direction}}</td>
             <td>{*$value.wind_power}}</td>
         </tr>
         {*/each}}
     </script>
     <script>
         // 获取table标签
         var box = document.getElementById('box');
         function dateFormat(date) {
             var year = date.substr(0, 4);
             var month = date.substr(4, 2);
             var day = date.substr(6, 2);
             var hour = date.substr(8, 2);
             var minute = date.substr(10, 2);
             var seconds = date.substr(12, 2);
             return year + '年' + month + '月' + day + '日' + hour + '时' + minute + '分' + seconds + '秒';
         }
         // 向模板中开放外部变量
         template.defaults.imports.dateFormat = dateFormat;
         // 向服务器端获取天气信息
         jsonp({
             url: 'https://wis.qq.com/weather/common',
             data: {
                 source: 'pc',
                 weather_type: 'forecast_1h',
                 // weather_type: 'forecast_1h|forecast_24h',
                 province: '黑龙江省',
                 city: '哈尔滨市'
             },
             success: function (data) {
                 var html = template('tpl', {info: data.data.forecast_1h});
                 box.innerHTML = html;
             }
         })
     </script>
     </body>
    

使用CORS解决同源限制问题-方案二

  1. CORS 跨域资源共享
    1. CORS:全称为 Cross-origin resource sharing,即跨域资源共享,它允许浏览器向跨域服务器发送 Ajax 请求,克服 了 Ajax 只能同源使用的限制。
  2. CORS与jsonp区别
    1. jsonp是绕过同源限制,发送的不是ajax请求
    2. CORS直接允许浏览器向服务器发送Ajax请求
  3. CORS实现
    1. 客户端的Ajax代码不变
    2. 服务器做一些配置
  4. 跨域访问流程
    1. 客户端向服务端发送请求,浏览器检测到请求是跨域的
    2. 自动在请求头中加上origin字段,字段值就是当前发送请求的域信息(当前网站的页面地址)
    3. 服务端根据origin字段的值来决定是否同意本次请求
    4. 不管服务端同不同意,服务端会给客户端一个正常的响应
      1. 如果服务端同意这次请求,会在响应头中添加Access-Control-Allow-Origin:字段
      2. 反之,不会在响应头中添加这个字段
    5. 浏览器会根据服务器返回的响应头判断是否有Access-Control-Allow-Origin:字段,来判断服务器是否同意本次请求,这个判断是浏览器自动完成不需要开发人员判断
    6. Access-Control-Allow-Origin:字段
      1. 这个字段就相当于服务端的白名单
      2. 这个值通常是客户端访问服务端的源信息,或者是*(代表所有的客户端访问这个服务端)

      pic

    7. Node 服务器端设置响应头示例代码:

       app.use((req, res, next) => { 
           res.header('Access-Control-Allow-Origin', '*'); 
           res.header('Access-Control-Allow-Methods', 'GET, POST'); 
           next(); 
       })
      
  5. 注意: 后台服务器把这个Access-Control-Allow-Origin属性由*设置为当前服务自己的域名。会影响:(自己的web端跟服务端部署在同一台服务器上,是同一个域名)别人的web页面中通过ajax请求(get、post)直接访问我们的服务器,其他无影响

非同源数据请求方案三

  1. 本方案也是绕过浏览器的同源政策
  2. 同源政策是浏览器(也就是说只有浏览器才会有同源政策)给予Ajax技术的限制,服务器端是不存在同源政策限制。
  3. 服务器开发语言对访问非同源数据是不存在限制的,所以对于客户端来说,当访问非同源数据时,让自己的网站服务端去获取非同源网站数据,然后自己的服务端再返回给客户端 pic

实战经验

案例一

  1. 使用场景:A是一个独立系统,里面所有的页面都需要进行授权,即不能直接单独打开,需要后台验证,否则不符合安全规范。
  2. 解决方案:
    1. A系统登录时将token写入到cookie,后序所有前端的所有后台接口访问都会自动携带cookie到后端
    2. 后端的过滤器过滤每一个请求获取携带的cookie,校验token,合法则返回,反之错误响应,或者重定向到登录页面。

案例二

  1. 使用场景:A是一个基础的大型系统,里面嵌入了很多其他子系统菜单,比如B、C、D、E,那么子系统必须保证页面都是授权的,即不能直接单独打开,需要后台验证,否则不符合安全规范。
  2. 存在问题:
    1. A系统登录的时候将cookie写入到本地cookie,
    2. 其他子系统因为跨域问题,无法自动携带A的cookie到,那应该怎么处理???
      1. 局部系统集成A系统的后台jar包,通过jar包的后台API主动写入到B系统的前端页面局部cookie,每个B系统就会携带A系统的cooki了。
    3. 那么其他系统只需要集成A系统封装的单点登录框架,然后后台进行校验即可。

cookie、withCredentials属性

  1. cookie是什么
    1. cookie就是用来实现服务端识别客户端身份的一种技术
    2. 当客户端第一次访问服务端时候,服务端检测这个客户端第一次访问,服务端在给客户端做出响应同时会给客户端发送一个身份证明(小卡片)
    3. 此时在客户端的浏览器中就有了这个身份证了,当客户端再一次访问服务器,这个身份证会随着请求发送到服务端,这样服务端就能够知道这个请求谁发送的了
    4. 浏览器发送请求携带cookie如果不跨域没问题,但是一旦跨域就cookie就不会随着请求被携带到服务端了,处于安全考虑
    5. 但是很多场合必须携带cookie,那么如何解决跨域携带cookie呢?
  2. withCredentials属性
    1. 在使用Ajax技术发送跨域请求时,默认情况下不会在请求中携带cookie信息。
    2. 解决步骤:
      1. 客户端: Ajax有个withCredentials:属性:指定在涉及到跨域请求时,是否携带cookie信息,默认值为false,要设置为true
      2. 服务端:同时服务端也要设置:Access-Control-Allow-Credentials:true ,允许客户端发送请求时携带cookie
  3. 代码举例:

     //index.html文件
     // 获取登录按钮
     var loginBtn = document.getElementById('loginBtn');
     // 获取登录表单
     var loginForm = document.getElementById('loginForm');
     // 为登录按钮添加点击事件
     loginBtn.onclick = function () {
         // 将html表单转换为formData表单对象
         var formData = new FormData(loginForm);
         // 创建ajax对象
         var xhr = new XMLHttpRequest();
         // 对ajax对象进行配置
         xhr.open('post', 'http://localhost:3001/login');
         // 当发送跨域请求时,携带cookie信息
         xhr.withCredentials = true;
         // 发送请求并传递请求参数
         xhr.send(formData);
         // 监听服务器端给予的响应内容
         xhr.onload = function () {
             console.log(xhr.responseText);
         }
     }
        
     //服务端代码 app.js文件
     // 拦截所有请求
     const express = require('express');
     const app = express();
     app.use((req, res, next) => { 
         // 1.允许哪些客户端访问我
         // * 代表允许所有的客户端访问我
         // 注意:如果跨域请求中涉及到cookie信息传递,值不可以为*号 必须是具体的域名信息
         res.header('Access-Control-Allow-Origin', 'http://localhost:3000')
         // 2.允许客户端使用哪些请求方法访问我
         res.header('Access-Control-Allow-Methods', 'get,post')
         // 允许客户端发送跨域请求时携带cookie信息
         res.header('Access-Control-Allow-Credentials', true);
         // 解决同源政策
         //res.header('Access-Control-Allow-Origin', '*'); 
         // res.header('Access-Control-Allow-Methods', 'GET, POST'); 
         next(); 
     })
     app.post('/login',(req,res)=>{
         // console.log(req.body);
          // 设置响应报文 参数:状态码,响应数据内容格式
         res.send('{"name":"张三","age":10}');
     });
     app.listen(3001);
     console.log('服务启动成功!');
    
    1. 先用nodemon app.js,启动服务,然后打开html文件点击发送请求

jQuery 中的 Ajax

  1. $.ajax()方法概述
    1. 方法1:
      1. 作用:发送Ajax请求。
       $.ajax({
           type: 'get',
           url: 'http://www.example.com',
           data: { name: 'zhangsan', age: '20' }, 
           contentType: 'application/x-www-form-urlencoded', 
           //请求发送之前做的事情
           beforeSend: function () {
               //是否继续请求
               return false
           },
           success: function (response) {}, 
           error: function (xhr) {}
       });
      
      1. 如果contentType: 'application/json'
      2. 则data为:JSON.stringify({name: 'zhangsan', age: '20'})
    2. 方法2:
      1. 作用:发送jsonp请求。

         $.ajax({
             url: 'http://www.example.com', 
             // 指定当前发送jsonp请求 
             dataType: 'jsonp', 
             // 修改callback参数名称 ,该参数可选设置
             jsonp: 'cb', 
             // 指定函数名称 ,该参数可选设置,如果设置,服务器响应就不会走success了,走fnName函数
             jsonCallback: 'fnName', 
             success: function (response) {}
         })
        
        1. jsonp字段
          1. 就是传递给服务器的函数字段
        2. jsonCallback字段
          1. 就是传递给服务器的函数名称
        3. 本质就是http://www.example.com?cb=fnName
        4. 一旦设置jsonp、jsonCallback,服务器响应就不会走success了,会走jsonCallback设置的自定义函数,这两个字段通常不设置
  2. serialize/serializeArray方法
    1. serialize作用:将表单中的数据自动拼接成字符串类型的参数

       var params = $('#form').serialize();
       // name=zhangsan&age=30
      
    2. serializeArray作用:将表单中的数据自动转化成一个对象数组

       var params = $('#form').serializeArray();
       /*
       [
         {name:"name",value:"zhangsan"},
         {name:"age",value:30},
       ]
       */
      
    3. 代码举例:(重要!将表单转化为json对象)

       <form id="form">
           <input type="text" name="username">
           <input type="password" name="password">
           <input type="submit" value="提交">
       </form>
       <script src="/js/jquery.min.js"></script>
       <script type="text/javascript">
           $('#form').on('submit', function () {
               // 将表单内容拼接成字符串类型的参数
               // var params = $('#form').serialize();
               // console.log(params)
               serializeObject($(this));
               return false;
           });
                  
           // 将表单中用户输入的内容转换为对象类型
           function serializeObject (obj) {
               // 处理结果对象
               var result = {};
               // [{name: 'username', value: '用户输入的内容'}, {name: 'password', value: '123456'}]
               //serializeArray将表单转化为对象数组
               var params = obj.serializeArray();
                      
               // 循环数组 将数组转换为对象类型
               $.each(params, function (index, value) {
                   result[value.name] = value.value;
               })
               // 将处理的结果返回到函数外部
               return result;
           }
       </script>
      
  3. $.get()、$.post()方法概述
    1. 作用:$.get方法用于发送get请求,$.post方法用于发送post请求。
     $.get('http://www.example.com', {name: 'zhangsan', age: 30}, function (response) {}) 
     $.post('http://www.example.com', {name: 'lisi', age: 22}, function (response) {})
    

jQuery中Ajax全局事件

  1. 只要页面中有Ajax请求被发送,对应的全局事件就会被触发
    1. 注意:这两个事件一定要绑定在document上面才有效
     // 当请求开始发送时触发
     .ajaxStart() 
     // 当请求完成时触发
     .ajaxComplete()
    
  2. NProgress插件
    1. 官宣:纳米级进度条,使用逼真的涓流动画来告诉用户正在发生的事情!

       <link rel='stylesheet' href='nprogress.css'/> 
       <script src='nprogress.js'></script>
       // 进度条开始运动
       NProgress.start();
       // 进度条结束运动
       NProgress.done();
      
  3. 代码举例:

     // 当页面中有ajax请求发送时触发
     $(document).on('ajaxStart', function () {
         NProgress.start() 
     })
            
     // 当页面中有ajax请求完成时触发
     $(document).on('ajaxComplete', function () {
         NProgress.done() 
     })
    

RESTful 风格的 API

  1. 传统请求地址回顾

     GET http://www.example.com/getUsers
     GET http://www.example.com/getUser?id=1
     POST http://www.example.com/modifyUser
     GET http://www.example.com/deleteUser?id=1
    
  2. RESTful API 概述

    1. 一套关于设计请求的规范。
      1. GET: 获取数据
      2. POST: 添加数据
      3. PUT: 更新数据
      4. DELETE: 删除数据
    2. RESTful API 的实现

       GET: http://www.example.com/users 获取用户列表数据
       POST: http://www.example.com/users 创建(添加)用户数据
       GET: http://www.example.com/users/1 获取用户ID为1的用户信息
       PUT: http://www.example.com/users/1 修改用户ID为1的用户信息
       DELETE: http://www.example.com/users/1 删除用户ID为1的用户信息
      

XML 基础

  1. XML是什么
    1. XML 的全称是 extensible markup language,代表可扩展标记语言,它的作用是传输和存储数据。
    2. 作用是用来传输、存储数据
    3. XML没有预定义标签,都是自定义标签

       <students> 
           <student> 
               <sid>001</sid> 
               <name>张三</name> 
           </student> 
           <student> 
               <sid>002</sid> 
               <name>王二丫</name> 
           </student> 
       </students>
      
  2. XML DOM
    1. XML DOM 即 XML 文档对象模型,是 w3c 组织定义的一套操作 XML 文档对象的API。浏览器会将 XML 文档解析 成文档对象模型。
  3. 代码举例:

     <button id="btn">发送请求</button>
     <div id="container"></div>
     <script type="text/javascript">
     var btn = document.getElementById('btn');
     var container = document.getElementById('container');
        
     btn.onclick = function () {
         var xhr = new XMLHttpRequest();
         xhr.open('get', '/xml');
         xhr.send();
         xhr.onload = function () {
             // xhr.responseXML 获取服务器端返回的xml数据
             var xmlDocument = xhr.responseXML;
             //XMLDOM操作,跟HTMLDOM操作类似
             var title = xmlDocument.getElementsByTagName('title')[0].innerHTML;
             container.innerHTML = title;
         }
     }
     </script>
    
Table of Contents