# JS-Web-API-Ajax

代码笔记 (opens new window)

# 1. 题目

  • 手写一个简易的ajax
    • jquery、fetch、axios等可以实现ajax
  • 跨域的常用实现方式

# 2. 知识点

# 2.1. 简述

  • XMLHttpRequest
    • 浏览器实现ajax最核心的api
  • 状态码
  • 跨域:同源策略、跨域解决方案
    • 浏览器要保证安全
    • 跨域实际上就是跨过同源策略

# 2.2. XMLHttpRequest

  • 代码示例
    // 初始化一个实例
    const xhr = new XMLHttpRequest()
    
    /**
     * get请求
      */
    // open方法最后一个true表示异步
    // open方法相当就是一个准备数据的方法
    // 如果请求时间长,用同步会卡顿
    // 不过一般是使用异步
    xhr.open("GET", "./json/api.json", true)
    // xhr.open("GET", "https://jsonplaceholder.typicode.com/posts", true)
    // 类似 img.onload = function(){}
    // 当XMLHttpRequest实例状态改变的时候触发这个函数
    // 在这个函数里面去接收数据
    xhr.onreadystatechange = function() {
      // 这里的函数异步执行
      // console.log(xhr);
    
      // 4:响应已完成;您可以获取并使用服务器的响应了
      if(xhr.readyState === 4) {
        // 状态码
        if(xhr.status === 200) {
          // console.log(xhr.responseText)
          let objData = JSON.parse(xhr.responseText)
          console.log("GET", objData);
        }
      }
    }
    
    // 因为是get请求,不需要发送任何数据,就写null
    // onreadystatechange这个钩子如果不去send,他是不会变的
    // 每send一次,xhr实例都会发生变化,所以异步回调里面要做判断
    xhr.send(null)
    
    /**
     * post请求
      */
    // 网络请求不能卡顿,所以用异步 - 如果请求时间长,用同步会卡顿
    xhr.open("POST", "https://jsonplaceholder.typicode.com/posts", true)
    // xhr.open("POST", "./json/login.json", true)
    xhr.onreadystatechange = function() {
      if(xhr.readyState === 4) {
        // 状态码
        if(xhr.status === 200 || xhr.status === 201) {
          // console.log(xhr.responseText)
    
          let objData = JSON.parse(xhr.responseText)
          console.log("POST", objData);
        }
      }
    }
    
    const postData = {
      username: "张三",
      password: "123456"
    }
    
    // 把它弄成字符串发送出去
    // 直接发送json是不对的,工具里面可以是因为它里面做了处理
    xhr.send(JSON.stringify(postData))
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61

# 2.3. 状态码

  • xhr.readyState => xhr的状态
    • 0 => (未初始化) 还没有调用send方法
    • 1 => (载入) 已调用send()方法,正在发送请求
    • 2 => (载入完成) send()方法执行完成,已经接收到全部响应内容
    • 3 => (交互) 正在解析响应内容
    • 4 => (完成) 响应内容解析完成,可以在客户端调用
  • xhr.status => http协议的状态码
    • 2xx => 表示成功处理请求,如200
    • 3xx => 需要重定向,浏览器直接跳转,如301、302、304
      • 重定向不需要我们自己处理,服务器返回浏览器自己会去跳页面
      • 301表示永久重定向
      • 302表示临时重定向
      • 304表示资源未改变
        • 资源没有改变,浏览器就会用自己缓存的资源
    • 4xx => 客户端请求错误,如404、403
      • 404表示请求的地址有错误
      • 403表示客户端没有权限
    • 5xx => 服务端错误

# 2.4. 跨域

# 2.4.1. 什么是跨域(同源策略)

  • ajax请求时,浏览器要求当前网页和server必须同源(安全)
    • ajax请求的服务和当前的网页的服务端地址要一样
    • 主要限制是浏览器,如果是爬虫从服务器去做网络请求,照样可以把数据拿到
    • 如果需要访问不同源的接口,server端是可以做到的,服务端没有像浏览器这样的同源策略限制
  • 同源的含义
    • 协议、域名、端口,三者必须一致
  • 加载图片css、js等可无视同源策略
    • 不是ajax请求
    • <img src=跨域的图片地址 />
      • 图片服务器如果做了防盗链(不是浏览器做的),就无法访问
    • 使用cdn地址实际上就是跨域
      • <link href=跨域的css地址 />
      • <script src=跨域的js地址><script>
    • <img />可用于统计打点,可使用第三方统计服务
      • 统计点击次数之类的
      • 通过图片去发送请求,就不会出现跨域的情况
    • <script>可实现JSONP
  • 所有的跨域,都必须经过server端允许和配合
  • 如果未经server端允许就实现跨域,说明浏览器有漏洞,危险信号

# 2.4.2. JSONP => 客户端解决跨域

  • JSONP跨域原理
    • 抛出问题
      • 访问一个网址,服务端一定返回一个html文件吗
        • 服务端可以任意动态拼接数据返回,只要符合html格式要求
        • 同理,我们通过script请求的js地址,也可以返回任意动态拼接的数据,只要符合js语法要求
        • html地址和js地址,实际上都是网址
    • JSONP的基本实现原理
      • <script>可绕过跨域限制
      • 这个<script>的地址,服务器可以任意动态拼接数据返回
      • 所以,<script>就可以获得跨域的数据,只要服务端愿意返回
      • 即server端可以动态拼接一些信息返回,这样就实现了jsonp的跨域
    • 代码示例
      <script>
        // JSONP
        window.abc = function(data) {
          // 这是我们跨域得到的信息
          console.log(data)
        }
      </script>
      <!-- 通过http-server另起一个服务 -->
      <!-- <script src="http://127.0.0.1:3333/jsonp.js?callback=abc"></script> -->
      <script src="https://suggest.taobao.com/sug?code=utf-8&q=卫衣&callback=abc"></script>
      
      <!-- jsonp.js -->
      <script>
        abc(
          { name: 'xxx' }
        )
      </script>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
  • jquery实现JSONP
    • 代码示例
      $.ajax({
        // ?code=utf-8&q=卫衣&callback=abc
        url: "https://suggest.taobao.com/sug?code=utf-8",
        type: 'GET',
        dataType:"jsonp",
        // jsonpCallback: 'cb',
        data: {
          q: "夹克"
        },
        success: function(data) {
          console.log(data);
        }
      })
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13

# 2.4.3. CROS => 服务端解决跨域

  • cros => 服务器端设置请求头允许跨域(http header)
    • 纯服务端操作
  • 代码示例
    // 第二个参数填写允许跨域的名称,不建议直接写 *
    // 允许的域名
    response.setHeader("Access-Control-Allow-Origin", "http://localhost:8011");
    // 允许的请求头
    response.setHeader("Access-Control-Allow-Headers", "X-Requested-Width");
    // 允许的请求方法
    response.setHeader("Access-Control-Allow-Methods", "PUT, POST, GET, DELETE, OPTIONS");
    
    // 接收跨域的cookie
    response.setHeader("Access-Control-Allow-Credentials", "true");
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    • 如果域名允许访问,就没必要搞jsonp了,直接做http请求就可以了

# 2.4.4. proxy => 通过代理解决跨域

  • vue.config.js
    module.exports = {
      devServer: {
        port: 6666,
    
        proxy: {
          '/api': {
            changeOrigin:true,//允许跨域
            //本地服务接口地址
            target: 'http://192.168.0.xxx:xxxx',
            ws: true,
            pathRewrite: {
              '^/api': '/'
            }
          },
          '/apitest': {
            changeOrigin:true,//允许跨域
            //本地服务接口地址
            target: 'http://192.168.0.xxx:xxxx',
            ws: true,
            pathRewrite: {
              '^/apitest': '/'
            }
          },
        }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
  • nginx
    server {
      listen       6080;
      server_name  localhost;
    
      location / {
        root   E:\workspace\_poj;
        index  index.html index.htm;
    
        # include       nginx_cors;
      }
    
      location ^~/api {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_buffering off;
        rewrite ^/api/(.*)$ /$1 break;
        proxy_pass http://192.168.0.xxx:xxxx/;
      }
    
      error_page   500 502 503 504  /50x.html;
      location = /50x.html {
        root   html;
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

# 2.5. 网络请求工具

  • jquery
    $.ajax({
      // 请求方式
      type: 'POST',
      // 请求的媒体类型
      contentType: 'application/json;chartset=UTF-8',
      // 请求地址
      url: 'http://xxxx',
      // 数据,json字符串
      data: JSON.stringify(list),
      // 请求成功
      success: function(res) {
        console.log(res)
      },
      // 请求失败,包含具体的错误信息
      error: function(e) {
        console.log(e.status)
        console.log(e.responseText)
      }
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
  • fetch
    • 它是一个新的api,浏览器兼容性并没有百分百兼容
    • fetch相比于XMLHttpRequest更简洁一些
    // 示例一
    fetch('http://xxxx').then(function(res) {
      retutn res.json()
    }).then(function(myJson) {
      console.log(myJson);
    })
    
    // 示例二
    fetch(url, {
      body: JSON.stringify(data),
      cache: 'no-cache',
      // 要发送cookie,必须使用credentials
      // 默认情况下,fetch不会从服务端发送或接收任何cookie
      credentials: 'same-origin',
      headers: {
        'content-type': 'application/json'
      }
      method: 'POST',
      // 跨域
      mode: 'cros',
    }).then(response => response.json())
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    • 从fetch()返回的Promise不会被标记为reject,即使该HTTP响应的状态码是404或500,相反,他会将Promise标记为resolved(但是会resolved返回值得ok属性设置为false),仅当网络故障或请求被阻止时,才会标记为reject
  • axios
    • 现在应用比较多,因为它即支持浏览器,又支持nodejs
    • 它在浏览器使用的api就是XMLHttpRequest
      • 即他是对XMLHttpRequest这个api的封装
    • axios支持promise的api
    // 示例一
    axios.get(url).then(res=> {
      console.log(res)
    }).catch(err=> {
      console.log(err);
    }).then(()=> {
      console.log("always")
    })
    
    // 示例二:
    axios.post(url, {data: "xxx"}).then(res=> {
      console.log(res)
    }).catch(err=> {
      console.log(err);
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

# 3. 面试题解答(总结)

  • 手写简易的ajax
    • 手写demo传送门 (opens new window)
    • 示例一
      let ajax = (type, url, successFn)=> {
        const xhr = new XMLHttpRequest()
        xhr.open(type, url, true)
        xhr.onreadystatechange = function() {
          if(xhr.readyState === 4) {
            if(xhr.status === 200) {
              successFn(JSON.parse(xhr.responseText))
            }
          }
        }
      
        xhr.send(null)
      }
      
      ajax('GET', "https://jsonplaceholder.typicode.com/users", data=> {
        console.log("简易写法", data);
      })
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
    • 示例二
      • promise
      // 示例二: promise
      let ajaxPro = (type, url)=> {
        const p = new Promise((resolve, reject)=> {
          const xhr = new XMLHttpRequest()
          xhr.open(type, url, true)
          xhr.onreadystatechange = function() {
            if(xhr.readyState === 4) {
              if(xhr.status === 200) {
                resolve(JSON.parse(xhr.responseText))
              } else if(xhr.status === 404) {
                reject(new Error('404 not find'));
              }
            }
          }
      
          xhr.send(null)
        })
      
        return p
      }
      
      ajaxPro("GET", "https://jsonplaceholder.typicode.com/posts").then(res=> {
          console.log("promise写法", res);
      }).catch(err=> {
          console.error("promise写法报错", err);
      })
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
  • 跨域的常用实现方式
    • JSONP
      • <script>可绕过跨域限制
      • server端可以动态拼接一些信息返回
    • CROS
      • 纯服务端
    • proxy


~End~