Skip to content

02 AJAX 综合案例

知识点自测

  1. 以下代码运行结果是什么?(考察扩展运算符的使用)

    js
    const result = {
      name: '老李',
      age: 18,
    };
    const obj = {
      ...result,
    };
    console.log(obj.age);
    • A:报错
    • B:18
    答案
    • B 正确。
    • 这段代码使用了扩展运算符(spread operator),它用于将一个对象的属性复制到另一个对象。
    • 在这里,result对象的属性被复制到了新的对象obj中。因此,obj对象也包含了age属性,其值为18。最后,通过console.log(obj.age)打印出obj对象的age属性值,结果为18
  2. 什么是事件委托?

    • A:只能把单击事件委托给父元素绑定
    • B:可以把能冒泡的事件,委托给已存在的向上的任意标签元素绑定
    答案
    • B 正确。
    • 事件委托是一种 JavaScript 编程模式,它利用事件冒泡的特性,将事件处理程序绑定到一个父元素上,而不是直接在子元素上绑定。这样,当子元素触发事件时,事件会冒泡到父元素,然后在父元素上触发事件处理程序。这样做的好处是减少事件处理程序的数量,提高性能,特别是在有大量子元素的情况下。
    • 所以,选项 B 描述了事件委托的主要思想,可以将能够冒泡的事件委托给已存在的向上的任意标签元素。
  3. 事件对象 e.target 作用是什么?

    • A:获取到这次触发事件相关的信息
    • B:获取到这次触发事件目标标签元素
    答案
    • B 正确。
    • 事件对象 e 包含了触发事件的相关信息,其中 e.target 表示触发事件的目标元素。当事件在文档中的某个元素上触发时,e.target 将引用这个元素。
    • 这对于事件委托非常有用,因为你可以通过检查 e.target 来确定事件的实际目标,从而执行相应的操作。
  4. 如果获取绑定在标签上自定义属性的值 10?

    html
    <div data-code="10">西游记</div>
    • A:div标签对象.innerHTML
    • B:div标签对象.dataset.code
    • C:div标签对象.code
    答案
    • B 正确。
    • 要获取绑定在标签上的自定义属性值,可以使用 dataset 属性。
    • 在这个例子中,正确的方式是通过 div 标签对象的 dataset.code 来获取 data-code 属性的值。所以,选项 B 是正确的。
  5. 哪个方法可以判断目标标签是否包含指定的类名?

    html
    <div class="my-div title info"></div>
    • A: div标签对象.className === 'title'
    • B: div标签对象.classList.contains('title')
    答案
    • B 正确。
    • 要判断目标标签是否包含指定的类名,可以使用 classList.contains 方法。
    • 在这个例子中,正确的方式是通过 div 标签对象的 classList.contains('title') 来检查是否包含名为 'title' 的类名。所以,选项 B 是正确的。
  6. 伪数组取值哪种方式是正确的?

    js
    let obj = { 0: '老李', 1: '老刘' };
    • A: obj.0
    • B: obj[0]
    答案
    • B 正确。
    • 正确的方式是使用方括号([])来访问伪数组(类数组对象)中的属性,因此 obj[0] 是正确的方法,可以得到 '老李' 的值。
    • 选项 A 中使用点号(.)来访问属性,这在伪数组中是不正确的。
  7. 以下哪个选项可以,往本地存储键为‘bgImg’,值为图片 url 网址的代码

    • A: localStorage.setItem('bgImg')
    • B: localStorage.getItem('bgImg')
    • C:localStorage.setItem('bgImg', '图片 url 网址')
    • D:localStorage.getItem('bgImg', '图片 url 网址')
    答案
    • C 正确。
    • 要往本地存储中设置键值对,可以使用 localStorage.setItem(key, value) 方法,其中 key 是键,value 是对应的值。所以,选项 C 是正确的方式。
  8. 以下代码运行结果是?

    js
    const obj = {
      username: '老李',
      age: 18,
      sex: '男',
    };
    Object.keys(obj);
    • A:代码报错
    • B:[username, age, sex]
    • C:["username", "age", "sex"]
    • D:["老李", 18, "男"]
    答案
    • C 正确。
    • Object.keys(obj) 方法返回一个包含给定对象的所有可枚举属性的字符串数组。
    • 在这个例子中,obj 对象有三个属性,分别是 usernameagesex,因此 Object.keys(obj) 返回的结果是 ["username", "age", "sex"]。所以,选项 C 是正确的。
  9. 下面哪个选项可以把数字字符串转成数字类型?

    • A:+'10'
    • B:'10' + 0
    答案
    • A 正确。
    • 使用一元加号 + 可以将数字字符串转换为数字类型。因此,+'10' 会将字符串 '10' 转换为数字 10。选项 A 是正确的。
    • 在选项 B 中,字符串 '10' 会被与数字 0 进行字符串拼接,结果是字符串 '100',而不是数字类型。
  10. 以下代码运行后的结果是什么?(考察逻辑与的短路特性)

    js
    const age = 18;
    const result1 = age || '有年龄';
    
    const sex = '';
    const result2 = sex || '没有性别';
    • A:报错,报错
    • B:18,没有性别
    • C:有年龄,没有性别
    • D:18,''
    答案
    • B 正确。
    • 这段代码利用了逻辑或的短路特性。在逻辑或运算中,如果第一个操作数为真(真值),则返回第一个操作数的值,否则返回第二个操作数的值。在这里:
      • result1 中,age 的值是真值(非零数字),所以 result1 的值是 age,即 18
      • result2 中,sex 的值是假值(空字符串),所以 result2 的值是 '没有性别'
    • 因此,代码运行后的结果是 18,没有性别

学习目标

今天主要就是练,巩固 axios 的使用

  1. 完成案例 - 图书管理系统(增删改查)经典业务
  2. 掌握图片上传的思路
  3. 完成案例 - 网站换肤并实现图片地址缓存
  4. 完成案例 - 个人信息设置

图书管理 - 介绍

  1. 打开备课代码运行图书管理案例效果 - 介绍要完成的增删改查业务效果和 Bootstrap 弹框使用

    468180b6-d9da-41ce-b862-66c8b2ab38f1

    8f446b92-8da0-46dc-93cc-9f546153292d

  2. 分析步骤和对应的视频模块

    • 先学习 Bootstrap 弹框的使用(因为添加图书和编辑图书需要这个窗口来承载图书表单)

    • 先做渲染图书列表(这样做添加和编辑以及删除可以看到数据变化,所以先做渲染)

    • 再做新增图书功能

    • 再做删除图书功能

    • 再做编辑图书功能(注意:编辑和新增图书是 2 套弹框 - 后续做项目我们再用同 1 个弹框)

Bootstrap 弹框

学习目标

  • 使用属性方式控制 Bootstarp 弹框的显示和隐藏
  • 使用 JS 方式控制 Bootstarp 弹框的显示和隐藏

属性控制

Modal · Bootstrap v5.3

  1. 什么是 Bootstrap 弹框?

    • 不离开当前页面,显示单独内容,供用户操作
  2. 需求:使用 Bootstrap 弹框,先做个简单效果,点击按钮,让弹框出现,点击 X 和 Close 让弹框隐藏

    6a7827bc-5202-4c84-92d9-c76496265c2d

  3. 如何使用 Bootstrap 弹框呢?

    • 先引入 bootstrap.cssbootstrap.js 到自己网页中

      html
      <link
        href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
        rel="stylesheet"
        integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
        crossorigin="anonymous" />
      <script
        src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.min.js"
        integrity="sha384-BBtl+eGJRgqQAUMxJ7pMwbEyER4l1g+O15P+16Ep7Q9Q+zqX6gSbd85u4mG4QzX+"
        crossorigin="anonymous"></script>
      
      <link
        rel="stylesheet"
        href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
        integrity="sha256-MBffSnbbXwHCuZtgPYiwMQbfE7z+GOZ7fBPCNB06Z98="
        crossorigin="anonymous" />
      <script
        src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.min.js"
        integrity="sha256-YMa+wAM6QkVyz999odX7lPRxkoYAan8suedu4k2Zur8="
        crossorigin="anonymous"></script>
    • 准备弹框标签,确认结构(可以从 Modal · Bootstrap v5.3 里复制基础例子)

    • 运行到网页后,逐一对应标签和弹框每个部分对应关系

    • 通过自定义属性,通知弹框的显示和隐藏,语法如下:

      html
      <button data-bs-toggle="modal" data-bs-target="css 选择器">显示弹框</button>
      
      <button data-bs-dismiss="modal">Close</button>

data-bs-toggle

  • 作用: 用于定义何种事件将触发控制。
  • 值: 取决于你想要使用的 Bootstrap 组件,例如 "modal" 表示点击按钮时打开对话框。
html
<!-- `data-bs-toggle="modal"` 表示点击按钮时以模态框方式展示目标元素 -->
<button data-bs-toggle="modal" data-bs-target="#myModal">打开对话框</button>

data-bs-target

  • 作用: 用于指定要控制的目标元素或组件。
  • 值: 通常包含一个选择器或元素的 ID,指示被控制的目标。
html
<!-- `data-bs-target` 指定了要控制的对话框的 ID 为 `myModal` -->
<button data-bs-toggle="modal" data-bs-target="#myModal">打开对话框</button>
<div class="modal" id="myModal">对话框内容</div>

data-bs-dismiss

  • 作用: 触发关闭指定组件或元素,通常与对话框一起使用。
  • 值: 取决于你想要关闭的组件类型。最常见的值是 "modal",表示关闭对话框。
html
<!-- 按钮被点击时,data-bs-dismiss="modal" 表示关闭包含它的最近的对话框 -->
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>

案例 - Bootstrap 属性控制对话框

案例 - Bootstrap 属性控制对话框
html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>12 属性控制 Bootstrap 弹窗</title>
  <!-- 引入 bootstrap.css -->
  <link rel="stylesheet" href="./css/bootstrap.min.css">
</head>

<body class="p-3 mb-2 d-flex justify-content-center align-items-center vh-100">

  <!-- Button trigger modal -->
  <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">
    显示弹窗/对话框
  </button>

  <!-- Modal -->
  <div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
    <div class="modal-dialog modal-dialog-centered">
      <div class="modal-content">
        <div class="modal-header">
          <h1 class="modal-title fs-5" id="exampleModalLabel">Modal title 弹框头部</h1>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          <p>Modal body text goes here. 弹框身体</p>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
          <button type="button" class="btn btn-primary">Save changes</button>
        </div>
      </div>
    </div>
  </div>

  <!-- 引入 bootstrap.js -->
  <script src="./js/bootstrap.min.js"></script>
</body>

</html>

JS 控制

  1. 为什么需要 JS 方式控制呢?

    • 当我显示之前,隐藏之前,需要执行一些 JS 逻辑代码,就需要引入 JS 控制弹框显示/隐藏的方式了

    • 例如:

      • 点击编辑姓名按钮,在弹框显示之前,在输入框填入默认姓名
      • 点击保存按钮,在弹框隐藏之前,获取用户填入的名字并打印
  2. 所以在现实和隐藏之前,需要执行 JS 代码逻辑,就使用 JS 方式 控制 Bootstrap 弹框显示和隐藏。

    语法如下:

    js
    // 创建弹框对象
    const modalDom = document.querySelector('css 选择器');
    const modal = new bootstrap.Modal(modelDom);
    
    // 显示弹框
    modal.show();
    // 隐藏弹框
    modal.hide();

案例 - Bootstrap JS 控制对话框

案例 - Bootstrap JS 控制对话框
html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>13 JS 控制 Bootstrap 弹窗</title>
  <!-- 引入 bootstrap.css -->
  <link rel="stylesheet" href="./css/bootstrap.min.css">
</head>

<body class="p-3 mb-2 d-flex justify-content-center align-items-center vh-100">

  <!-- Button trigger modal -->
  <button type="button" class="btn btn-primary edit-btn" data-bs-toggle="modal" data-bs-target="#exampleModal">
    编辑姓名
  </button>

  <!-- Modal -->
  <div class="modal name-box" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
    <div class="modal-dialog modal-dialog-centered">
      <div class="modal-content">
        <div class="modal-header">
          <h4 class="modal-title fs-5" id="exampleModalLabel">请输入姓名</h4>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          <form action="" method="get" class="add-form">
            <label for="name">姓名:</label>
            <input type="text" id="name" name="name" class="form-control" placeholder="请输入姓名">
          </form>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
          <button type="button" class="btn btn-primary save-btn">保存</button>
        </div>
      </div>
    </div>
  </div>

  <!-- 引入 bootstrap.js -->
  <script src="./js/bootstrap.min.js"></script>
  <script>
    // 创建弹窗对象
    const modalDom = document.querySelector(".name-box");
    const modal = new bootstrap.Modal(modalDom);

    // 点击按钮显示弹窗
    const editBtn = document.querySelector(".edit-btn");
    editBtn.addEventListener("click", () => {
      modal.show();
    });

    // 点击保存按钮,获取输入框的值,并关闭弹窗
    const saveBtn = document.querySelector(".save-btn");
    saveBtn.addEventListener("click", () => {
      const name = document.querySelector("#name").value;
      console.log(name);
      modal.hide();
      // 将输入框内容设置为默认值
      // document.querySelector("#name").value = "";
      // document.querySelector("#name").placeholder = "请输入姓名";
      document.querySelector(".add-form").reset(); // .reset() 重置表单
    });
  </script>
</body>

</html>

总结

  1. 用哪个属性绑定来控制弹框显示呢?
    • data-bs-toggledata-bs-target
  2. 用哪个属性来控制隐藏弹框呢?
    • data-bs-dismiss
  3. 什么时候用属性控制,什么时候用 JS 控制 Bootstrap 弹框的显示/隐藏?
    • 直接出现/隐藏用属性方式控制,如果需要先执行一段 JS 逻辑再显示/隐藏就用 JS 方式控制

图书管理

查询图书信息

  1. 获取数据
  2. 渲染列表
js
/**
 * @description 渲染图书列表
 *
 * api: http://hmajax.itheima.net/api/books - GET
 * @param {string} creator - 操作者昵称
 */

const creator = '秋豆麻袋';
function renderBookList() {
  axios
    .get('http://hmajax.itheima.net/api/books', {
      params: {
        creator,
      },
    })
    .then((response) => {
      // console.log(response.data);
      console.log(response.data.data);

      document.querySelector('.list').innerHTML = response.data.data
        .map((item, index) => {
          return `
            <tr>
              <td>${index + 1}</td>
              <td>${item.bookname}</td>
              <td>${item.author}</td>
              <td>${item.publisher}</td>
              <td data-id=${item.id}>
                <span class="del">删除</span>
                <span class="edit">编辑</span>
              </td>
            </tr>`;
        })
        .join('');
    })
    .catch((error) => {
      console.log(error.message);
    });
}
// 网页加载完成后执行
renderBookList();

新增图书

  1. 点击右上角'添加'按钮,新增弹窗(显示与隐藏),使用 bootstrap 的 modal
  2. 使用 form-serialize 收集表单数据
  3. 点击保存按钮,然后发送表单里的图书信息到服务器保存
  4. 成功后重新从服务器获取数据,然后渲染图书列表,重置表单,最后关闭弹窗
js
/**
 * @description 新增图书
 *
 * api: http://hmajax.itheima.net/api/books - POST
 * @param {string} bookname - 书名
 * @param {string} author - 作者
 * @param {string} publisher - 出版社
 * @param {string} creator - 操作者昵称
 */
function addBook() {
  // 新增弹窗(显示与隐藏),使用 bootstrap 的 modal
  const addModalDom = document.querySelector('.add-modal');
  const addModal = new bootstrap.Modal(addModalDom);

  const addBtn = document.querySelector('.add-btn');
  addBtn.addEventListener('click', () => {
    // 使用 form-serialize 收集表单数据,然后发送到服务器保存
    const addForm = document.querySelector('.add-form');
    const bookObj = serialize(addForm, { hash: true, empty: true });

    axios
      .post('http://hmajax.itheima.net/api/books', {
        ...bookObj, // 合并对象
        creator,
      })
      .then((response) => {
        console.log(response.data);
        // 成功后重新从服务器获取数据,然后渲染图书列表
        renderBookList();
        // 重置表单
        addForm.reset();
        // 关闭弹窗
        addModal.hide();
      })
      .catch((error) => {
        console.log(error.message);
      });
  });
}

addBook();

服务器响应:

json
{
  "message": "添加图书成功",
  "data": {
      "id": 349535,
      "bookname": "我在北京送快递",
      "author": "胡安焉",
      "publisher": "湖南文艺出版社"
  }
}
{
  "message": "添加图书成功",
  "data": {
      "id": 349536,
      "bookname": "寻求意义",
      "author": "李泽厚",
      "publisher": "人民文学出版社"
  }
}

删除图书

  1. 列表里的删除元素绑定点击事件(事件委托,判断点击的是删除元素然后再执行删除逻辑代码),获取要删除的图书 id
  2. 点击删除元素,发送删除请求到服务器
  3. 成功后重新从服务器获取数据,然后渲染图书列表
js
/**
 * @description 删除图书
 *
 * api: http://hmajax.itheima.net/api/books/:id - DELETE
 * @param {number} id - 图书 id
 */
function delBook() {
  // 删除元素绑定点击事件(事件委托)
  document.querySelector('.list').addEventListener('click', (e) => {
    // 判断触发事件目标元素:点击的是哪个元素
    // console.log(e.target);

    // 判断点击的是删除按钮
    if (e.target.classList.contains('del')) {
      // console.log('点击了删除按钮!!!');

      // 获取元素的 data-id 属性,即图书 id
      // const id = e.target.parentNode.dataset.id;
      const id = e.target.parentNode.getAttribute('data-id');
      // console.log(`图书 id:${id}`);
      axios
        .delete(`http://hmajax.itheima.net/api/books/${id}`)
        .then((response) => {
          console.log(response.data);
          // 成功后重新从服务器获取数据,然后渲染图书列表
          renderBookList();
        })
        .catch((error) => {
          console.log(error.message);
        });
    }
  });
}
delBook();
// {"message":"删除图书成功","data":{"id":"349536"}}

编辑图书信息

  1. 编辑元素绑定点击事件(事件委托,判断点击的是编辑元素然后再执行编辑逻辑代码),获取要编辑的图书 id
  2. 点击列表中的编辑元素,弹出编辑图书弹窗(使用 bootstrap 的 modal),然后从服务器获取图书信息渲染在弹窗中
  3. 点击保存按钮,然后发送表单里的图书信息到服务器保存
  4. 成功后重新从服务器获取数据,然后渲染图书列表,最后关闭弹窗
js
/**
 * @description 编辑图书信息
 *
 * api: http://hmajax.itheima.net/api/books/:id - PUT
 * @param {number} id - 图书 id
 */
function editBook() {
  // 新增弹窗(显示与隐藏),使用 bootstrap 的 modal
  const editModalDom = document.querySelector('.edit-modal');
  const editModal = new bootstrap.Modal(editModalDom);

  // 点击列表中的编辑按钮,弹出编辑图书弹窗,然后从服务器获取图书信息渲染在弹窗中
  document.querySelector('.list').addEventListener('click', (e) => {
    // 判断触发事件目标元素:点击的是哪个元素
    // console.log(e.target);

    // 判断点击的是编辑按钮
    if (e.target.classList.contains('edit')) {
      // console.log('点击了编辑按钮!!!');

      // 获取元素的 data-id 属性,即图书 id
      const id = e.target.parentNode.getAttribute('data-id');
      // console.log(`图书 id:${id}`);

      // 使用 axios 发送请求,获取图书信息
      axios
        .get(`http://hmajax.itheima.net/api/books/${id}`)
        .then((response) => {
          // console.log(response.data);
          // 将图书信息渲染到弹窗中
          const bookObj = response.data.data;
          // console.log(bookObj);

          // 给表单赋值
          const editForm = document.querySelector('.edit-form');
          // editForm.bookname.value = bookObj.bookname;
          // editForm.author.value = bookObj.author;
          // editForm.publisher.value = bookObj.publisher;
          // editForm.id.value = bookObj.id;

          // 因为对象 "属性" 和标签 "类名" 一样,所以我们使用循环遍历 bookObj 对象,使用属性去获取对应的标签,实现快速赋值
          for (const key in bookObj) {
            // console.log(key);
            // console.log(bookObj[key]);
            // console.log(editForm[key]);
            editForm[key].value = bookObj[key];
          }

          // 显示弹窗
          editModal.show();
        })
        .catch((error) => {
          console.log(error.message);
        });
    }
  });

  // 在打开的编辑图书弹窗中,点击修改按钮后,发送数据到服务器保存
  document.querySelector('.edit-btn').addEventListener('click', () => {
    // 使用 form-serialize 收集表单数据,然后发送到服务器保存
    const editForm = document.querySelector('.edit-form');
    // const bookObj = serialize(editForm, { hash: true, empty: true });
    const { bookname, author, publisher, id } = serialize(editForm, { hash: true, empty: true });

    axios
      .put(`http://hmajax.itheima.net/api/books/${id}`, {
        // ...bookObj, // 合并对象
        bookname,
        author,
        publisher,
        creator,
      })
      .then((response) => {
        console.log(response.data);
        // 成功后重新从服务器获取数据,然后渲染图书列表
        renderBookList();

        // 关闭弹窗
        editModal.hide();
      })
      .catch((error) => {
        console.log(error.message);
      });
  });
}
editBook();

服务器响应:

json
{
  "message": "修改图书成功",
  "data": {
    "id": 349535,
    "bookname": "我在北京送快递",
    "author": "胡安焉",
    "publisher": "湖南文艺出版社"
  }
}

总结

js
// 1.1 获取数据
axios({...}).then(result => {
  const bookList = result.data.data
  // 1.2 渲染数据
  const htmlStr = bookList.map((item, index) => {
    return `<tr>
    <td>${index + 1}</td>
    <td>${item.bookname}</td>
    <td>${item.author}</td>
    <td>${item.publisher}</td>
    <td data-id=${item.id}>
      <span class="del">删除</span>
      <span class="edit">编辑</span>
    </td>
  </tr>`
  }).join('')
  document.querySelector('.list').innerHTML = htmlStr
})
js
// 2.1 创建弹框对象
const addModalDom = document.querySelector('.add-modal')
const addModal = new bootstrap.Modal(addModalDom)
document.querySelector('.add-btn').addEventListener('click', () => {
  // 2.2 收集表单数据,并提交到服务器保存
  const addForm = document.querySelector('.add-form')
  const bookObj = serialize(addForm, { hash: true, empty: true })
  axios({...}).then(result => {
    // 2.3 添加成功后,重新请求并渲染图书列表
    getBooksList()
    addForm.reset()
    addModal.hide()
  })
})
js
// 3.1 删除元素->点击(事件委托)
document.querySelector('.list').addEventListener('click', e => {
  if (e.target.classList.contains('del')) {
    // 获取图书 id(自定义属性 id)
    const theId = e.target.parentNode.dataset.id
    // 3.2 调用删除接口
    axios({...}).then(() => {
      // 3.3 刷新图书列表
      getBooksList()
    })
  }
})
js
// 4.1 编辑弹框->显示和隐藏
const editDom = document.querySelector('.edit-modal')
const editModal = new bootstrap.Modal(editDom)
document.querySelector('.list').addEventListener('click', e => {
  if (e.target.classList.contains('edit')) {
    // 4.2 获取当前编辑图书数据->回显到编辑表单中
    const theId = e.target.parentNode.dataset.id
    axios({...}).then(result => {
      const bookObj = result.data.data
      // 遍历数据对象,使用属性去获取对应的标签,快速赋值
      const keys = Object.keys(bookObj)
      keys.forEach(key => {
        document.querySelector(`.edit-form .${key}`).value = bookObj[key]
      })
    })
    editModal.show()
  }
})

document.querySelector('.edit-btn').addEventListener('click', () => {
  // 4.3 提交保存修改,并刷新列表
  const editForm = document.querySelector('.edit-form')
  const { id, bookname, author, publisher } = serialize(editForm, { hash: true, empty: true})
  // 保存正在编辑的图书 id,隐藏起来:无需让用户修改
  // <input type="hidden" class="id" name="id" value="84783">
  axios({...}).then(() => {
    getBooksList()
    editModal.hide()
  })
})

图片上传

  1. 什么是图片上传?

    • 就是把本地的图片上传到网页上显示
  2. 图片上传怎么做?

    • 先依靠文件选择元素获取用户选择的本地文件,接着提交到服务器保存,服务器会返回图片的 url 网址,然后把网址加载到 img 标签的 src 属性中即可显示
  3. 为什么不直接显示到浏览器上,要放到服务器上呢?

    • 因为浏览器保存是临时的,如果你想随时随地访问图片,需要上传到服务器上
  4. 图片上传步骤:

    • 先获取图片文件对象

    • 使用 FormData 表单数据对象装入

      • 因为图片是文件而不是以前的数字和字符串了所以传递文件一般需要放入 FormData 以键值对 - 文件流的数据传递(可以查看请求体 - 确认请求体结构)
      js
      const formData = new FormData()
      formData.append(参数名)
    • 提交表单数据对象,使用服务器返回图片 url 网址

案例 - 图片上传

  • 先用文件选择元素,获取到文件对象
  • 然后装入 FormData 表单对象中,再发给服务器
  • 解析服务器响应,得到图片在服务器的 URL 网址,再通过 img 标签加载图片显示
案例 - 图片上传
html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>15 图片上传</title>
  <!-- 引入 bootstrap.css -->
  <link rel="stylesheet" href="./css/bootstrap.min.css">
</head>

<body class="p-5">
  <div class="mb-3">
    <label for="formFile" class="form-label">上传图片</label>
    <input class="form-control" type="file" id="formFile">
  </div>
  <img src="" class="rounded mx-auto d-block" alt="上传的图片">

  <script src="./js/axios.min.js"></script>
  <script>
    document.querySelector("#formFile").addEventListener("change", (e) => {
      // 获取文件
      const file = e.target.files[0]
      console.log(file)

      // FormData 对象用于构建一组键/值对,以模拟一个完整的 HTML 表单
      // FormData 对象可以携带文件用来上传
      const formData = new FormData()
      formData.append("img", file)
      axios
        .post("http://hmajax.itheima.net/api/uploadimg", formData)
        .then((response) => {
          console.log(response.data)
          document.querySelector("img.rounded").src = response.data.data.url
        }).catch(error => {
          console.log(error.message)
        })
    })
  </script>
</body>

</html>
bash
------WebKitFormBoundarykB0csH4AW9ehZW9Z
Content-Disposition: form-data; name="img"; filename="16761891466140.7797740766205927.png"
Content-Type: image/png


------WebKitFormBoundarykB0csH4AW9ehZW9Z--
json
{
  "message": "上传成功",
  "data": {
    "url": "http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/ajax/17045282675650.4073109932758119.png"
  }
}

案例 - 更换网站背景图

  • 网站更换背景图如何实现呢,并且保证刷新后背景图还在显示呢?

  • 解决方案:

    • 使用 localStorage 本地存储,将图片的 url 存储到本地,

    • 刷新页面时,再通过 localStorage 获取到 url 并加载,并判断本地有图片 url 网址字符串才设置

    • localStorage 取值和赋值的语法

      js
      // 赋值
      localStorage.setItem('background-image', 'url(https://picsum.photos/id/1005/1920/1080)');
      
      // 取值
      localStorage.getItem('background-image');
案例 - 更换网站背景图
html
<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>网站-更换背景</title>
  <!-- 初始化样式 -->
  <link rel="stylesheet" href="./css/reset.min.css">
  <!-- 核心样式 -->
  <link rel="stylesheet" href="./css/index.css">
</head>

<body>
  <div class="container">
    <div class="nav">
      <div class="left">
        <ul>
          <li><a href="http://yun.itheima.com/?webzly" target="_blank" rel="nofollow">免费教程</a></li>
          <li><a href="http://resource.ityxb.com/booklist/?webzly" target="_blank" rel="nofollow">原创书籍</a></li>
          <li><a href="http://www.itheima.com/teacher.html?webzly#ajavaee" target="_blank" rel="nofollow">教研团队</a></li>
          <li><a href="http://www.itheima.com/special/hmschool/index.shtml?webzly" target="_blank"
              rel="nofollow">校区汇总</a></li>
          <li><a href="http://www.itheima.com/flow/flow.html?webzly" target="_blank" rel="nofollow">报名流程</a></li>
          <li><a href="https://pip.itcast.cn?hmgw$webzly" target="_blank" rel="nofollow">项目信息站</a></li>
          <li><a href="http://bbs.itheima.com/forum.php?webzly" target="_blank" rel="nofollow">技术社区</a></li>
        </ul>
      </div>
      <div class="right">
        <label for="bg">更换背景</label>
        <input class="bg-ipt" type="file" id="bg">
      </div>
    </div>
    <div class="search-container">
      <img src="./logo.png" alt="">
      <div class="search-box">
        <input type="text">
        <button>搜索一下</button>
      </div>
    </div>
  </div>
  <script src="./js/axios.min.js"></script>
  <!-- <script src="./js/index.js"></script> -->
  <script>
    // 更换背景按钮绑定点击事件
    document.querySelector('.bg-ipt').addEventListener('change', (e) => {
      if (e.target.files[0]) {
        // 读取文件
        const formData = new FormData();
        formData.append('img', e.target.files[0]);

        // 上传文件
        axios
          .post('http://hmajax.itheima.net/api/uploadimg', formData)
          .then((response) => {
            // 上传成功
            console.log(response.data);
            document.body.style.backgroundImage = `url(${response.data.data.url})`;

            // 保存背景图片 url 地址到 localStorage
            localStorage.setItem('bgImg', response.data.data.url);
          })
          .catch((error) => {
            console.log(error.message);
          });
      }
    });

    // 网页运行后从 localStorage 中读取背景图片
    const bgImg = localStorage.getItem('bgImg');
    // bgImg && (document.body.style.backgroundImage = `url(${bgImg})`);
    if (bgImg) {
      document.body.style.backgroundImage = `url(${bgImg})`;
    }

  </script>

</body>

</html>

个人信息设置

介绍个人信息设置案例。

介绍

项目分为:信息回显 + 头像修改 + 信息修改 + 提示框反馈 4 部分。

  1. 先完成信息回显
  2. 再做头像修改 - 立刻就更新给此用户
  3. 收集个人信息表单 - 提交保存
  4. 提交后反馈结果给用户(提示框)

信息渲染

把昵称对应的用户信息渲染到页面上。

js
/**
 * @description 个人信息渲染:把用户信息渲染到页面上
 *
 * api: http://hmajax.itheima.net/api/settings - GET
 * @param {string} creator - 操作者昵称
 */

const creator = '秋豆麻袋';
function renderInfo() {
  axios
    .get(`http://hmajax.itheima.net/api/settings?creator=${creator}`)
    .then((response) => {
      console.log(response.data);
      const userObj = response.data.data;

      for (const key of Object.keys(userObj)) {
        // console.log(`${key}: ${userObj[key]}`);

        // userObj: {avatar: 'http://hmajax.itheima.net/avatar/avatar1.png', nickname: 'itheima', email: 'itheima@itcast.cn', desc: '我是秋豆麻袋', gender: 0(0 男 1 女)}
        // html 里的 class 为:prew, nickname, email, desc, gender
        if (key === 'avatar') {
          document.querySelector('.avatar-box .prew').src = userObj[key];
        } else if (key === 'gender') {
          // 获取性别单选框:[男 radio 元素,女 radio 元素]
          const genderRadioList = document.querySelectorAll('.user-form .gender');
          // 获取性别数字:0 男,1 女
          const genderNum = userObj[key];
          // 通过性别数字,作为下标,找到对应性别单选框,设置选中状态
          genderRadioList[genderNum].checked = true;
        } else {
          // 其他情况,直接赋值给对应的输入框
          document.querySelector(`.user-form .${key}`).value = userObj[key];
        }
      }
    })
    .catch((error) => {
      console.log(error.message);
    });
}
renderInfo();

头像修改

  1. 获取到用户选择的头像文件
  2. 调用头像修改接口,并除了头像文件外,还要在 FormData 表单数据对象中携带操作者昵称
  3. 提交到服务器保存此用户对应头像文件,并把返回的头像图片 url 网址设置在页面上
js
/**
 * @description: 个人头像修改
 *
 * api: http://hmajax.itheima.net/api/avatar - PUT
 * @param {file} avatar - 个人头像文件
 * @param {string} creator - 操作者昵称
 */
function changeAvatar() {
  // 更改头像 按钮绑定事件
  document.querySelector('.avatar-box #upload').addEventListener('change', (e) => {
    if (e.target.files.length > 0) {
      const formData = new FormData();
      formData.append('avatar', e.target.files[0]);
      formData.append('creator', creator);

      axios
        .put('http://hmajax.itheima.net/api/avatar', formData)
        .then((response) => {
          console.log('保存个人头像成功');
          document.querySelector('.avatar-box .prew').src = response.data.data.avatar;
        })
        .catch((error) => {
          console.log(error.message);
        });
    }
  });
}
changeAvatar();

信息修改

  1. 收集表单数据
  2. 提交到服务器保存 - 调用用户信息更新接口(注意请求方法是 PUT)代表数据更新的意思
js
/**
 * @description: 个人信息修改
 *
 * api: http://hmajax.itheima.net/api/settings - PUT
 * @param {string} desc - 用户简介
 * @param {string} email - 邮箱
 * @param {integer} gender - 用户性别 (0 男 1 女)
 * @param {string} nickname - 用户昵称
 * @param {string} creator - 操作者昵称
 */
function updateInfo() {
  // 提交按钮绑定事件
  document.querySelector('.submit').addEventListener('click', () => {
    // 获取表单数据
    const userForm = document.querySelector('.user-form');
    const userObj = serialize(userForm, { hash: true, empty: true });
    console.log(userObj);
    // {email: 'itheima@itcast.cn', nickname: 'itheima', gender: '0', desc: '我是秋豆麻袋'}
    // 需要将 gender 转换为数字,添加 creator 属性
    userObj.gender = +userObj.gender;
    userObj.creator = creator;
    console.log(userObj);
    // {email: 'itheima@itcast.cn', nickname: 'itheima', gender: 0, desc: '我是秋豆麻袋', creator: '秋豆麻袋'}

    axios
      .put('http://hmajax.itheima.net/api/settings', userObj)
      .then((response) => {
        console.log(response.data);
        console.log('保存个人设置成功');

        // 使用 bootstrap 里的 toast 组件
        const toastDom = document.querySelector('.my-toast');
        const toast = new bootstrap.Toast(toastDom);

        // 显示 toast
        toast.show();
      })
      .catch((error) => {
        console.log(error.msessage);
      });
  });
}
updateInfo();

提示框

  1. 把用户更新个人信息结果,用提示框反馈给用户

  2. 使用 bootstrap 提示框,提示个人信息设置后的结果。

  3. bootstrap 的 toast 提示框和 modal 弹框使用很像,语法如下:

    • 先准备对应的标签结构(模板里已有),设置延迟自动消失的时间

      html
      <!-- data-bs-delay="1500" 表示延迟 1.5 秒后自动消失 -->
      <div class="toast" data-bs-delay="1500">提示框内容</div>
    • 使用 JS 的方式,在 axios 请求响应成功时,展示结果

      js
      // 创建提示框对象
      const toastDom = document.querySelector('css 选择器');
      const toast = new bootstrap.Toast(toastDom);
      
      // 显示提示框
      toast.show();

今日重点

  1. 掌握增删改查数据的思路

    js
    // 1.1 获取数据
    axios({...}).then(result => {
      const bookList = result.data.data
      // 1.2 渲染数据
      const htmlStr = bookList.map((item, index) => {
        return `<tr>
        <td>${index + 1}</td>
        <td>${item.bookname}</td>
        <td>${item.author}</td>
        <td>${item.publisher}</td>
        <td data-id=${item.id}>
          <span class="del">删除</span>
          <span class="edit">编辑</span>
        </td>
      </tr>`
      }).join('')
      document.querySelector('.list').innerHTML = htmlStr
    })
    js
    // 2.1 创建弹框对象
    const addModalDom = document.querySelector('.add-modal')
    const addModal = new bootstrap.Modal(addModalDom)
    document.querySelector('.add-btn').addEventListener('click', () => {
      // 2.2 收集表单数据,并提交到服务器保存
      const addForm = document.querySelector('.add-form')
      const bookObj = serialize(addForm, { hash: true, empty: true })
      axios({...}).then(result => {
        // 2.3 添加成功后,重新请求并渲染图书列表
        getBooksList()
        addForm.reset()
        addModal.hide()
      })
    })
    js
    // 3.1 删除元素->点击(事件委托)
    document.querySelector('.list').addEventListener('click', e => {
      if (e.target.classList.contains('del')) {
        // 获取图书 id(自定义属性 id)
        const theId = e.target.parentNode.dataset.id
        // 3.2 调用删除接口
        axios({...}).then(() => {
          // 3.3 刷新图书列表
          getBooksList()
        })
      }
    })
    js
    // 4.1 编辑弹框->显示和隐藏
    const editDom = document.querySelector('.edit-modal')
    const editModal = new bootstrap.Modal(editDom)
    document.querySelector('.list').addEventListener('click', e => {
      if (e.target.classList.contains('edit')) {
        // 4.2 获取当前编辑图书数据->回显到编辑表单中
        const theId = e.target.parentNode.dataset.id
        axios({...}).then(result => {
          const bookObj = result.data.data
          // 遍历数据对象,使用属性去获取对应的标签,快速赋值
          const keys = Object.keys(bookObj)
          keys.forEach(key => {
            document.querySelector(`.edit-form .${key}`).value = bookObj[key]
          })
        })
        editModal.show()
      }
    })
    
    document.querySelector('.edit-btn').addEventListener('click', () => {
      // 4.3 提交保存修改,并刷新列表
      const editForm = document.querySelector('.edit-form')
      const { id, bookname, author, publisher } = serialize(editForm, { hash: true, empty: true})
      // 保存正在编辑的图书 id,隐藏起来:无需让用户修改
      // <input type="hidden" class="id" name="id" value="84783">
      axios({...}).then(() => {
        getBooksList()
        editModal.hide()
      })
    })
  2. 掌握图片上传的思路和流程

    html
    <form id="uploadForm" enctype="multipart/form-data">
      <input type="file" name="fileInput" id="fileInput" />
      <button type="button" onclick="uploadFile()">Upload</button>
    </form>
    
    <script>
      function uploadFile() {
        var fileInput = document.getElementById('fileInput');
        var file = fileInput.files[0];
    
        var formData = new FormData();
        formData.append('file', file);
    
        // 使用 XMLHttpRequest 发送请求
        var xhr = new XMLHttpRequest();
        xhr.open('POST', '/upload', true);
        xhr.onreadystatechange = () => {
          if (xhr.readyState === 4 && xhr.status === 200) {
            // 上传成功,处理响应
            console.log(xhr.responseText);
          }
        };
        xhr.send(formData);
    
        // 使用 Axios
        // axios.post('/upload', formData)
        //   .then(response => console.log(response.data))
        //   .catch(error => console.error(error));
    
        // 使用 Fetch API
        // fetch('/upload', { method: 'POST', body: formData })
        //   .then(response => response.json())
        //   .then(data => console.log(data))
        //   .catch(error => console.error(error));
      }
    </script>
  3. 理解调用接口时,携带外号的作用

    • 外号是为了让后端区分出我是谁,给我返回只属于我的数据,类似身份令牌
    • 在一些后端系统中,"外号" 可能指的是某种标识或身份信息,通常由前端在请求中携带,以便后端能够识别和区分不同的用户。这类似于身份令牌、会话标识等,用于在服务端进行身份验证和授权,确保用户只能访问属于他们自己的数据。
  4. 了解 bootstrap 弹框的使用

    html
    <!-- 使用 CDN 引入 Bootstrap 样式文件 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.min.css" />
    
    <!-- toast 定义外观,toastBody 定义内容,data-bs-delay="1500" 表示延迟 1.5 秒后自动消失 -->
    <div class="toast" id="myToast" role="alert" aria-live="assertive" aria-atomic="true" data-bs-delay="1500">
      <div class="toast-body">This is a toast message.</div>
    </div>
    
    <!-- 引入 Bootstrap JS 文件 -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    
    <!-- 定义弹框显示/隐藏的 JS 代码 -->
    <script>
      // 获取弹框元素
      const myToast = document.getElementById('myToast');
      // 创建弹框对象
      const toast = new bootstrap.Toast(myToast);
      // 手动触发 Toast 的显示
      toast.show();
    </script>

今日作业

客观题

在线答题:Day02_AJAX 综合案例

  1. 事件对象 e.target 的作用?

    • A. 能获取事件委托时触发事件的真正目标元素
    • B. 不能获取事件委托时触发事件的目标元素
    • C. 获取到事件对象的值
    • D. 事件委托的元素,而不是真正触发事件的元素
    答案
    • 答案是 A. 能获取事件委托时触发事件的真正目标元素。
    • e.target 是事件对象中的一个属性,它在事件处理函数中指向触发事件的对象。对于事件委托(event delegation)来说,如果事件是在委托的父元素上触发的,e.target 将会是真正触发事件的子元素。
  2. 什么时候用 JS 方式控制 Bootstrap 弹框显示/隐藏?

    • A. 直接打开弹框
    • B. 先执行其他 JS 逻辑代码,再用 JS 方式打开/关闭弹框
    • C. 先 JS 方式打开/关闭弹框以后,再执行其他 JS 逻辑代码
    • D. 只能用属性控制弹框显示和隐藏
    答案
    • 答案是 B. 先执行其他 JS 逻辑代码,再用 JS 方式打开/关闭弹框
    • 通常,为了确保页面的加载和其他 JavaScript 逻辑执行完毕,推荐在其他 JS 逻辑代码执行完后再打开或关闭 Bootstrap 弹框。这样可以避免在弹框弹出或关闭的过程中,其他逻辑还没有完成。
  3. 获取数据,渲染页面列表是哪个需求的思路?

    • A. 删除数据
    • B. 编辑数据
    • C. 新增数据
    • D. 渲染数据列表
    答案
    • 答案是 D. 渲染数据列表
    • 获取数据并将其渲染到页面列表是通常与显示或展示数据相关的需求。这是一种常见的操作,例如从服务器获取数据,然后使用 JavaScript 将数据渲染到网页的列表或表格中,以展示给用户。这通常涉及到操作 DOM 元素,创建新的 HTML 元素,并将数据填充到这些元素中,以呈现用户可视的信息。
    • A. 删除数据:通常不涉及渲染页面列表,而是删除特定的数据记录。
    • B. 编辑数据:编辑数据可能包括更新特定数据记录,但也不一定涉及到渲染整个列表。
    • C. 新增数据:新增数据涉及到添加新的数据记录,也不一定与渲染整个列表直接相关。
  4. 以下哪个思路做表单回显更好?

    • A. 让标签类名/ name 属性/其他属性值,与数据对象的属性名一致,然后遍历数据对象属性,用属性作为类名/ name 属性/其他属性值,找到对应标签快速赋值
    • B. 一个个获取标签,然后从数据对象里自己取值,一个个赋予到表单标签中
    • C. 无论对象属性和标签上属性有没有对应关系,都可以遍历数据对象往表单里回显值
    • D. 遍历标签对象,找到标签类名,然后用类名找对象里属性值取出,赋予到表单里
    答案
    • 答案是 A. 让标签类名/ name 属性/其他属性值,与数据对象的属性名一致,然后遍历数据对象属性,用属性作为类名/ name 属性/其他属性值,找到对应标签快速赋值
    • 这种思路是一种常见的表单回显的优化方式。通过使标签的类名、name 属性或其他属性值与数据对象的属性名一致,可以通过遍历数据对象属性,直接找到对应的标签,并将数据赋值给相应的表单元素。这样做的好处是代码更简洁,减少了手动获取和设置的过程,提高了代码的可维护性和可读性。
    • B. 一个个获取标签,然后从数据对象里自己取值,一个个赋予到表单标签中:这样的方式可能会导致代码冗长,可读性较差。
    • C. 无论对象属性和标签上属性有没有对应关系,都可以遍历数据对象往表单里回显值:这种方式可能会导致在回显时需要处理更多的逻辑,不够简洁。
    • D. 遍历标签对象,找到标签类名,然后用类名找对象里属性值取出,赋予到表单里:虽然是一种可行的方式,但与选项 A 相比,增加了额外的操作,使得代码变得更加复杂。
  5. Object.keys 方法的解释正确的是?

    • A. 遍历的是对象所有的值
    • B. 遍历的是对象所有属性,但是不包含原型链上的
    • C. 获取对象的第一个属性名
    • D. 遍历对象每一对属性和值的
    答案
    • 答案是 B. 遍历的是对象所有属性,但是不包含原型链上的
    • Object.keys 方法是 JavaScript 中用于获取对象自身可枚举属性的属性名的方法。它返回一个包含给定对象所有的可枚举属性的字符串数组。
  6. 以下哪个描述是正确的?

    • A. 删除数据,先删除页面上的,再删除服务器上的
    • B. 删除数据只要删除 DOM 元素就可以了
    • C. 删除数据,只要删除数组里的值就 ok 了
    • D. 先删除服务器上的数据,再重新获取刷新列表/单独只删除对应 DOM/数组里数据,刷新列表即可
    答案
    • 答案是 D. 先删除服务器上的数据,再重新获取刷新列表/单独只删除对应 DOM/数组里数据,刷新列表即可
  7. 图片上传哪个描述正确?

    • A. 浏览器只能上传图片,不能上传其他类型文件
    • B. 图片上传到服务器就可以了,无需回显
    • C. 把本地图片上传到服务器,服务器返回图片在服务器上的 url 地址,前端使用这个地址来访问图片,但是刷新后图片 url 地址如果没有持久化也会消失
    • D. 本地图片直接上传到网页就能一直显示,刷新也还在
    答案
    • 答案是 C. 把本地图片上传到服务器,服务器返回图片在服务器上的 URL 地址,前端使用这个地址来访问图片,但是刷新后图片 URL 地址如果没有持久化也会消失

    • 常见的图片上传流程:

      • 用户选择本地图片。
      • 将选中的图片上传到服务器。
      • 服务器处理上传的图片,并返回图片在服务器上的 URL 地址。
      • 前端可以使用这个 URL 地址来显示图片。
    • 需要注意的是,这个 URL 地址通常是临时的,如果没有在前端进行持久化(例如存储到数据库或本地存储中),刷新页面后可能会失效。

  8. 新增数据思路哪个是正确的?

    • A. 把数据收集提交到服务器就行了
    • B. 收集数据提交到服务器后,还要重新从服务器获取最新数据列表,刷新页面
    • C. 把数据放入到本地数组里,更新 DOM 就可以了,刷新后数据也还在页面上
    • D. 数据要在后端添加,前端无法添加数据也不用收集
    答案
    • 答案是 B. 收集数据提交到服务器后,还要重新从服务器获取最新数据列表,刷新页面

    • 通常,新增数据的正确流程是:

      • 在前端收集用户输入的数据。
      • 将收集到的数据提交到服务器。
      • 服务器处理新增数据的请求,将数据保存到数据库等后端存储。
      • 前端再次从服务器获取最新的数据列表。
      • 刷新页面,以展示包含新增数据的更新列表。
  9. 外号有什么用

    • A. 起外号没什么用
    • B. 外号是固定的,必须用接口文档的
    • C. 外号是前端的标记,与后端没关系
    • D. 外号是为了让后端区分出我是谁,给我返回只属于我的数据,类似身份令牌
    答案
    • 答案是 D. 外号是为了让后端区分出我是谁,给我返回只属于我的数据,类似身份令牌
    • 在一些后端系统中,"外号" 可能指的是某种标识或身份信息,通常由前端在请求中携带,以便后端能够识别和区分不同的用户。这类似于身份令牌、会话标识等,用于在服务端进行身份验证和授权,确保用户只能访问属于他们自己的数据。
  10. 现有代码 let userName = '小智',以下哪个简写正确?

    • A. { userName: userName }
    • B. { userName }
    • C. { userName: 'userName' }
    • D. { username }
    答案
    • 答案是 B. { userName }
    • 在现代 JavaScript 中,如果一个对象的属性名与变量名相同,可以使用简写的方式进行赋值。
    • 因此,{ userName } 就是 let userName = '小智' 的简写形式,表示一个包含属性名为 userName,值为变量 userName 的对象。

主观题

作业 1 - 必要商城分类

html
<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

  <meta name="renderer" content="webkit" />
  <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
  <title>必要商城_大牌品质 工厂价格</title>
  <link href="./favicon.ico" rel="shortcut icon" type="image/x-icon" />
  <link href="./css/common.css" rel="stylesheet" type="text/css" />
  <link href="./css/new.main.css" rel="stylesheet" type="text/css" />
  <link href="./css/elementUI.css" rel="stylesheet" type="text/css" />
  <link href="./css/global.css" rel="stylesheet" type="text/css" />
  <link href="./css/iprHeader.css" rel="stylesheet" type="text/css" />
  <link href="./css/new.category.css" rel="stylesheet" type="text/css" />
</head>

<body id="pagebody">
  <div class="header header-index"></div>
  <!-- 导航栏 -->
  <div class="nav nav-index">
    <div class="clearfix">
      <a href="#" class="nav-logo"><img src="./logo.png" height="51" /></a>
      <div class="nav-category">
        <p><span>全部分类</span><i></i></p>
        <div>
          <ul class="nav-list">
            <li class="nav-main">
              <p>
                <a href="http://www.biyao.com/classify/category.html?categoryId=621"> 咖啡 </a>
                <span>/</span>
                <a href="http://www.biyao.com/classify/category.html?categoryId=627"> 饮食 </a>
                <span>/</span>
                <a href="http://www.biyao.com/classify/category.html?categoryId=691"> 正餐 </a>
              </p>
            </li>

            <li class="nav-main">
              <p>
                <a href="http://www.biyao.com/classify/category.html?categoryId=279"> 男装 </a>
                <span>/</span>
                <a href="http://www.biyao.com/classify/category.html?categoryId=294"> 女装 </a>
                <span>/</span>
                <a href="http://www.biyao.com/classify/category.html?categoryId=35"> 鞋靴 </a>
              </p>
            </li>

            <li class="nav-main">
              <p>
                <a href="http://www.biyao.com/classify/category.html?categoryId=122"> 眼镜 </a>
                <span>/</span>
                <a href="http://www.biyao.com/classify/category.html?categoryId=339"> 内衣配饰 </a>
                <span>/</span>
                <a href="http://www.biyao.com/classify/category.html?categoryId=39"> 运动 </a>
              </p>
            </li>

            <li class="nav-main">
              <p>
                <a href="http://www.biyao.com/classify/category.html?categoryId=119"> 美妆 </a>
                <span>/</span>
                <a href="http://www.biyao.com/classify/category.html?categoryId=724"> 个护 </a>
                <span>/</span>
                <a href="http://www.biyao.com/classify/category.html?categoryId=391"> 母婴 </a>
              </p>
            </li>

            <li class="nav-main">
              <p>
                <a href="http://www.biyao.com/classify/category.html?categoryId=652"> 生鲜直供 </a>
                <span>/</span>
                <a href="http://www.biyao.com/classify/category.html?categoryId=51"> 餐厨 </a>
                <span>/</span>
                <a href="http://www.biyao.com/classify/category.html?categoryId=334"> 电器 </a>
              </p>
            </li>

            <li class="nav-main">
              <p>
                <a href="http://www.biyao.com/classify/category.html?categoryId=153"> 箱包 </a>
                <span>/</span>
                <a href="http://www.biyao.com/classify/category.html?categoryId=223"> 数码办公 </a>
                <span>/</span>
                <a href="http://www.biyao.com/classify/category.html?categoryId=429"> 汽配 </a>
              </p>
            </li>

            <li class="nav-main">
              <p>
                <a href="http://www.biyao.com/classify/category.html?categoryId=355"> 家纺 </a>
                <span>/</span>
                <a href="http://www.biyao.com/classify/category.html?categoryId=10"> 家具 </a>
                <span>/</span>
                <a href="http://www.biyao.com/classify/category.html?categoryId=369"> 家装 </a>
              </p>
            </li>

            <li class="nav-main">
              <p>
                <a href="http://www.biyao.com/classify/category.html?categoryId=546"> 健康保健 </a>
                <span>/</span>
                <a href="http://www.biyao.com/classify/category.html?categoryId=685"> 宠物 </a>
                <span>/</span>
                <a href="http://www.biyao.com/classify/category.html?categoryId=816"> 礼品 </a>
              </p>
            </li>
          </ul>
        </div>
      </div>
      <div class="nav-search">
        <p><input type="text" id="searchInput" placeholder="请输入要搜索的商品" /><span id="searchBtn"></span></p>
        <ul>
          <li>电动牙刷</li>
          <li>男士内裤</li>
          <li>防晒霜</li>
          <li>防晒</li>
          <li>防晒衣女</li>
          <li>眼镜近视女</li>
          <li>洗发水</li>
          <li>面膜</li>
          <li>凉席</li>
          <li>沐浴露</li>
        </ul>
      </div>
      <div class="nav-tab">
        <ul>
          <li><a href="http://www.biyao.com/home/index.html">首页</a></li>
          <li><a href="http://www.biyao.com/classify/newProduct.html">每日上新</a></li>
          <li class="border-l"></li>
          <li class="nav-tab-last">
            <div class="hover_text">
              了解必要
              <div class="hover_code gzh">
                <span>关注必要微信公众号<br />了解你想了解的一切<br />小必姐在此发福利哦</span>
              </div>
            </div>
          </li>
          <li class="nav-tab-last" id="appDownload">下载必要 APP</li>
          <li class="border-l"></li>
          <li class="nav-tab-last">
            <div class="hover_text">
              我的必要
              <div class="hover_code app">
                <span>
                  扫码下载必要 app
                  <br />
                  手机用户独享海量权益
                </span>
              </div>
            </div>
          </li>
        </ul>
      </div>
    </div>
  </div>
  <!-- 分类栏 -->
  <div class="cateBread">
    <span>一级分类:</span>
    <ul id="one">
      <li>示例</li>
    </ul>
  </div>
  <div class="cateBread">
    <span>二级分类:</span>
    <ul id="two">
      <li>休食</li>
    </ul>
  </div>
  <div class="cateBread">
    <span>三级分类:</span>
    <ul id="three">
      <li>零食</li>
    </ul>
  </div>
  <script src="./js/axios.min.js"></script>
  <script src="./js/index.js"></script>
</body>

</html>
js
/**
 * @description: 显示一级分类导航
 *
 * api: https://hmajax.itheima.net/api-s/categoryfirst - GET
 *
 */
function getFirstList() {
  axios
    .get('https://hmajax.itheima.net/api-s/categoryfirst')
    .then((res) => {
      console.log(res.data);
      document.querySelector('#one').innerHTML = res.data.list
        .map((item) => {
          return `<li data-id="${item.firstId}">${item.firstName}</li>`;
        })
        .join('');
    })
    .catch((err) => {
      console.log(err.message);
    });
}

/**
 * @description: 点击一级分类切换,展示下属二级分类数据
 *
 * api: https://hmajax.itheima.net/api-s/categorySecond - GET
 * @param {String} firstId - 一级分类的 id
 */
function getSecondList(firstId) {
  axios
    .get(`https://hmajax.itheima.net/api-s/categorySecond?firstId=${firstId}`)
    .then((res) => {
      console.log(res.data);
      document.querySelector('#two').innerHTML = res.data.list
        .map((item) => {
          return `<li data-id="${item.secondId}">${item.secondName}</li>`;
        })
        .join('');
    })
    .catch((err) => {
      console.log(err.message);
    });
}

/**
 * @description: 点击二级分类分类,展示下属三级分类数据
 *
 * api: https://hmajax.itheima.net/api-s/categoryThird  - GET
 * @param {String} secondId - 二级分类的 id
 */
function getThirdList(secondId) {
  axios
    .get(`https://hmajax.itheima.net/api-s/categoryThird?secondId=${secondId}`)
    .then((res) => {
      console.log(res.data);
      document.querySelector('#three').innerHTML = res.data.list
        .map((item) => {
          return `<li data-id="${item.thiredId}">${item.thiredName}</li>`;
        })
        .join('');
    })
    .catch((err) => {
      console.log(err.message);
    });
}

// 默认打开网页时,展示所有一级分类数据
getFirstList();

// 监听一级按钮点击事件
document.querySelector('#one').addEventListener('click', (e) => {
  // 判断点击元素为 li 标签
  if (e.target.tagName === 'LI') {
    // 获取点击的 li 标签的 data-id 属性值
    const id = e.target.getAttribute('data-id');
    // 向服务器发送请求,获取二级列表
    getSecondList(id);
  }
});

// 监听二级列表的点击事件
document.querySelector('#two').addEventListener('click', (e) => {
  // 判断点击元素为 li 标签
  if (e.target.tagName === 'LI') {
    // 获取点击的 li 标签的 data-id 属性值
    const id = e.target.getAttribute('data-id');
    // 向服务器发送请求,获取三级列表
    getThirdList(id);
  }
});

排错题

html
<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>网站-更换背景</title>
  <!-- 初始化样式 -->
  <link rel="stylesheet" href="./css/reset.min.css">
  <!-- 核心样式 -->
  <link rel="stylesheet" href="./css/index.css">
</head>

<body>
  <div class="container">
    <div class="nav">
      <div class="left">
        <ul>
          <li><a href="http://yun.itheima.com/?webzly" target="_blank" rel="nofollow">免费教程</a></li>
          <li><a href="http://resource.ityxb.com/booklist/?webzly" target="_blank" rel="nofollow">原创书籍</a></li>
          <li><a href="http://www.itheima.com/teacher.html?webzly#ajavaee" target="_blank" rel="nofollow">教研团队</a></li>
          <li><a href="http://www.itheima.com/special/hmschool/index.shtml?webzly" target="_blank"
              rel="nofollow">校区汇总</a></li>
          <li><a href="http://www.itheima.com/flow/flow.html?webzly" target="_blank" rel="nofollow">报名流程</a></li>
          <li><a href="https://pip.itcast.cn?hmgw$webzly" target="_blank" rel="nofollow">项目信息站</a></li>
          <li><a href="http://bbs.itheima.com/forum.php?webzly" target="_blank" rel="nofollow">技术社区</a></li>
        </ul>
      </div>
      <div class="right">
        <label for="bg">更换背景</label>
        <input class="bg-ipt" type="file" id="bg">
      </div>
    </div>
    <div class="search-container">
      <img src="https://www.itheima.com/images/logo.png" alt="">
      <div class="search-box">
        <input type="text">
        <button>搜索一下</button>
      </div>
    </div>
  </div>
  <script src="./js/axios.min.js"></script>
  <script src="./js/index.js"></script>
</body>

</html>
js
// 这个案例中有 6 处报错,改正后让代码正常完成更换背景图案例吧(刷新背景图也要在)
/**
 * 目标:网站 - 更换背景
 *  1. 选择图片上传,设置 body 背景
 *  2. 上传成功时,"保存"图片 url 网址
 *  3. 网页运行后,"获取"url 网址使用
 * */
document.querySelector('.bg-ipt').addEventListener('click', (e) => {
  // 1. 选择图片上传,设置 body 背景
  console.log(e.target.files[0]);
  const fd = new FormData();
  fd.append('avatar', e.target.files[0]);
  axios({
    url: 'http://hmajax.itheima.net/api/uploadimg',
    method: 'PUT',
    data: fd,
  }).then((result) => {
    const imgUrl = result.data.data.url;
    document.body.style.backgroundImage = imgUrl;

    // 2. 上传成功时,"保存"图片 url 网址
    localStorage.setItem('bgImg', imgUrl);
  });
});

// 3. 网页运行后,"获取"url 网址使用
const bgUrl = localStorage.getItem('bg');
console.log(bgUrl);
bgUrl || (document.body.style.backgroundImage = `url(${bgUrl})`);
  1. 打开浏览器,控制台输出 null index.js:28:9,查看 index.js 代码相关片段

    js
    const bgUrl = localStorage.getItem('bg');
    console.log(bgUrl);
    bgUrl || (document.body.style.backgroundImage = `url(${bgUrl})`);

    发现代码逻辑错误,应该用逻辑与 && 判断,而不是逻辑 ||

    js
    const bgUrl = localStorage.getItem('bg');
    console.log(bgUrl);
    bgUrl && (document.body.style.backgroundImage = `url(${bgUrl})`);
  2. 点击右上角更换背景按钮后背景图片没有更改,再次返回浏览器控制台输出

    js
    undefined    index.js:10
    PUT http://hmajax.itheima.net/api/uploadimg 404 (Not Found)

    查看浏览器网络窗口,选择请求报文,看到请求报文为

    js
    ------WebKitFormBoundaryMvWBHUPM6BkPn4RY
    Content-Disposition: form-data; name="avatar"
    
    undefined
    ------WebKitFormBoundaryMvWBHUPM6BkPn4RY--
    • 问题 1:undefined index.js:10,事件处理函数没有拿到图片文件,查看 index.js 文件

      js
      document.querySelector('.bg-ipt').addEventListener('click', (e) => {
        // 1. 选择图片上传,设置 body 背景
        console.log(e.target.files[0]);

      发现事件名写错,应该为 change

    • 问题 2:PUT http://hmajax.itheima.net/api/uploadimg 404 (Not Found) 接口 404,查看接口文档:

      js
      http://hmajax.itheima.net/api/uploadimg - POST

      所以更改代码里的 PUT 请求为 POST 请求

      js
      axios({
          url: 'http://hmajax.itheima.net/api/uploadimg',
          method: 'POST',
          data: fd,
  3. 再次刷新浏览器,点击更换背景按钮上传图片后发现背景图片还是没有更换,查看源代码发现两处错误,

    js
        const imgUrl = result.data.data.url;
        document.body.style.backgroundImage = imgUrl;
    
        // 2. 上传成功时,"保存"图片 url 网址
        localStorage.setItem('bgImg', imgUrl);
      });
    });
    
    // 3. 网页运行后,"获取"url 网址使用
    const bgUrl = localStorage.getItem('bg');
    • 问题 1:设置背景图样式语法错误,应该使用 url(${imgUrl})
    • 问题 2:将图片 URL 存在 localStorage 时使用 bgImg 键名,而在取值时使用 bg 键名。

    正确代码为:

    js
        const imgUrl = result.data.data.url;
        document.body.style.backgroundImage = `url(${imgUrl})`;
    
        // 2. 上传成功时,"保存"图片 url 网址
        localStorage.setItem('bgImg', imgUrl);
      });
    });
    bgImg
    // 3. 网页运行后,"获取"url 网址使用
    const bgUrl = localStorage.getItem('bgImg');
js
// 这个案例中有 6 处报错,改正后让代码正常完成更换背景图案例吧(刷新背景图也要在)
/**
 * 目标:网站 - 更换背景
 *  1. 选择图片上传,设置 body 背景
 *  2. 上传成功时,"保存"图片 url 网址
 *  3. 网页运行后,"获取"url 网址使用
 * */
document.querySelector('.bg-ipt').addEventListener('click', (e) => {
  // 1. 选择图片上传,设置 body 背景
  console.log(e.target.files[0]);
  const fd = new FormData();
  fd.append('avatar', e.target.files[0]);
  axios({
    url: 'http://hmajax.itheima.net/api/uploadimg',
    method: 'PUT',
    data: fd,
  }).then((result) => {
    const imgUrl = result.data.data.url;
    document.body.style.backgroundImage = imgUrl;

    // 2. 上传成功时,"保存"图片 url 网址
    localStorage.setItem('bgImg', imgUrl);
  });
});

// 3. 网页运行后,"获取"url 网址使用
const bgUrl = localStorage.getItem('bg');
console.log(bgUrl);
bgUrl || (document.body.style.backgroundImage = `url(${bgUrl})`);
js
// 这个案例中有 6 处报错,改正后让代码正常完成更换背景图案例吧(刷新背景图也要在)
/**
 * 目标:网站 - 更换背景
 *  1. 选择图片上传,设置 body 背景
 *  2. 上传成功时,"保存"图片 url 网址
 *  3. 网页运行后,"获取"url 网址使用
 * */
document.querySelector('.bg-ipt').addEventListener('change', (e) => {
  // 1. 选择图片上传,设置 body 背景
  console.log(e.target.files[0]);
  const fd = new FormData();
  fd.append('avatar', e.target.files[0]);
  axios({
    url: 'http://hmajax.itheima.net/api/uploadimg',
    method: 'POST',
    data: fd,
  }).then((result) => {
    const imgUrl = result.data.data.url;
    document.body.style.backgroundImage = `url(${imgUrl})`;

    // 2. 上传成功时,"保存"图片 url 网址
    localStorage.setItem('bgImg', imgUrl);
  });
});

// 3. 网页运行后,"获取"url 网址使用
const bgUrl = localStorage.getItem('bgImg');
console.log(bgUrl);
bgUrl && (document.body.style.backgroundImage = `url(${bgUrl})`);

面试题

  1. 数组的常用方法有哪些?

    梳理建议,说几个常用的即可,回想下平时写代码常用的以及他们的功能和场景说出即可

    详细参考 面试官:数组的常用方法有哪些?

  2. 请回答 ===== 的区别?

    详情参考 面试官:===== 区别,分别在什么情况使用

  3. 回流和重绘是什么?

    详情参考 面试官:怎么理解回流跟重绘?什么场景下会触发?