# Ajax网络编程

# 1. 背景知识

# 1.1 静态网页的局限性

使用以前的知识,浏览器提交数据给服务端,只能使用表单的形式提交,表单提交会造成页面刷新,影响使用体验。例如下面的登录案例,输入用户名和密码点击登录,表单提交数据给服务端,服务端返回的信息会在新页面显示,用户如果输入错误的密码需要返回重新填写。再看到使用Ajax技术实现的登录,错误信息直接显示在页面上,用户根据错误信息直接修改即可。

这就是Ajax技术需要解决的问题,解决浏览器提交/获取服务端数据刷新问题

使用表单进行登录:

login

使用Ajax实现登录:

login_ajax

# 1.2 无处不见的Ajax

除了登录功能使用Ajax外,其实我们浏览的网站Ajax几乎是无处不见的,只要页面中存在动态变更的数据,那基本都是使用了Ajax技术获取的。

例如掘金网站的文章的“点赞”,“评论”,“翻页”功能。

image-20200706143820590

# 1.3 使用chrome调试工具分析Ajax请求

打开chrome调试工具,切换到network模块,就可以看到客户端与服务端通过网络请求和接受数据的详细信息,点击左侧请求地址,就可以查看HTTP请求信息。

image-20200706145855203

# 1.4 登录功能网络请求流程分析

浏览器输入http://localhost:3000/login_ajax.html 浏览器请求服务端获取login_ajax.html页面,客户端收到html内容,浏览器解析页面并渲染到浏览器上。

当输入用户名和密码点击登录,发送ajax请求服务端收到用户名密码,判断是否正确,返回“登录成功”文字。

image-20200706151422984

# 2. Ajax概念与实现

# 2.1 概念

Ajax 即“Asynchronous Javascript And XML”(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术。

通俗说法:ajax就是一套可以让网站跟服务器交互的一种技术,能在我们需要时,不用再刷新网页就能去服务器要一些数据回来

# 2.1.1 异步与同步(重点)

先看一段代码,下面代码输出顺序为1 2 3,因为我们使用了setTimeout方法,把里面的代码延迟1秒执行,所以数字3最后输出。我们把这种非顺序执行的代码称为异步代码。

setTimeout(function() {
  console.log(3);
}, 1000)
console.log(1);
console.log(2);
// 运行结果 1 2 3

另外,异步代码没法直接返回数据,只能通过回调函数获取

function sum(a,b) {
	return a + b;
}
const res = sum(1,2); // res为3

// res1为undefined
const res1 = setTimeout(function() {
  sum(1,2)
}, 1000);

// 通过回调函数获取
let res2;
setTimeout(function() {
  res2 = sum(1,2);
  console.log(res2) // 输出3
}, 1000);
console.log(res2) // 输出undefined,因为sum函数还没执行

同步:在处理一件事情时,从头做到尾,中间没有被中断

异步:在处理一件事,可以做另外一件事,再回来处理原来的事。

# 2.2 尝试发送Ajax

# 2.2.1 XHR(XMLHttpRequest)对象

XMLHttpRequest(XHR)对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。XMLHttpRequest 在 AJAX 编程中被大量使用。

发送一个get请求:

// 创建XMLHttpRequest实例
const xhr = new XMLHttpRequest();
// 调用open方法,第一个参数设置请求方式get/post;第二个参数设置请求地址
xhr.open('get', '/userLogin?username=admin&password=123456')
// 调用send方法发送请求
xhr.send();

# 2.2.2 监听返回数据

// 创建XMLHttpRequest实例
const xhr = new XMLHttpRequest();
// 调用open方法,第一个参数设置请求方式get/post;第二个参数设置请求地址
xhr.open('get', '/userLogin?username=admin&password=123456')
//-------------接收数据返回----------------------
xhr.onload = function() {
    // xhr.responseText表示服务端返回的数据
    document.querySelector('.error').innerHTML = xhr.responseText;
}
//-------------接收数据返回----------------------
// 调用send方法发送请求
xhr.send();

# 2.2.3 发送Ajax请求步骤

步骤:

  1. 创建异步对象,XMLHTTPRequest实例(浏览器的内置对象)

    const xhr = new XMLHttpRequest();
    
  2. 设置请求方式和请求地址

    xhr.open('get', '/userLogin?username=admin&password=123456')
    
  3. 监听异步对象的状态变更

    xhr.onload = function() {
        // xhr.responseText表示服务端返回的数据
        document.querySelector('.error').innerHTML = xhr.responseText;
    }
    
  4. 发送请求,根据需求传参

    xhr.send();
    

# 2.3 实现注册功能

# 2.3.1 验证用户名是否可用

请求地址:/userLogin

请求方式:get

function validate() {
  const userName = document.querySelector('#userName').value;
  const tips = document.querySelector('#tips');
  const xhr = new XMLHttpRequest();
  xhr.open('get', 'http://127.0.0.1:3001/validate?userName='+userName);
  xhr.onload = function() {
      const res = JSON.parse(xhr.responseText);
      if (res.code === 200) {
        tips.innerHTML = res.msg;
      } else {
        tips.innerHTML = res.msg;
      }
  }
  xhr.send();
}

# 2.3.2 注册功能

function register() {
  const userName = document.querySelector('#userName').value;
  const userPwd = document.querySelector('#userPwd').value;
  const tips = document.querySelector('#tips');
  const xhr = new XMLHttpRequest();
  xhr.open('post', 'http://127.0.0.1:3001/register');
  xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
  xhr.onload = function() {
    const res = JSON.parse(xhr.responseText);
    if (res.code === 200) {
      tips.innerHTML = res.msg;
      tips.style.color = 'green';
    } else {
      tips.innerHTML = res.msg;
    }
  }
  xhr.send(`userName=${userName}&userPwd=${userPwd}`);
}

POST和GET请求区别:

  1. 需要设置请求头。xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
  2. 通过send设置请求参数。xhr.send(userName=${userName}&userPwd=${userPwd});

# 3. JSON数据格式

JSON(JavaScript Object Notation)是一种由道格拉斯·克罗克福特构想和设计、轻量级的数据交换语言,该语言以易于让人阅读的文字为基础,用来传输由属性值或者序列性的值组成的数据对象。

简单来说,JSON就是一种书写规范,目的是方便客户端与服务端之间交流。

# 3.1 JSON格式规范(了解,不要记忆)

  1. JSON 中对象的属性名称必须用双引号包裹
  2. JSON 中不能写注释
  3. JSON中数据由逗号分隔(最后一个健/值对不能带逗号)
  4. JSON 没有 undefined 这个值

下面是package.json文件,遵循JSON格式规范

{
  "name": "docs",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "docs:dev": "vuepress dev docs",
    "docs:build": "vuepress build docs"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "vuepress": "^1.5.0"
  }
}

# 3.2 JSON字符串和对象间转换

由于客户端和服务端传输数据只能是字符串或者二进制(图片、视频),而我们编写代码时使用的是JS对象,所以需要把JS对象转换为JSON字符串

// JS对象转JSON字符串
const user = {name: 'tom', age: 18};
const jUser = JSON.stringify(user);
console.log(jUser); // 输出 {"name":"tom","age":18}

服务单返回JSON字符串到浏览器后,由于需要方便读取JSON中数据,需要将JSON字符串转换为JS对象。

const userStr = '{"name":"tom","age":18}';
const user = JSON.parse(userStr);
console.log(user); // 输出 {name: "tom", age: 18}
console.log(user.name) // 输出 tom

# 3.3 Ajax请求JSON数据

  // 1. 创建异步对象,XMLHTTPRequest实例(浏览器的内置对象)
  const xhr = new XMLHttpRequest();
  // 2. 使用open方法初始化请求对象,声明请求方式和请求地址
  xhr.open('get', 'http://127.0.0.1:3001/getHero?heroName=东皇太一')
  // 3. 监听异步对象的状态变更
  xhr.onload = function() {
      // 5. 成功获取数据,处理渲染数据
      const res = xhr.responseText;
      const jRes = JSON.parse(res); // ⚠️通过JSON.parse将JSON字符串转换为JS对象方便操作
  }
  // 4. 发送请求,根据需求传参
  xhr.send();

# 4. XML数据格式(了解)

XML是一种标记语言,很类似HTML,其宗旨是用来传输数据,具有自我描述性(固定的格式的数据)。

# 4.1 XML格式规范

  1. xml的数据一定要设置头部 <?xml version="1.0" encoding="utf-8"?>
  2. xml中的标识,我们可以自己随便定义,必须有开始,就得有结束
  3. xml中只能有一对根标签
<?xml version="1.0" encoding="utf-8" ?>
<students>
  <student>
    <name>tom</name>
    <age>18</age>
    <gender></gender>
  </student>
  <student>
    <name>tom</name>
    <age>18</age>
    <gender></gender>
  </student>
</students>

JSON对应格式

{
  "students": [
    { "name": "tom", "age": 18, "gender": "男" },
    { "name": "tom", "age": 18, "gender": "男" }
  ]
}

# 4.2 Ajax读取XML数据

  1. 读取服务端返回xml内容在responseXML里
  2. 通过getElementByTagName读取XML节点数据
// 实例化XHR对象
const xhr = new XMLHttpRequest();
xhr.open('get', 'http://127.0.0.1:3001/getStudentsXML');
xhr.onload = function () {
  // 注意,xml数据存放在responseXML中
  const students = xhr.responseXML;
  // 通过getElementByTagName获取xml节点数据
  const id = students.getElementsByTagName('id');
  console.log(id[0].innerHTML)
}
xhr.send();

# 5. 封装Ajax工具函数

封装函数的目的是为了消除重复代码,提升代码的复用率。下面我们封装一个简单的ajax方法帮助我们发送请求。

下面先对比get和post请求,找出相同的代码和不同的代码

// 发送一个get请求的代码
const xhr = new XMLHttpRequest();
xhr.open('get', 'http://127.0.0.1:3001/validate?userName='+userName') 
xhr.onload = function() {
  const res = xhr.responseText;
  const jRes = JSON.parse(res);
  console.log(jRes)
}
xhr.send();

// ------------------------------------------------------------------------------

// 发送post请求代码
const xhr = new XMLHttpRequest();
xhr.open('post', 'http://127.0.0.1:3001/userLogin');
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
xhr.onload = function() {
  const res = xhr.responseText;
  const jRes = JSON.parse(res);
  console.log(jRes)
}
xhr.send('username=admin&password=123456');

分析不同的代码,通过参数形式传入:

1、xhr.open时有可能是get/post,我们把这个参数叫method

2、xhr.open时,get请求参数直接拼接在后面而post请求通过send方法发送数据

3、post请求需要设置请求头

function ajax(method, url, data, success) {
  const xhr = new XMLHttpRequest();
  // 3、get请求参数拼接在URL后
  if (method === 'get') {
    url = `${url}?${data}`
  }
  xhr.open(method, url);
  // 2、post请求需要设置请求头
  if (method === 'post') {
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  }
  xhr.onload = function() {
    // 通过传递函数,实现不同的逻辑处理
    const data = JSON.parse(xhr.responseText)
    success(data);
  }
  // 4、post请求需要通过send方法传递
  if (method === 'get') {
    xhr.send();
  } else {
    xhr.send(data);
  }
}

# 5.1 优化参数传递

刚才封装的ajax函数,在传递参数时需要传递的是字符串,例如username=admin&password=123456,当参数较多的时候手动拼接较麻烦,我们可以封装一个对象转字符串的方法帮助我们转换。

// 转换为name=tom&age=18&gender=男
    function obj2str(obj) {
      var result = ''
      // 循环读取对象的key值和value值
      for(var key in obj) {
        // 获取value值
        const value = obj[key];
        // 拼接字符串
        var str = `${key}=${value}&`
        result += str;
      }
      // 去除最后的字符
      result = result.substr(0, result.length - 1)
      return result;
    }

# 6. 使用jQuery发送ajax

jQueryt提供发送GET请求方法

jQuery.get(url, [data], [callback], [type])

jQueryt提供发送POST请求方法

$.post(url, [data], [callback], [dataType])

参数说明:

url: 请求URL地址

data: 发送服务端参数。

callback: 成功时回调函数。

type: 指定内容格式,如果不传递,系统会自动转换

# 6.1 对比XHR发送代码

下面是实现请求验证用户名是否使用接口的原生版本和jquery版本

// 使用xhr对象发送请求
const xhr = new XMLHttpRequest();
xhr.open('get', 'http://127.0.0.1:3001/validate?userName='+userName') 
xhr.onload = function() {
  const res = xhr.responseText;
  const jRes = JSON.parse(res);
  console.log(jRes)
}
xhr.send();

// ------------------------------------------------------------------------------------

// 使用jquery发送
$.get('http://127.0.0.1:3001/validate', { userName }, function(data) {
  console.log(jRes)
})

# 6.2 发送前拦截请求beforeSend

请求发送前的回调函数,用来修改请求发送前jqXHR对象,此功能用来设置自定义 HTTP 头信息,等等。该jqXHR和设置对象作为参数传递。这是一个Ajax事件 。在beforeSend函数中返回false将取消这个请求。

const user = $('#userName').val();
$.ajax({
  type: 'post',
  url: 'http://127.0.0.1:3001/validate',
  data: {
    userName: user
  },
  success: function(res) {
    $('#tips').html(res.msg);
  },
  beforeSend: function() {
    // 判断用户名是否为空
    if(user === '') {
      return false;
    } else {
      return true;
    }
  },
})

# 6.3 设置请求超时

当用户网路信号不佳,会出现一个网络请求长时间等待,这种情况下可以利用网络超时参数,等待超过设置时间后,提醒用户重新请求。

当请求超出时间后,可以在error回调函数提醒用户。

$.ajax({
  type: 'post',
  url: 'http://127.0.0.1:3001/validate',
  data: {
    userName: user
  },
  success: function(res) {
    $('#tips').html(res.msg);
  },
  // 设置请求超时为2秒
  timeout: 2000,
  error: function(jqxhr, textStatus) {
    if(textStatus === 'timeout') {
      alert('你的网络不太好,等会再试试')
    }
  }
})

# 6.4 表单序列化

在实现注册功能时,获取用户名和密码需要一个个获取,当表单字段较多时会很麻烦,jquery提供名为serialize方法自动获取表单数据。

使用该方法前需要确保你获取的是一个form表单,而且表单内input字段具有name属性

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>表单序列化</title>
    <script src="../assets/js/jquery-1.12.2.js"></script>
    <script>
        // 目标:理解如何使用serialize方法和其注意事项
        function register() {
            // 太麻烦
            // const userName = $('#userName').val();
            // const userPwd = $('#userPwd').val()
            const form = $('form').serialize();
            console.log(form) // userName=tom&userPwd=123
            $.post('http://127.0.0.1:3001/register', form, function(res) {
                console.log(res)
            })
        }
    </script>
</head>
<body>
    <!-- 注意点一:必须是一个表单 -->
    <form action="">
        <!-- 注意点二:必须有name属性 -->
        用户名:<input type="text" name="userName" id="userName">
        密码:<input type="password" name="userPwd" id="userPwd">
        <button type="button" onclick="register()">注册</button>
    </form>
</body>
</html>

# 7. 模版引擎

我们往往在ajax请求数据后需要把数据显示在页面上,例如请求学生列表数据,页面上需以表格形式显示学生信息,如果使用现有知识需使用循环和字符串拼接来实现。

看下面的案例,为了以表格的方式显示学生的数据,需要复杂的拼接,容易错误。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="../assets/js/jquery-1.12.2.js"></script>
</head>
<body>
  <button onclick="getStudents()">获取学生列表</button>
  <div id="student"></div>
</body>
<script>
  function getStudents() {
    $.post('http://127.0.0.1:3001/getStudentsJSON', (res) => {
      const { data } = res;
      let table = '';
      data.forEach(item => {
        table += `<tr><th>${item.name}</th><th>${item.age}</th></tr>`
      })
      document.querySelector('#student').innerHTML = `<table border="1"><tr><th>姓名</th><th>年龄</th></tr>${table}</table>`
    })
  }
</script>
</html>

# 7.1 art-template使用

art-template模版引擎就像jQuery一样,是一个工具库,解决字符串+数据渲染问题,虽然有ES6的模版字符串,但是如果需要实现循环输出,逻辑判断,ES6的模版字符串就显得单薄。

使用步骤:

  1. 页面引入template-web.js

  2. 定义模版,注意type为text/html 还有有唯一id。

    <script type="text/html" id="user">
      我的名字叫{{name}}, 今年{{age}}, template
    </script>	
    
  3. 通过template方法传入模版id和数据

    const html = template('user', user)
    
  4. 完整代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>art-template输出</title>
        <!-- 引入art-template资源 -->
        <script src="../assets/js/template-web.js"></script>
        <script type="text/html" id="user">
            我的名字叫{{name}},年龄{{age}}</script>
    </head>
    <body>
        <div id="result"></div>
        <script>
            const user = {
                name: 'tom',
                age: 18
            }
            // template('模板id', 数据)
            const result = template('user', user );
            document.querySelector('#result').innerHTML = result;
        </script>
    </body>
    </html>
    

# 7.2 模版语法

art-template 支持标准语法与原始语法。标准语法可以让模板易读写,而原始语法拥有强大的逻辑表达能力。

标准语法支持基本模板语法以及基本 JavaScript 表达式;原始语法支持任意 JavaScript 语句,这和 EJS 一样。

原生语法只做了解即可。

# 7.2.1 输出

用于显示元素到页面

// 标准语法,使用花括号包裹,可以执行运算符
{{value}}
{{data.key}}
{{data['key']}}
{{a ? b : c}}
{{a || b}}
{{a + b}}

// 原生语法 <%= 变量名%>
<%= value %>
<%= data.key %>
<%= data['key'] %>
<%= a ? b : c %>
<%= a || b %>
<%= a + b %>

# 7.2.2 循环语法

// 标准语法,target表示需要迭代的对象或者数组
{{each target}}
	// $index表示循环的下标,$value表示当前下标的数据
    {{$index}} {{$value}}
{{/each}}
 
// 原生语法
<% for(var i = 0; i < target.length; i++){ %>
    <%= i %> <%= target[i] %>
<% } %>

例如要渲染学生数据

// 标准语法
{{each data student idx}}
	<div>名字:{{student.name}},年龄:{{student.age}}, 性别:{{student.gender}}</div>
{{/each}}
  
// 原生语法
 <% for(var i = 0; i < data.length; i++){ %>
   <div>名字:<%= data[i].name%>,年龄:<%= data[i].age%>, 性别:<%= data[i].gender%></div>
 <% } %>

# 7.2.3 条件判断

// 标准语法
{{if value}} ... {{/if}}
{{if v1}} ... {{else if v2}} ... {{/if}}

// 原生语法
<% if (value) { %> ... <% } %>
<% if (v1) { %> ... <% } else if (v2) { %> ... <% } %>

循环输出学生数据,判断性别,如果是男字体显示绿色,女显示红色

// 标准语法    
{{each data student idx}}
      {{if student.gender === '男'}}
        <div style="color: green">名字:{{student.name}},年龄:{{student.age}}, 性别:{{student.gender}}</div>
      {{else}}
        <div style="color: red">名字:{{student.name}},年龄:{{student.age}}, 性别:{{student.gender}}</div>
      {{/if}}
{{/each}}
  
// 原生语法
    <% for(var i = 0; i < data.length; i++){ %>
      <% if (data[i].gender === '男') { %> 
        <div style="color: green">名字:<%= data[i].name %>,年龄:<%= data[i].age %>, 性别:<%= data[i].gender %></div>  
      <% } else { %> 
        <div style="color: red">名字:<%= data[i].name %>,年龄:<%= data[i].age %>, 性别:<%= data[i].gender %></div>  
      <% } %>
    <% } %>

# 8. XMLHttpRequest 2.0

XMLHttpRequest2.0是XHR的新版本,针对老版本的XHR对象的问题作出了很多的改进。XHR2.0发布于2008年,现代浏览器基本都支持XHR2.0的特性,大家可以放心使用。如果你要支持IE10以下的浏览器,就要注意该特性在IE下的最低要求版本了。

# 8.1 请求超时timeout属性

用于设置请求超时,可以在ontimeout方法处理超时逻辑

function getJoke() {
    const xhr = new XMLHttpRequest()
    xhr.open('get', 'http://127.0.0.1:3001/getJoke?jokeId=1');
    xhr.timeout = 2000; // 表示设置两秒钟超时
    // 请求超时处理
    xhr.ontimeout = function() {
        alert('请求超时')
    }
    xhr.onLoad = function() {
      console.log(xhr.responseText)
    }
    xhr.send();
}

# 8.2 FormData对象

ajax操作往往用来传递表单数据。为了方便表单处理,HTML 5新增了一个FormData对象,可以模拟表单。但是FormData对象发送数据给服务端时以二进制的形式发送,多用于上传文件/图片。

下面例子演示如何通过FormData对象自动获取表单用户名和密码,大家可以注意打开chrome network查看发送的请求头 Content-Type为multipart/form-data;

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script>
        function register() {
            // 创建formdata
            const form = document.querySelector('form');
            const formData = new FormData(form); // 创建formData对象
            // 发送ajax
            const xhr = new XMLHttpRequest();
            xhr.open('post', 'http://127.0.0.1:3001/register');
            // xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')// 因为我们以二进制形式提交,不需要进行编码
            xhr.onload= function() {
              console.log(xhr.responseText)
            }
            // 以二进制传递
            xhr.send(formData)
        }
    </script>
</head>
<body>
    <form>
        用户名:<input type="text" name="userName" id="userName" />
        密码:<input type="text" name="userPwd" id="userPwd" />
        <input type="button" value="注册" onclick="register()">
    </form>
</body>

</html>

# 8.3 上传文件

# 8.3.1 使用FormData上传图片

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>原生图片上传</title>
    <style>
        .progress {
            width: 0%;
            height: 20px;
            background-color: lime;
        }
    </style>
    <script>
        function uploadImg() {
            const xhr = new XMLHttpRequest();
            xhr.open('post', 'http://127.0.0.1:3001/uploadFile')
            xhr.onLoad = function() {
              const res = JSON.parse(xhr.responseText);
              document.querySelector('#result').src = res.src;
            }
            // 获取图片
            const file = document.querySelector('#image').files[0];
            const formData = new FormData()
            formData.append('avatar', file) // 给formdata对象添加属性
            xhr.send(formData);
        }
    </script>
</head>

<body>
    <!-- onchange事件,表示用户选择文件后调用 -->
    <input type="file" name="image" id="image" onchange="uploadImg()">
    <img id="result" src="" alt="">
    <div class="progress"></div>
</body>

</html>

# 8.3.2 显示上传进度

// 显示上传进度
xhr.upload.onprogress = function(event) {
  // 判断是否可以计算进度
  if (event.lengthComputable) {
    // 计算百分比 event.loaded / event.total  0.9874876443501531
    // 乘100,去掉末尾小数
    const uploadPercent = parseInt((event.loaded / event.total) * 100) + '%';
    document.querySelector('.progress').style.width = uploadPercent;
    document.querySelector('.progress').innerHTML = uploadPercent;
  }
}

# 8.3.3 使用jquery上传图片

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>jquery上传图片</title>
    <script src="../assets/js/jquery-1.12.2.js"></script>
    <script>
        function uploadImg() {
            // 获取用户图片
            const file = $('#image')[0].files[0];
            // 创建formdata对象
            const formdata = new FormData();
            formdata.append('avatar', file)
            $.ajax({
                type: 'post',
                url: './uploadFile',
                data: formdata,
                contentType: false, // 禁止进行编码
                processData: false, // 禁止对数据进行处理,因为我们的是二进制数据
                success: function(res) {
                    const jRes = JSON.parse(res)
                    $("#uploadImg").attr('src', jRes.src);
                },
                // 重新设置xhr对象,获取上传进度
                xhr: function () {
                  const xhr = new XMLHttpRequest();
                  xhr.upload.onprogress = function(event) {
                  if (event.lengthComputable) {
                    const uploadPercent = parseInt((event.loaded / event.total) * 100) + '%';
                  }
                }
                  return xhr;
                },
            })
        }
    </script>
</head>
<body>
     <input type="file" name="image" id="image" onchange="uploadImg()">
     <img id="uploadImg" src="" alt="">
</body>
</html>

# 8.4 跨域

浏览器出于安全需要,实现了同源策略。同源策略限制浏览器不同源间资源的访问,例如cookie,localstorage,而且不同源间ajax请求无法发送。

# 8.4.1 怎么才算不同源

例如我们访问的页面是:http://127.0.0.1/index.html

检查协议、域名、端口,如果和访问地址有区别即认为不同源,下面三个地址均为不同源

协议:https://127.0.0.1/index.html

域名不同/ip地址:http://127.0.0.2/index.html

端口:http://127.0.0.1:3000/index.html

# 8.4.2 对不同源发送ajax请求

对不同源发送ajax请求,浏览器会提示下图红色的错误。

image-20191225122156753

# 8.4.3 跨域解决方法

  1. 服务端设置响应头Access-Control-Allow-Origin:*(推荐使用)

    var server = http.createServer((req,res) => {
      res.setHeader('access-control-allow-origin', '*'); // 关键代码
    })
    

    设置完毕后,发起ajax请求查看该请求的响应头,会出现下图,表示设置成功。

    image-20191225122949028

  2. 服务器代理请求(较少使用)

    服务器代理模式相当于,你需要购买国外的一件商品,你因为没法出国购买,这时候你可以委托别人旅游的时候帮你带一件回来,这个委托人就相当于代理服务器。

  3. JSONP(不推荐使用,了解即可)

<script>
  function success(res) {
    console.log(res)
  }
</script>
<!-- JSONP需要服务端配置实现 -->
<script src="http://127.0.0.1:5001/getStudentsJSONP.js?callback=success"></script>