前端AJAX入门到实战全套教程,包含学前端框架必会的(ajax+node.js+webpack+git)

237 阅读17分钟

来源:黑马程序员前端AJAX入门到实战全套教程,包含学前端框架必会的(ajax+node.js+webpack+git),一套全覆盖

Ajax

一. Ajax入门

1. AJAX 概念和 axios 使用

什么是 AJAX ? mdn
  • AJAX 是异步的 JavaScript 和 XML(Asynchronous JavaScript And XML)
  • 使用浏览器的 XMLHttpRequest 对象 与服务器通信
  • 它可以使用 JSON、XML、HTML 和text 文本等格式发送和接收数据。AJAX最吸引人的就是它的“异步”特性,也就是是它可以在不重新刷新页面的情况下与服务器通信,交换数据,或更新页面。

简单来说,AJAX是浏览器与服务器进行数据通信的技术。

什么是服务器?
  • 可以暂时理解为提供数据的一台电脑
为何学 AJAX ?
  • 以前我们的数据都是写在代码里固定的, 无法随时变化
  • 现在我们的数据可以从服务器上进行获取,让数据变活
怎么学 AJAX ?
  1. 先使用一个第三方库叫 axios,与服务器通信

    • 基于 XMLHttpRequest 封装、代码简单、月下载量在 14 亿次,让我们有更多精力关注在与服务器通信上
    • 后续 Vue,React项目中都会用到 axios
  2. 再学习 XMLHttpRequest 对象的使用,了解 AJAX 底层原理

axios 使用
  1. 引入 axios.js

    axios.js文件链接: cdn.jsdelivr.net/npm/axios/d…

  2. 使用 axios 函数

    • 传入配置对象
    • 再用 .then 回调函数接收结果,并做后续处理
    axios({
      url: '目标资源地址'
    }).then((result) => {
      // 对服务器返回的数据做后续处理
    })
    

    注意:请求的 url 地址, 就是标记资源的网址

2. 认识 URL

了解 URL 的组成和作用

1. 为什么要认识 URL ? mdn

知道作用和组成,方便与后端人员沟通

2. 什么是 URL ?

统一资源定位符(Uniform Resource Locator,简写:URL),或称定位地址、URL地址,俗称网页地址,简称网址用于定位访问网络上的资源(资源指的是:网页,图片,数据,视频,音频等等)

3. URL 的组成?
  • 协议,域名,资源路径(URL 组成有很多部分,我们先掌握这3个重要的部分即可)

image.png

协议

http 协议:超文本传输协议,规定了浏览器和服务器之间传输数据的格式(而格式具体有哪些稍后我们就会学到)

协议范围:http、https...

域名

域名:标记服务器在互联网中的方位

网络中有很多服务器,你想访问哪一台,就需要知道它的域名才可以

image.png

资源路径

资源路径:一个服务器内有多个资源,用于标记资源在服务器下的具体位置

image.png

3. URL 查询参数

通过URL传递查询参数,获取匹配的数据

查询参数定义:浏览器提供给服务器的额外信息,让服务器返回浏览器想要的数据

语法:http://xxxx.com/xxx/xxx?参数名1=值1&参数名2=值2

image.png

axios 如何携带查询参数?

  • 使用 params 选项即可

    注意:axios 在运行时把参数名和值,会拼接到 url?参数名=值

    axios({
      url: '目标资源地址',
      params: {
        参数名: 值
      }
    }).then(result => {
      // 对服务器返回的数据做后续处理
    })
    

案例-查询-地区列表 代码如下:

/*
  获取地区列表: http://hmajax.itheima.net/api/area
  查询参数:
    pname: 省份或直辖市名字
    cname: 城市名字
*/
// 目标: 根据省份和城市名字, 查询地区列表
// 1. 查询按钮-点击事件
document.querySelector('.sel-btn').addEventListener('click', () => {
    // 2. 获取省份和城市名字
    let pname = document.querySelector('.province').value
    let cname = document.querySelector('.city').value

    // 3. 基于axios请求地区列表数据
    axios({
        url: 'http://hmajax.itheima.net/api/area',
        params: {
            pname,
            cname
        }
    }).then(result => {
        // console.log(result)
        // 4. 把数据转li标签插入到页面上
        let list = result.data.list
        console.log(list)
        let theLi = list.map(areaName => `<li class="list-group-item">${areaName}</li>`).join('')
        console.log(theLi)
        document.querySelector('.list-group').innerHTML = theLi
    })
})

4. 常用请求方法和数据提交

掌握如何向服务器提交数据,而不单单是获取数据

想要提交数据,先来了解什么是请求方法

请求方法

请求方法:对服务器资源,要执行的操作。

请求方法是一些固定单词的英文,例如:GET,POST,PUT,DELETE,PATCH(这些都是http协议规定的),每个单词对应一种对服务器资源要执行的操作。

image.png

前面我们获取数据其实用的就是GET请求方法,但是axios内部设置了默认请求方法就是GET,我们就没有写

提交数据需要使用POST请求方法

  1. 什么时候进行数据提交呢?

    场景:当数据需要在服务器上保存

    例如:多端要查看同一份订单数据,或者使用同一个账号进行登录,那订单/用户名+密码,就需要保存在服务器上,随时随地进行访问

  2. axios 如何提交数据到服务器呢?

    需要学习,method 和 data 这2个新的选项了

axios 请求配置
  • url:请求的 URL 网址

  • method:请求的方法,GET可以省略(不区分大小写)

  • data:提交数据

    axios({
      url: '目标资源地址',
      method: '请求方法',
      data: {
        参数名: 值
      }
    }).then(result => {
      // 对服务器返回的数据做后续处理
    })
    
  • 案例:注册账号,提交用户名和密码到服务器保存

    注册用户 URL 网址:hmajax.itheima.net/api/registe…

    请求方法:POST

    参数名:

    username:用户名(要求中英文和数字组成,最少8位)

    password:密码(最少6位)

代码如下:

/*
  注册用户:http://hmajax.itheima.net/api/register
  请求方法:POST
  参数名:
    username:用户名(中英文和数字组成,最少8位)
    password:密码  (最少6位)

  目标:点击按钮,通过axios提交用户和密码,完成注册
*/
document.querySelector('.btn').addEventListener('click', () => {
  axios({
    url: 'http://hmajax.itheima.net/api/register',
    method: 'POST',
    data: {
      username: 'itheima007',
      password: '7654321'
    }
  })
})

5. axios 错误处理

掌握接收 axios 响应错误信息的处理语法

  • 场景:再次注册相同的账号,会遇到报错信息

  • 处理:用更直观的方式,给普通用户展示错误信息

    因为,普通用户不会去控制台里看错误信息,我们要编写代码拿到错误并展示给用户在页面上

  • 语法:在 then 方法的后面,通过点语法调用 catch 方法,传入回调函数并定义形参

    axios({
      // ...请求选项
    }).then(result => {
      // 处理成功数据
    }).catch(error => {
      // 处理失败错误
    })
    

案例:

//注册案例,重复注册时通过弹框提示用户错误原因
document.querySelector('.btn').addEventListener('click', () => {
    axios({
      url: 'http://hmajax.itheima.net/api/register',
      method: 'post',
      data: {
        username: 'itheima007',
        password: '7654321'
      }
    }).then(result => {
      // 成功
      console.log(result)
    }).catch(error => {
      // 失败
      // 处理错误信息
      console.log(error)
      console.log(error.response.data.message)
      alert(error.response.data.message)
    })
})

6. HTTP 协议-报文

HTTP 协议:规定了浏览器发送及服务器返回内容的格式

请求报文

了解 HTTP 协议中,请求报文的组成和作用

请求报文:浏览器按照 HTTP 协议要求的格式,发送给服务器的内容

请求报文的组成部分有:

  • 请求行:请求方法,URL,协议版本
  • 请求头:以键值对的格式携带的附加信息,比如:Content-Type(指定了本次传递的内容类型)
  • 空行:分割请求头,空行之后的是发送给服务器的资源
  • 请求体:发送的资源

image.png

通过 Chrome 的网络面板如何查看请求体?

image.png

错误排查

需求:通过请求报文排查错误原因,并修复

学习了查看请求报文有什么用呢?

  • 可以用来确认我们代码发送的请求数据是否真的正确
响应报文

响应报文:服务器按照http协议要求的格式,返回给浏览器的内容

响应报文的组成:

  • 响应行(状态行):协议,HTTP响应状态码,状态信息
  • 响应头:以键值对的格式携带的附加信息,比如:Content-Type(告诉浏览器,本次返回的内容类型)
  • 空行:分割响应头,控制之后的是服务器返回的资源
  • 响应体:返回的资源

image.png

HTTP 响应状态码

HTTP 响应状态码:用来表明请求是否成功完成

例如:404(客户端要找的资源,在服务器上不存在)

image.png

7. 接口文档

接口文档:描述接口的文章(一般是后端工程师,编写和提供)

接口:指使用 AJAX 和 服务器通讯时,使用的 URL,请求方法,以及参数,例如:AJAX阶段接口文档

8. form-serialize 插件

  • 作用:快速收集表单元素的值

  • 我们前面收集表单元素的值,是一个个标签获取的,如果一套表单里有很多很多表单元素,如何一次性快速收集出来呢?

    使用 form-serialize 插件提供的 serialize 函数就可以办到

  • form-serialize 插件语法:

    1. 引入 form-serialize 插件到自己网页中

    2. 使用 serialize 函数

      serialize(参数1, 参数2)

      • 参数1:要获取的 form 表单标签对象(要求表单元素需要有 name 属性-用来作为收集的数据中属性名)

      • 参数2:配置对象

        • hash 设置获取数据结构:
          • true - 收集出来的是一个 JS 对象结构
          • false - 收集出来的是一个查询字符串格式
        • empty 设置是否获取空值:
          • true - 收集空值
          • false - 不收集空值

需求:收集登录表单里用户名和密码

对应代码:

<!DOCTYPE html>
<html lang="en">

<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>form-serialize插件使用</title>
</head>

<body>
  <form action="javascript:;" class="example-form">
    <input type="text" name="username">
    <br>
    <input type="text" name="password">
    <br>
    <input type="button" class="btn" value="提交">
  </form>
  <!-- 
    目标:在点击提交时,使用form-serialize插件,快速收集表单元素值
    1. 把插件引入到自己网页中
  -->
  <script src="./lib/form-serialize.js"></script>
  <script>
    document.querySelector('.btn').addEventListener('click', () => {
      /**
       * 2. 使用serialize函数,快速收集表单元素的值
       * 参数1:要获取哪个表单的数据
       *  表单元素设置name属性,值会作为对象的属性名
       *  建议name属性的值,最好和接口文档参数名一致
       * 参数2:配置对象
       *  hash 设置获取数据结构
       *    - true:JS对象(推荐)一般请求体里提交给服务器
       *    - false: 查询字符串
       *  empty 设置是否获取空值
       *    - true: 获取空值(推荐)数据结构和标签结构一致
       *    - false:不获取空值
      */
      const form = document.querySelector('.example-form')
      const data = serialize(form, { hash: true, empty: true })
      // const data = serialize(form, { hash: false, empty: true })
      // const data = serialize(form, { hash: true, empty: false })
      console.log(data)
    })
  </script>
</body>

</html>

案例-用户登录-form-serialize

  1. 基于模板代码,使用 form-serialize 插件来收集用户名和密码

  2. 在原来的代码基础上修改即可

    1. 先引入插件

      <!-- 3.1 引入插件 -->
      <script src="./lib/form-serialize.js"></script>
      
    2. 然后修改代码

      // 3.2 使用serialize函数,收集登录表单里用户名和密码
      const form = document.querySelector('.login-form')
      const data = serialize(form, { hash: true, empty: true })
      console.log(data)
      // {username: 'itheima007', password: '7654321'}
      const { username, password } = data
      

9. 小结

  1. AJAX 有什么用?

    答案
    • 浏览器和服务器之间通信,动态数据交互
  2. AJAX 如何学:

    答案
    • 先掌握 axios 库使用,再了解 XMLHttpRequest 原理
  3. 这一节 axios 体验步骤(语法)?

    答案
    • 引入 axios 库,使用 axios 相关语法
  4. URL 是什么?

    答案
    • 统一资源定位符,网址,用于访问服务器上资源
  5. 请解释这个 URL,每个部分作用?

    hmajax.itheima.net/api/news

    答案
    • 协议://域名/资源路径
  6. URL 查询参数有什么用?

    答案
    • 浏览器提供给服务器额外信息,获取对应的数据
  7. axios 要如何携带查询参数?

    答案
    • 使用 params 选项,携带参数名和值在对象结构中
  8. 请求方法最常用的是哪2个,分别有什么作用?

    答案
    • POST 提交数据,GET 查询数据
  9. axios 的核心配置项有哪几个,作用分别是什么?

    答案
    • url:目标资源地址(请求 URL 网址);
    • method:请求方法,GET 可以省略(不区分大小写);
    • params:查询参数;
    • data:提交的数据
  10. axios 如何拿到请求响应失败的信息?

    答案
    • 通过 axios 函数调用后,在后面接着调用 .catch 方法捕获

  11. 浏览器发送给服务器的内容叫做,请求报文

  12. 请求报文的组成是什么?

    答案
    • 请求行,请求头,空行,请求体
  13. 学会了查看请求报文,对实际开发有什么帮助呢?

    答案
    • 可以快速确认我们发送的内容是否正确

  14. 响应报文的组成:

    答案
    • 响应行(状态行):协议,HTTP响应状态码,状态信息
    • 响应头:以键值对的格式携带的附加信息,比如:Content-Type(告诉浏览器,本次返回的内容类型)
    • 空行:分割响应头,控制之后的是服务器返回的资源
    • 响应体:返回的资源
  15. 接口文档是什么?

    答案
    • 由后端提供的描述接口的文章
  16. 接口文档都包含哪些信息?

    答案
    • 请求的 URL 网址,请求方法,请求参数和说明
  17. 在浏览器中如何查看查询参数/请求体,以及响应体数据?

  18. 请求报文和响应报文由几个部分组成,每个部分的作用?

  19. 我们什么时候使用 form-serialize 插件?

    答案
    • 快速收集表单元素的值
  20. 如何使用 form-serialize 插件?

    答案
    • 1. 先引入插件到自己的网页中,2. 准备form和表单元素的name属性,3.使用serialize函数,传入form表单和配置对象

  21. 配置对象中 hash 和 empty 有什么用?

    答案
    • hash 决定是收集为 JS 对象还是查询参数字符串,empty 决定是否收集空值

  22. 如何把一个第三方插件使用在已完成的案例中?

    答案
    • 引入后,只需要使用在要修改的地方,修改一点就要确认测试一下

二. AJAX原理

1. AJAX原理-XMLHttpRequest

概念

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

关系:axios 内部采用 XMLHttpRequest 与服务器交互。

image.png

好处:掌握使用 XHR 与服务器进行数据交互,了解 axios 内部原理

AJAX 是浏览器与服务器通信的技术,采用 XMLHttpRequest 对象相关代码

axios 是对 XHR 相关代码进行了封装,让我们只关心传递的接口参数

学习 XHR 也是了解 axios 内部与服务器交互过程的真正原理

使用XMLHttpRequest

步骤:

  1. 创建 XMLHttpRequest 对象
  2. 配置请求方法和请求 url 地址
  3. 监听 loadend 事件,接收响应结果
  4. 发起请求
const xhr = new XMLHttpRequest()
xhr.open('请求方法', '请求url网址')
xhr.addEventListener('loadend', () => {
  // 响应结果 接收- 响应
  console.log(xhr.response)
})
xhr.send() // 发送 - 请求
XMLHttpRequest - 查询参数

什么是查询参数:浏览器提供给服务器的额外信息,让服务器返回浏览器想要的数据

语法:http://xxxx.com/xxx/xxx?参数名1=值1&参数名2=值2

所以,原生 XHR 需要自己在 url 后面携带查询参数字符串,没有 axios 帮助我们把 params 参数拼接到 url 字符串后面了

/**
 * 目标:使用XHR携带查询参数,展示某个省下属的城市列表
*/
const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://hmajax.itheima.net/api/city?pname=辽宁省')
xhr.addEventListener('loadend', () => {
  console.log(xhr.response)
  const data = JSON.parse(xhr.response)
  console.log(data)
  document.querySelector('.city-p').innerHTML = data.list.join('<br>')
})
xhr.send()
URLSearchParams

多个查询参数,如果自己拼接很麻烦,这里用 URLSearchParams 把参数对象转成“参数名=值&参数名=值“格式的字符串,语法如下:

// 1. 创建 URLSearchParams 对象
const paramsObj = new URLSearchParams({
  参数名1: 值1,
  参数名2: 值2
})

// 2. 生成指定格式查询参数字符串
const queryString = paramsObj.toString()
// 结果:参数名1=值1&参数名2=值2
XMLHttpRequest - 数据提交

需求:通过 XHR 提交用户名和密码,完成注册用户功能

核心:

  • 请求头设置 Content-Type:application/json

  • 请求体携带 JSON 字符串

步骤和语法:

  1. 注意1:但是这次没有 axios 帮我们了,我们需要自己设置请求头 Content-Type:application/json,来告诉服务器端,我们发过去的内容类型是 JSON 字符串,让他转成对应数据结构取值使用

  2. 注意2:没有 axios 了,我们前端要传递的请求体数据,也没人帮我把 JS 对象转成 JSON 字符串了,需要我们自己转换

  3. 注意3:原生 XHR 需要在 send 方法调用时,传入请求体携带

const xhr = new XMLHttpRequest()
xhr.open('请求方法', '请求url网址')
xhr.addEventListener('loadend', () => {
  console.log(xhr.response)
})

// 1. 告诉服务器,我传递的内容类型,是 JSON 字符串
xhr.setRequestHeader('Content-Type', 'application/json')
// 2. 准备数据并转成 JSON 字符串
const user = { username: 'itheima007', password: '7654321' }
const userStr = JSON.stringify(user)
// 3. 发送请求体数据
xhr.send(userStr)
小结
  1. AJAX 原理是什么?

    答案
    • window 提供的 XMLHttpRequest对象
  2. 为什么学习 XHR ?

    答案
    • 有更多与服务器数据通信方式
    • 了解 axios 内部原理
  3. XHR 使用步骤?

    答案
    • 1. 创建 XHR 对象
    • 2. 调用 open 方法,设置 url 和请求方法
    • 3. 监听 loadend 事件,接收结果
    • 4. 调用 send 方法,发起请求
  4. XHR 如何携带查询参数?

    答案
    • 在调用 open 方法的时候,在 url? 后面按照指定格式拼接参数名和值
  5. JS 对象如何转成查询参数格式字符串?

    答案
    • 在调用 open 方法的时候,在 url? 后面按照指定格式拼接参数名和值
  6. XHR 如何提交请求体数据?

    答案
    • 在 send 中携带请求体数据,要按照后端要求的内容类型携带

2. Promise

Promise 定义
  1. 什么是 Promise ?

    • Promise 对象用于表示一个异步操作的最终完成(或失败)及其结果值。
  2. Promise 的好处是什么?

    • 逻辑更清晰(成功或失败会关联后续的处理函数)

    • 了解 axios 函数内部运作的机制

    • 能解决回调函数地狱问题

image.png

  1. Promise 管理异步任务,语法怎么用?

    // 1. 创建 Promise 对象
    const p = new Promise((resolve, reject) => {
     // 2. 执行异步任务-并传递结果
     // 成功调用: resolve(值) 触发 then() 执行
     // 失败调用: reject(值) 触发 catch() 执行
    })
    // 3. 接收结果
    p.then(result => {
     // 成功
    }).catch(error => {
     // 失败
    })
    
  2. 示例代码:

    /**
     * 目标:使用Promise管理异步任务
    */
    // 1. 创建Promise对象
    const p = new Promise((resolve, reject) => {
      // 2. 执行异步代码
      setTimeout(() => {
        // resolve('模拟AJAX请求-成功结果')
        reject(new Error('模拟AJAX请求-失败结果'))
      }, 2000)
    })
    
    // 3. 获取结果
    p.then(result => {
      console.log(result)
    }).catch(error => {
      console.log(error)
    })
    
Promise 三种状态
  1. 为什么要了解 Promise 的三种状态(作用) ?

    • 知道 Promise 对象如何关联的处理函数,以及代码的执行顺序
  2. Promise 有哪三种状态?

    每个 Promise 对象必定处于以下三种状态之一

    1. 待定(pending):初始状态,既没有被兑现,也没有被拒绝

    2. 已兑现(fulfilled):操作成功完成

    3. 已拒绝(rejected):操作失败

    image.png

  3. Promise 的状态改变有什么用:调用对应函数,改变 Promise 对象状态后,内部触发对应回调函数传参并执行。

注意: Promise 对象一旦被兑现/拒绝,那就是已敲定了,状态无法再被改变

封装_简易axios-获取省份列表

模拟 axios 函数封装,更深入了解 axios 内部运作原理

  1. 需求:基于 Promise 和 XHR 封装 myAxios 函数,获取省份列表展示到页面

  2. 核心语法:

    function myAxios(config) {
      return new Promise((resolve, reject) => {
        // XHR 请求
        // 调用成功/失败的处理程序
      })
    }
    
    myAxios({
      url: '目标资源地址'
    }).then(result => {
        
    }).catch(error => {
        
    })
    

代码如下:

```js
/**
 * 目标:封装_简易axios函数_获取省份列表
 *  1. 定义myAxios函数,接收配置对象,返回Promise对象
 *  2. 发起XHR请求,默认请求方法为GET
 *  3. 调用成功/失败的处理程序
 *  4. 使用myAxios函数,获取省份列表展示
*/
// 1. 定义myAxios函数,接收配置对象,返回Promise对象
function myAxios(config) {
  return new Promise((resolve, reject) => {
    // 2. 发起XHR请求,默认请求方法为GET
    const xhr = new XMLHttpRequest()
    xhr.open(config.method || 'GET', config.url)
    xhr.addEventListener('loadend', () => {
      // 3. 调用成功/失败的处理程序
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(JSON.parse(xhr.response))
      } else {
        reject(new Error(xhr.response))
      }
    })
    xhr.send()
  })
}

// 4. 使用myAxios函数,获取省份列表展示
myAxios({
  url: 'http://hmajax.itheima.net/api/province'
}).then(result => {
  console.log(result)
  document.querySelector('.my-p').innerHTML = result.list.join('<br>')
}).catch(error => {
  console.log(error)
  document.querySelector('.my-p').innerHTML = error.message
})
```
封装_简易axios-获取地区列表

修改 myAxios 函数支持传递查询参数,获取辽宁省,大连市的地区列表

  1. 需求:在上个封装的建议 axios 函数基础上,修改代码支持传递查询参数功能

  2. 修改步骤:

    1. myAxios 函数调用后,判断 params 选项
    2. 基于 URLSearchParams 转换查询参数字符串
    3. 使用自己封装的 myAxios 函数显示地区列表
  3. 核心代码:

    function myAxios(config) {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        // 1. 判断有params选项,携带查询参数
        if (config.params) {
          // 2. 使用URLSearchParams转换,并携带到url上
          const paramsObj = new URLSearchParams(config.params)
          const queryString = paramsObj.toString()
          // 把查询参数字符串,拼接在url?后面
          config.url += `?${queryString}`
        }
    
        xhr.open(config.method || 'GET', config.url)
        xhr.addEventListener('loadend', () => {
          if (xhr.status >= 200 && xhr.status < 300) {
            resolve(JSON.parse(xhr.response))
          } else {
            reject(new Error(xhr.response))
          }
        })
        xhr.send()
      })
    }
    
    // 3. 使用myAxios函数,获取地区列表
    myAxios({
      url: 'http://hmajax.itheima.net/api/area',
      params: {
        pname: '辽宁省',
        cname: '大连市'
      }
    }).then(result => {
      console.log(result)
      document.querySelector('.my-p').innerHTML = result.list.join('<br>')
    })
    
封装_简易axios-注册用户
function myAxios(config) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()

    if (config.params) {
      const paramsObj = new URLSearchParams(config.params)
      const queryString = paramsObj.toString()
      config.url += `?${queryString}`
    }
    xhr.open(config.method || 'GET', config.url)

    xhr.addEventListener('loadend', () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(JSON.parse(xhr.response))
      } else {
        reject(new Error(xhr.response))
      }
    })
    // 1. 判断有data选项,携带请求体
    if (config.data) {
      // 2. 转换数据类型,在send中发送
      const jsonStr = JSON.stringify(config.data)
      xhr.setRequestHeader('Content-Type', 'application/json')
      xhr.send(jsonStr)
    } else {
      // 如果没有请求体数据,正常的发起请求
      xhr.send()
    }
  })
}

document.querySelector('.reg-btn').addEventListener('click', () => {
  // 3. 使用myAxios函数,完成注册用户
  myAxios({
    url: 'http://hmajax.itheima.net/api/register',
    method: 'POST',
    data: {
      username: 'itheima999',
      password: '666666'
    }
  }).then(result => {
    console.log(result)
  }).catch(error => {
    console.dir(error)
  })
})

3. 小结

  1. 什么是 Promise ?

    答案
    • 表示(管理)一个异步操作最终状态和结果值的对象
  2. 为什么学习 Promise ?

    答案
    • 成功和失败状态,可以关联对应处理函数
    • 了解 axios 内部运作的原理
  3. Promise 使用步骤?

    答案
    • new Promise 创建Promise对象。
    • 执行异步任务-并传递结果1. 用 resolve 关联 then 的回调函数传递成功结果。2.用 reject 关联 catch 的回调函数传递失败结果。
  4. Promise 对象有哪 3 种状态?

    答案
    • 待定 pending,已兑现 fulfilled,已拒绝 rejected
  5. Promise 状态有什么用?

    答案
    • 状态改变后,调用关联的处理函数
  6. AJAX 如何判断是否请求响应成功了?

    答案
    • 响应状态码在大于等于 200 并且小于 300 的范围是成功的
  7. 自己封装的 myAxios 如何设置默认请求方法 GET?

    答案
    • config.method 判断有值就用,无值用‘GET’方法
  8. 外面传入查询参数对象,myAxios 函数内如何转查询参数字符串?

    答案
    • 使用 URLSearchParams 对象转换
  9. 外面传入 data 选项,myAxios 函数内如何携带请求体参数?

    答案
    • 判断外面传入了这个属性,自己转成 JSON 字符串并设置请求头并在 send 方法中携带
  10. 做完这个项目会带来什么收获?

    答案
    • 可以做一个真正有意义的业务,查看城市的天气预报,测试自己封装的 myAxios 函数是否好用

  11. 监听输入框实时改变的事件是什么?

    答案
    • input事件

4. 重点(必须会)

  1. 了解 AJAX 原理之 XMLHttpRequest(XHR)相关语法
  2. 了解 Promise 的作用和三种状态
  3. 了解 axios 内部运作的过程

三. AJAX进阶

1. 同步代码和异步代码

同步代码:浏览器是按照我们书写代码的顺序一行一行地执行程序的。浏览器会等待代码的解析和工作,在上一行完成才会执行下一行。(逐行执行,需原地等待结果后,才继续向下执行)

异步代码:调用后耗时,不阻塞代码继续执行(不必原地等待),在将来完成后触发回调函数传递结果

代码打印顺序:

const result = 0 + 1
console.log(result)
setTimeout(() => {
  console.log(2)
}, 2000)
document.querySelector('.btn').addEventListener('click', () => {
  console.log(3)
})
document.body.style.backgroundColor = 'pink'
console.log(4)
//结果:1, 4, 2
//按钮点击一次打印一次 3

异步代码接收结果,使用的都是回调函数

2. 回调函数地狱和 Promise 链式调用

回调函数地狱

了解回调函数地狱的概念和缺点

需求:展示默认第一个省,第一个城市,第一个地区在下拉菜单中

image.png image.png

概念:在回调函数中嵌套回调函数,一直嵌套下去就形成了回调函数地狱

缺点:可读性差,异常无法捕获,耦合性严重,牵一发动全身

Promise-链式调用

了解 Promise 链式调用特点和语法

概念:依靠 then() 方法会返回一个新生成的 Promise 对象特性,继续串联下一环任务,直到结束

细节:then() 回调函数中的返回值,会影响新生成的 Promise 对象最终状态和结果

好处:通过链式调用,解决回调函数嵌套问题

image.png

按照图解,编写核心代码:

/**
 * 目标:掌握Promise的链式调用
 * 需求:把省市的嵌套结构,改成链式调用的线性结构
*/
// 1. 创建Promise对象-模拟请求省份名字
const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('北京市')
  }, 2000)
})

// 2. 获取省份名字
const p2 = p.then(result => {
  console.log(result)
  // 3. 创建Promise对象-模拟请求城市名字
  // return Promise对象最终状态和结果,影响到新的Promise对象
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(result + '--- 北京')
    }, 2000)
  })
})

// 4. 获取城市名字
p2.then(result => {
  console.log(result)
})

// then()原地的结果是一个新的Promise对象
console.log(p2 === p)
Promise-链式调用_解决回调地狱

使用 Promise 链式调用,解决回调函数地狱问题

做法:每个 Promise 对象中管理一个异步任务,用 then 返回 Promise 对象,串联起来

image.png

按照图解思路,编写核心代码:

/**
 * 目标:把回调函数嵌套代码,改成Promise链式调用结构
 * 需求:获取默认第一个省,第一个市,第一个地区并展示在下拉菜单中
*/
let pname = ''
// 1. 得到-获取省份Promise对象
axios({url: 'http://hmajax.itheima.net/api/province'}).then(result => {
  pname = result.data.list[0]
  document.querySelector('.province').innerHTML = pname
  // 2. 得到-获取城市Promise对象
  return axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }})
}).then(result => {
  const cname = result.data.list[0]
  document.querySelector('.city').innerHTML = cname
  // 3. 得到-获取地区Promise对象
  return axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }})
}).then(result => {
  console.log(result)
  const areaName = result.data.list[0]
  document.querySelector('.area').innerHTML = areaName
})

3. async 函数和 await

概念

  • async 和await关键字让我们可以用一种更简洁的方式写出基于 Promise的异步行为,而无需刻意地链式调用promise。
  • 在 async 函数内,使用 await 关键字取代 then 函数,等待获取 Promise 对象成功状态的结果值

做法:使用 async 和 await 解决回调地狱问题

注意:await必须用在async修饰的函数内(await会阻止"异步函数内"代码继续执行,原地等待结果)

核心代码:

// 1. 定义async修饰函数
async function getData() {
  // 2. await等待Promise对象成功的结果
  const pObj = await axios({url: 'http://hmajax.itheima.net/api/province'})
  const pname = pObj.data.list[0]
  const cObj = await axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }})
  const cname = cObj.data.list[0]
  const aObj = await axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }})
  const areaName = aObj.data.list[0]

  document.querySelector('.province').innerHTML = pname
  document.querySelector('.city').innerHTML = cname
  document.querySelector('.area').innerHTML = areaName
}
getData()
async 函数和 await 捕获错误

使用:try...catch

try 和 catch 的作用:语句标记要尝试的语句块,并指定一个出现异常时抛出的响应

语法:

try {
  // 要执行的代码
} catch (error) {
  // error 接收的是,错误消息
  // try 里代码,如果有错误,直接进入这里执行
}

注意:try 里有报错的代码,会立刻跳转到 catch 中

4. 事件循环 EventLoop

掌握事件循环模型是如何执行异步代码的

事件循环(EventLoop)好处:掌握后知道 JS 是如何安排和运行代码的

作用:JavaScript有一个基于事件循环的并发模型,事件循环负责执行代码,收集和处理事件以及执行队列中的子任务

原因:JavaScript 单线程(某一刻只能执行一行代码),为了让耗时代码不阻塞其他代码运行,设计了事件循环模型

概念:执行代码和收集异步任务的模型,在调用栈空闲,反复调用任务队列里回调函数的执行机制,就叫事件循环

事件循环-练习

需求:请根据掌握的事件循环的模型概念,分析代码执行过程

/**
 * 目标:阅读并回答执行的顺序结果
*/
console.log(1)
setTimeout(() => {
  console.log(2)
}, 0)
function myFn() {
  console.log(3)
}
function ajaxFn() {
  const xhr = new XMLHttpRequest()
  xhr.open('GET', 'http://hmajax.itheima.net/api/province')
  xhr.addEventListener('loadend', () => {
    console.log(4)
  })
  xhr.send()
}
for (let i = 0; i < 1; i++) {
  console.log(5)
}
ajaxFn()
document.addEventListener('click', () => {
  console.log(6)
})
myFn()

结果:1 5 3 2 4 点击一次document就会执行一次打印6

宏任务与微任务

掌握微任务和宏任务的概念和区分

ES6 之后引入了 Promise 对象, 让 JS 引擎也可以发起异步任务

异步任务划分为了

  • 宏任务:由浏览器环境执行的异步代码
  • 微任务:由 JS 引擎环境执行的异步代码

image.png

image.png

注意:Promise 本身是同步的,而 then 和 catch 回调函数是异步的。

微任务队列清空后,才会执行下一个宏任务

注意:宏任务每次在执行同步代码时,产生微任务队列,清空微任务队列任务后,微任务队列空间释放!

下一次宏任务执行时,遇到微任务代码,才会再次申请微任务队列空间放入回调函数消息排队

总结:一个宏任务包含微任务队列,他们之间是包含关系,不是并列关系

image.png

经典面试题 宏任务和微任务-执行顺序

示例1

image.png

执行步骤说明:

  1. script脚本交给【宿主环境(浏览器)】,将整个js代码推入宏任务队列,此时调用栈是空闲,就开始执行第一个宏任务
  2. 【console.log(1)】放入调用栈执行,在控制台打印1,执行完之后出栈
  3. js引擎接着读取代码,【setTimeout(() => {console.log(2)}, 0)】是宏任务,交给宿主浏览器,浏览器进行倒计时,发现是0秒,会立刻将回调函数【() => {console.log(2)}】放到宏任务队列当中排队
  4. js引擎继续读取代码,【new Promise((resolve) => { console.log(3) resolve(4) })】放到执行栈调用,Promise 本身是同步的,所以控制台立刻打印3,【resolve(4)】标记promise成功状态,【new Promise】执行完毕出栈,此时得到一个promise对象,它是成功状态的
  5. 接着,js引擎执行同步代码【p.then(result => console.log(result))】放入执行栈,发现回调函数是异步微任务,将【result => console.log(result)】放入微任务队列进行排队,【p.then(result => console.log(result))】执行完毕出栈
  6. js引擎接着读取代码,【console.log(5)】推入调用栈执行,在控制台打印5,执行完毕之后出栈
  7. 此时,script中的同步代码都执行完毕,调用栈空闲了,开始去任务队列中反复调度有没有要执行的异步回调函数
  8. 优先调度微任务队列,因为微任务更接近JS 引擎
  9. 发现微任务队列中有待执行的回调函数,推入到调用栈执行【result => console.log(result),已确认成功并且传入4】,所以在控制台打印4,微任务的回调就执行完了,出栈
  10. 调用栈又空闲了,又反复调度任务队列中的回调,发现微任务队列中的任务已经被清空了,所以调度宏任务队列中任务,【() => {console.log(2)}】推入调用栈执行,在控制台打印2,宏任务的回调执行完毕,出栈
  11. 执行完毕

示例2

// 目标:回答代码执行顺序
console.log(1)
setTimeout(() => {
  console.log(2)
  const p = new Promise(resolve => resolve(3))
  p.then(result => console.log(result))
}, 0)
const p = new Promise(resolve => {
  setTimeout(() => {
    console.log(4)
  }, 0)
  resolve(5)
})
p.then(result => console.log(result))
const p2 = new Promise(resolve => resolve(6))
p2.then(result => console.log(result))
console.log(7)

//打印顺序为:1 7 5 6 2 3 4

image.png

5. Promise.all 静态方法

了解 Promise.all 作用和使用场景

概念:合并多个 Promise 对象,等待所有同时成功完成(或某一个失败),做后续逻辑

image.png

语法:

const p = Promise.all([Promise对象, Promise对象, ...])
p.then(result => {
  // result 结果: [Promise对象成功结果, Promise对象成功结果, ...]
}).catch(error => {
  // 第一个失败的 Promise 对象,抛出的异常对象
})
<body>
  <ul class="my-ul"></ul>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script>
    /**
     * 目标:掌握Promise的all方法作用,和使用场景
     * 业务:当我需要同一时间显示多个请求的结果时,就要把多请求合并
     * 例如:默认显示"北京", "上海", "广州", "深圳"的天气在首页查看
     * code:
     * 北京-110100
     * 上海-310100
     * 广州-440100
     * 深圳-440300
    */
    // 1. 请求城市天气,得到Promise对象
    const bjPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '110100' } })
    const shPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '310100' } })
    const gzPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '440100' } })
    const szPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '440300' } })

    // 2. 使用Promise.all,合并多个Promise对象
    const p = Promise.all([bjPromise, shPromise, gzPromise, szPromise])
    p.then(result => {
      // 注意:结果数组顺序和合并时顺序是一致
      console.log(result)
      const htmlStr = result.map(item => {
        return `<li>${item.data.data.area} --- ${item.data.data.weather}</li>`
      }).join('')
      document.querySelector('.my-ul').innerHTML = htmlStr
    }).catch(error => {
      console.dir(error)
    })
  </script>
</body>

6. 小结

  1. 什么是同步代码?

    答案
    • 逐行执行,原地等待结果后,才继续向下执行
  2. 什么是异步代码?

    答案
    • 调用后耗时,不阻塞代码执行,将来完成后触发回调函数
  3. JS 中有哪些异步代码?

    答案
    • setTimeout / setInterval,事件,AJAX
  4. 异步代码如何接收结果?

    答案
    • 依靠回调函数来接收
  5. 什么是回调函数地狱?

    答案
    • 在回调函数一直向下嵌套回调函数,形成回调函数地狱
  6. 回调函数地狱问题?

    答案
    • 可读性差,异常捕获困难,耦合性严重
  7. 什么是 Promise 的链式调用?

    答案
    • 使用 then 方法返回新 Promise 对象特性,一直串联下去
  8. then 回调函数中,return 的值会传给哪里?

    答案
    • 传给 then 方法生成的新 Promise 对象
  9. Promise 链式调用有什么用?

    答案
    • 解决回调函数嵌套问题
  10. Promise 链式调用如何解决回调函数地狱?

    答案
    • then 的回调函数中 return Promise对象,影响当前新 Promise 对象的值
  11. await 的作用是什么?

    答案
    • 替代 then 方法来提取 Promise 对象成功状态的结果
  12. try 和 catch 有什么作用?

    答案
    • 捕获同步流程的代码报错信息
  13. 什么是事件循环?

    答案
    • 执行代码和收集异步任务,在调用栈空闲时,反复调用任务队列里回调函数执行机制
  14. 为什么有事件循环?

    答案
    • JavaScript 是单线程的,为了不阻塞 JS 引擎,设计执行代码的模型
  15. JavaScript 内代码如何执行?

    答案
    • 执行同步代码,遇到异步代码交给宿主浏览器环境执行
    • 异步有了结果后,把回调函数放入任务队列排队
    • 当调用栈空闲后,反复调用任务队列里的回调函数
  16. 什么是宏任务?

    答案
    • 浏览器执行的异步代码
    • 例如:JS 执行脚本事件(script),setTimeout/setInterval,AJAX请求完成事件,用户交互事件等
  17. 什么是微任务?

    答案
    • JS 引擎执行的异步代码
    • 例如:Promise对象.then()的回调(注:Promise对象本身是同步的)
  18. JavaScript 内代码如何执行?

    答案
    • 执行第一个 script 脚本事件宏任务,执行里面同步代码
    • 遇到 宏任务/微任务 交给宿主环境(浏览器/js引擎),有结果回调函数进入对应队列
    • 当执行栈空闲时,清空微任务队列,再执行下一个宏任务,从1再来
  19. Promise.all 什么时候使用?

    答案
    • 合并多个 Promise 对象并等待所有同时成功的结果,如果有一个报错就会最终为失败状态,当需要同时渲染多个接口数据同时到网页上时使用

7. 今日重点(必须会)

  1. 区分异步代码,回调函数地狱问题和所有解决防范(Promise 链式调用)

  2. 掌握 async 和 await 的使用

  3. 理解 EventLoop 和宏任务微任务执行顺序

  4. 了解 Promise.all 的作用和使用场景

扩展阅读

ES6

  1. ES6 对象属性和值简写的前提是什么?

    答案
    • 当属性名和value位置变量名同名即可简写

函数

  1. 我们什么时候需要封装函数?

    答案
    • 遇到相同逻辑,重复代码要复用的时候
  2. 如何封装一个函数呢?

    答案
    • 先明确要完成的需求,以及需要的参数,再来实现其中的细节,然后在需要的地方调用
  3. 哪个事件能实时检测到输入框值的变化?

    A:input 事件

    B:change 事件

    答案
    • 选A

项目实战注意点

token

概念:访问权限的令牌,本质上是一串字符串

创建:正确登录后,由后端签发并返回

作用:判断是否有登录状态等,控制访问权限

注意:前端只能判断 token 有无,而后端通过解密可以提取 token 字符串的原始信息,判断有效性

image.png

token 的使用:只有登录状态,才可以访问内容页面

axios 请求拦截器和响应拦截器

axios 请求拦截器

axios 请求拦截器:发起请求之前,触发的配置函数,对请求参数进行额外配置

axios 请求拦截器,什么时候使用?

  • 有公共配置和设置时,统一设置在请求拦截器中

场景: image.png

axios 响应拦截器

axios 响应拦截器:响应回到 then/catch 之前,触发的拦截函数,对响应结果统一处理

例如:身份验证失败,统一判断并做处理

axios 响应拦截器,什么时候触发成功/失败的回调函数?

  • 状态为 2xx 触发成功回调,其他则触发失败的回调函数

image.png

富文本编辑器

wangEditor 插件

Node.js

一. Node.js入门

1. Node.js 简介

什么是 Node.js

什么是 Node.js,有什么用,为何能独立执行 JS 代码

定义:Node.js 是一个跨平台的 JavaScript 运行环境,使开发者可以搭建服务器端的 JavaScript 应用程序。

作用:编写服务器端程序(也可以对前端代码进行压缩,转译,整合等等,提高前端开发和运行效率)

  • 编写数据接口,提供网页资源浏览功能等等
  • 前端工程化:为后续学习 Vue 和 React 等框架做铺垫
什么是前端工程化?
  • 前端工程化:开发项目直到上线,过程中集成的所有工具和技术(前端代码压缩,整合,转译,测试,自动部署等等工具的集成统称,为了提高前端开发项目的效率)(对前端代码进行优化,集成各种提高前端开发效率的工具等)
  • Node.js 是前端工程化的基础(因为 Node.js 可以主动读取前端代码内容)

image.png

Node.js 为何能执行 JS?

首先:浏览器能执行 JS 代码,依靠的是内核中的 V8 引擎(C++ 程序)

其次:Node.js 是基于 Chrome V8 引擎进行封装(运行环境)

区别:都支持 ECMAScript 标准语法,Node.js 有独立的 API,但是语法和浏览器环境的 V8 有所不同,没有 DOM 和 BOM 等(document 和 window)

image.png

注意:Node.js 环境没有 DOM 和 BOM 等

Node.js 安装

要求:下载 node-v16.19.0.msi 安装程序(指定版本:兼容 vue-admin-template 模板)

安装过程:默认下一步即可

注释事项:

  1. 安装在非中文路径下

  2. 无需勾选自动安装其他配套软件

成功验证:

  1. 打开 cmd 终端,输入 node -v 命令查看版本号

  2. 如果有显示,则代表安装成功

Node.js 没有图形化界面,需要使用 cmd 终端命令行(利用一些命令来操控电脑执行某些程序软件)输入

使用 Node.js

需求:新建 JS 文件,并编写代码后,在 node 环境下执行

命令:在 VSCode 集成终端中,输入 node xxx.js,回车即可执行

image.png

小结
  1. Node.js 是什么?

    答案
    • 基于 Chrome 的 V8 引擎封装,独立执行 JavaScript 代码的环境
  2. Node.js 有什么用?

    答案
    • 编写后端程序:提供数据和网页资源等
    • 前端工程化:集成各种开发中使用的工具和技术(如翻译压缩整合代码等,提高开发效率)
  3. Node.js 为何能执行 JS 代码?

    答案
    • 基于 Chrome 的 V8 引擎封装
  4. Node.js 与浏览器环境的 JS 最大区别?

    答案
    • Node.js 环境中没有 BOM 和 DOM,但是也用 JS 语法
  5. Node.js 如何执行代码?

    答案
    • 在 VSCode 终端中输入:node xxx.js 回车即可执行(注意路径)

2. fs模块-读写文件

了解模块概念,使用 fs 模块封装的方法读写文件内容

模块:类似插件,封装了方法/属性

fs 模块:封装了与本机文件系统进行交互的,方法和属性

fs 模块使用语法如下:

  • 加载 fs 模块,得到 fs 对象

    const fs = require('fs')
    
  • 写入文件内容语法:

    //注意:文件可以没有,但是写入文件的文件夹要有
    fs.writeFile('文件路径', '写入内容', err => {
      // 写入后的回调函数
    })
    
  • 读取文件内容的语法:

    fs.readFile('文件路径', (err, data) => {
      // 读取后的回调函数
      // data 是文件内容的 Buffer 数据流
    })
    

向 test.txt 文件写入内容并读取打印

/**
 * 目标:使用 fs 模块,读写文件内容
 * 语法:
 * 1. 引入 fs 模块
 * 2. 调用 writeFile 写入内容
 * 3. 调用 readFile  读取内容
 */
// 1. 引入 fs 模块
const fs = require('fs')
// 2. 调用 writeFile 写入内容
// 注意:建议写入字符串内容,会覆盖目标文件所有内容
fs.writeFile('./text.txt', '欢迎使用 fs 模块读写文件内容', err => {
  if (err) console.log(err)
  else console.log('写入成功')
})
// 3. 调用 readFile  读取内容
fs.readFile('./text.txt', (err, data) => {
  if (err) console.log(err)
  else console.log(data.toString()) // 把 Buffer 数据流转成字符串类型
})

3. path模块-路径处理

使用 path 模块来得到绝对路径

问题:Node.js 代码中,相对路径是根据终端所在路径来查找的,可能无法找到你想要的文件

举例 image.png

如果终端是要执行文件所在文件夹,node执行当前js文件路径没问题;但是,如果终端不是要执行文件所在文件夹,js文件里面使用相对路径,就会导致找不到文件

建议:在 Node.js 代码中,使用绝对路径

  1. 为什么在 Node.js 待执行的 JS 代码中要用绝对路径:

    Node.js 执行 JS 代码时,代码中的路径都是以终端所在文件夹出发查找相对路径,而不是以我们认为的从代码本身出发,会遇到问题,所以在 Node.js 要执行的代码中,访问其他文件,建议使用绝对路径

补充:__dirname 内置变量(动态获取当前文件所在文件夹的绝对路径)

  • windows:D:\备课代码\3-B站课程\03_Node.js与Webpack\03-code\03
  • mac:/Users/xxx/Desktop/备课代码/3-B站课程/03_Node.js与Webpack/03-code/03
  • 在 windows 和 mac 地址中,分隔符不一样

注意:path.join() 会按照所在本机系统的分隔符作为定界符来链接你传入的路径

总结:使用模块内置变量 __dirname配合 path.join() 来得到绝对路径使用

语法:

  1. 加载 path 模块

    const path = require('path)

  2. 使用 path.join 方法,拼接路径

    path.join('路径1', '路径2',...)

    const fs = require('fs')
    console.log(__dirname) // D:\备课代码\2_node_3天\Node_代码\Day01_Node.js入门\代码\03
    
    // 1. 加载 path 模块
    const path = require('path')
    // 2. 使用 path.join() 来拼接路径
    // const pathStr = path.join(__dirname, '../text.txt')
    const pathStr = path.join(__dirname, '..', 'text.txt')
    console.log(pathStr)
    
    fs.readFile(pathStr, (err, data) => {
      if (err) console.log(err)
      else console.log(data.toString())
    })
    

4. 案例-压缩前端html

需求:把准备好的 html 文件里的回车符(\r)和换行符(\n)去掉进行压缩,写入到新 html 中

步骤:

  1. 读取源 html 文件内容
  2. 正则替换字符串
  3. 写入到新的 html 文件中,并运行查看是否能正常打开网页

代码实现如下:

/**
 * 目标一:压缩 html 里代码
 * 需求:把 public/index.html 里的,回车/换行符去掉,写入到 dist/index.html 中
 *  1.1 读取 public/index.html 内容
 *  1.2 使用正则替换内容字符串里的,回车符\r 换行符\n
 *  1.3 确认后,写入到 dist/index.html 内
 */
const fs = require('fs')
const path = require('path')
// 1.1 读取 public/index.html 内容
fs.readFile(path.join(__dirname, 'public', 'index.html'), (err, data) => {
  const htmlStr = data.toString()
  // 1.2 使用正则替换内容字符串里的,回车符\r 换行符\n
  const resultStr = htmlStr.replace(/[\r\n]/g, '')
  // 1.3 确认后,写入到 dist/index.html 内
  fs.writeFile(path.join(__dirname, 'dist', 'index.html'), resultStr, err => {
    if (err) console.log(err)
    else console.log('压缩成功')
  })
})

5. 认识URL中的端口号

认识 URL 中端口号的作用,以及 Web 服务的作用

URL 统一资源定位符,简称网址,用于访问网络上的资源

端口号的作用:标记服务器里不同的服务程序(端口号范围:0-65535 之间的任意整数)

image.png

常见的服务程序

  • Web服务程序:用于提供网上信息浏览的程序代码
  • 注意:0-1023 和一些特定的端口号被占用,我们自己编写服务程序请避开使用

image.png

6. http模块-创建Web服务

基于 Node.js 环境,使用内置 http 模块,创建 Web 服务程序

需求:引入 http 模块,使用相关语法,创建 Web 服务程序,响应返回给请求方一句提示 ‘hello,world’

步骤:

  1. 引载 http 模块,创建 Web 服务对象
  2. 监听 request 请求事件,设置响应头和响应体
  3. 启动 Web 服务监听对应端口号(配置端口号并启动 Web 服务)
  4. 运行本服务在终端进程中,用浏览器发起请求(浏览器请求 http://localhost:3000 测试)

注意:本机的域名叫做 localhost

代码如下:

/**
 * 目标:基于 http 模块创建 Web 服务程序
 *  1.1 加载 http 模块,创建 Web 服务对象
 *  1.2 监听 request 请求事件,设置响应头和响应体
 *  1.3 配置端口号并启动 Web 服务
 *  1.4 浏览器请求(http://localhost:3000)测试
 */
// 1.1 加载 http 模块,创建 Web 服务对象
const http = require('http')
const server = http.createServer()
// 1.2 监听 request 请求事件,设置响应头和响应体
server.on('request', (req, res) => {
  // 设置响应头-内容类型-普通文本以及中文编码格式
  res.setHeader('Content-Type', 'text/plain;charset=utf-8')
  // 设置响应体内容,结束本次请求与响应
  res.end('欢迎使用 Node.js 和 http 模块创建的 Web 服务')
})
// 1.3 配置端口号并启动 Web 服务
server.listen(3000, () => {
  console.log('Web 服务启动成功了')
})

小结

  1. 如何访问本机里运行的 Web 服务?

    答案
    • http://localhost:Web服务的端口号/资源路径

7. 案例-浏览时钟

体验 Web 服务除了接口数据以外,还能返回网页资源等

需求:基于 Web 服务,开发提供网页资源的功能,了解下后端的代码工作过程

步骤:

  1. 基于 http 模块,创建 Web 服务
  2. 使用 req.url 获取请求资源路径为 /index.html 的时候,读取 index.html 文件内容字符串返回给请求方
  3. 其他路径,暂时返回不存在的提示
  4. 运行 Web 服务,用浏览器发起请求

代码如下:

/**
 * 目标:编写 web 服务,监听请求的是 /index.html 路径的时候,返回 dist/index.html 时钟案例页面内容
 * 步骤:
 *  1. 基于 http 模块,创建 Web 服务
 *  2. 使用 req.url 获取请求资源路径,并读取 index.html 里字符串内容返回给请求方
 *  3. 其他路径,暂时返回不存在提示
 *  4. 运行 Web 服务,用浏览器发起请求
 */
const fs = require('fs')
const path = require('path')
// 1. 基于 http 模块,创建 Web 服务
const http = require('http')
const server = http.createServer()
server.on('request', (req, res) => {
  // 2. 使用 req.url 获取请求资源路径,并读取 index.html 里字符串内容返回给请求方
  if (req.url === '/index.html') {
    fs.readFile(path.join(__dirname, 'dist/index.html'), (err, data) => {
      res.setHeader('Content-Type', 'text/html;charset=utf-8')
      res.end(data.toString())
    })
  } else {
    // 3. 其他路径,暂时返回不存在提示
    res.setHeader('Content-Type', 'text/html;charset=utf-8')
    res.end('你要访问的资源路径不存在')
  }
})
server.listen(8080, () => {
  console.log('Web 服务启动了')
})

小结

  1. Web 服务程序都有什么功能?

    答案
    • 提供数据和网页资源等等功能,其他它的功能远不止于此

二. Node.js模块化

1. 模块化简介

了解模块化概念和好处,以及 CommonJS 标准语法导出和导入

定义:在 Node.js 中,每个文件都视为一个单独的模块。

概念:项目是由多个模块文件组成的

好处:提高代码复用性,按需加载,独立作用域

使用:需要标准语法导出和导入才进行使用

  • 因为模块内的属性和函数都是私有的,如果对外使用,需要使用标准语法导出和导入才可以,而这个标准叫 CommonJS 标准

image.png

CommonJS 标准

需求:定义 utils.js 模块,封装基地址和求数组总和的函数,导入到 index.js

使用:

  1. 导出语法:

    module.exports = {
      对外属性名: 模块内私有变量
    }
    
  2. 导入语法:

    const 变量名 = require('模块名或路径')
    // Node.js 环境内置模块直接写模块名(例如:fs,path,http)
    // 自定义模块:写模块文件路径(例如:./utils.js)
    

    变量名的值接收的就是目标模块导出的对象

模块名或路径:

  • 内置模块:直接写名字(例如:fs,path,http)
  • 自定义模块:写模块文件路径(例如:./utils.js)

代码实现

  • utils.js:导出

    /**
     * 目标:基于 CommonJS 标准语法,封装属性和方法并导出
     */
    const baseURL = 'http://hmajax.itheima.net'
    const getArraySum = arr => arr.reduce((sum, item) => sum += item, 0)
    
    // 导出
    module.exports = {
      url: baseURL,
      arraySum: getArraySum
    }
    
  • index.js:导入使用

    /**
     * 目标:基于 CommonJS 标准语法,导入工具属性和方法使用
     */
    // 导入
    const obj = require('./utils.js')
    console.log(obj)
    const result = obj.arraySum([5, 1, 2, 3])
    console.log(result)
    
小结
  1. Node.js 中什么是模块化?

    答案
    • 每个文件都是独立的模块
  2. 模块之间如何联系呢?

    答案
    • 使用特定语法,导出和导入使用
  3. CommonJS 标准规定如何导出和导入模块?

    答案
    • 导出:module.exports = {},
    • 导入:require('模块名或路径')
  4. 模块名/路径如何选择?

    答案
    • 内置模块:直接写名字。例如:fs,path,http等。
    • 自定义模块:写模块文件路径,例如:./utils.js

2. ECMAScript标准-默认导出和导入

掌握 ECMAScript 标准语法中,默认导出和导入的使用

CommonJS 规范是 Node.js 环境中默认的,后来官方推出 ECMAScript 标准语法,我们接下来在一个需求中,体验下这个标准中默认导出和导入的语法要如何使用

需求:封装并导出基地址和求数组元素和的函数

默认标准使用:

  • 导出语法:

    export default {
      对外属性名: 模块内私有变量
    }
    
  • 导入语法:

    import 变量名 from '模块名或路径'
    

注意:Node.js 默认只支持 CommonJS 标准语法,如果想要在当前项目环境下使用 ECMAScript 标准语法,请新建 package.json 文件并设置 type: 'module'

package.json

{ "type": "module" }

代码实现:

  • utils.js:导出

    /**
     * 目标:基于 ECMAScript 标准语法,封装属性和方法并"默认"导出
     */
    const baseURL = 'http://hmajax.itheima.net'
    const getArraySum = arr => arr.reduce((sum, item) => sum += item, 0)
    
    // 默认导出
    export default {
      url: baseURL,
      arraySum: getArraySum
    }
    
  • index.js:导入

    /**
     * 目标:基于 ECMAScript 标准语法,"默认"导入,工具属性和方法使用
     */
    // 默认导入
    import obj from './utils.js'
    console.log(obj)
    const result = obj.arraySum([10, 20, 30])
    console.log(result)
    
小结
  1. ECMAScript 标准规定如何默认导出和导入模块?

    答案
    • 导出:export default {}
    • 导入:import 变量名 from '模块名或路径'
  2. 如何让 Node.js 切换模块标准为 ECMAScript?

    答案
    • 运行模块所在文件夹,新建 package.json 并设置 {“type”:“module”}

3. ECMAScript标准-命名导出和导入

掌握 ECMAScript 标准语法中,命名导出和导入的使用

ECMAScript 标准的语法有很多,常用的就是默认和命名导出和导入,这节课我们来学习下命名导出和导入的使用

需求:封装并导出基地址和数组求和函数

命名标准使用:

  • 命名导出语法:

    export 修饰定义语句
    
  • 命名导入语法:

    import { 同名变量 } from '模块名或路径'
    

代码示例:

  • utils.js 导出

    /**
     * 目标:基于 ECMAScript 标准语法,封装属性和方法并"命名"导出
     */
    export const baseURL = 'http://hmajax.itheima.net'
    export const getArraySum = arr => arr.reduce((sum, item) => sum += item, 0)
    
    
  • index.js 导入

    /**
     * 目标:基于 ECMAScript 标准语法,"命名"导入,工具属性和方法使用
     */
    // 命名导入
    import {baseURL, getArraySum} from './utils.js'
    console.log(obj)
    console.log(baseURL)
    console.log(getArraySum)
    const result = getArraySum([10, 21, 33])
    console.log(result)
    

与默认导出如何选择:

*   按需加载,使用命名导出和导入
*   全部加载,使用默认导出和导入
小结
  1. Node.js 支持哪 2 种模块化标准?

    答案
    • CommonJS 标准语法(默认)
    • ECMAScript 标准语法
  2. ECMAScript 标准,命名导出和导入的语法?

    答案
    • 导出:export 修饰定义的语句
    • 导入:import { 同名变量 } from '模块名或路径'
  3. ECMAScript 标准,默认导出和导入的语法?

    答案
    • 导出:export default {}
    • 导入:import 变量名 from '模块名或路径'
  4. ECMAScript 标准,命名导出和与默认导出如何选择?

    答案
    • 按需加载,使用命名导出和导入
    • 全部加载,使用默认导出和导入

4.包的概念

包:将模块,代码,其他资料整合成一个文件夹,这个文件夹就叫包

包分类:

  • 项目包:主要用于编写项目和业务逻辑
  • 软件包:封装工具和方法进行使用

包要求:根目录中,必须有 package.json 文件(记录包的清单信息)

包使用:导入软件包时,引入的默认是 index.js 模块文件 / main 属性指定的模块文件(在引入一个包文件夹到代码中,默认引入的是包文件节下的 index.js 模块文件里导出的对象,如果没有 index.js 文件,则会引入 package.json 里 main 属性指定的文件模块导出的对象)

需求:封装数组求和函数的模块,封装判断用户名和密码长度函数的模块,形成一个软件包

代码示例:

/**
 * 本文件是,utils 工具包的唯一出口
 * 作用:把所有工具模块方法集中起来,统一向外暴露
 */
const { getArraySum } = require('./lib/arr.js')
const { checkUser, checkPwd } = require('./lib/str.js')

// 统一导出所有函数
module.exports = {
  getArraySum,
  checkUser,
  checkPwd
}

  • index.js 导入软件包文件夹使用(注意:这次导入的是包文件夹,不是模块文件)
/**
 * 目标:导入 utils 软件包,使用里面封装的工具函数
 */
const obj = require('./utils')
console.log(obj)
const result = obj.getArraySum([10, 20, 30])
console.log(result)
小结
  1. 什么是包?

    答案
    • 将模块,代码,其他资料聚合成的文件夹
  2. 包分为哪 2 类呢?

    答案
    • 项目包:编写项目代码的文件夹,
    • 软件包:封装工具和方法供开发者使用
  3. package.json 文件的作用?

    答案
    • 记录软件包的名字,作者,入口文件等信息
  4. 导入一个包文件夹的时候,导入的是哪个文件?

    答案
    • 默认 index.js 文件,或者 package.json里面 main 属性指定的文件

5. npm软件包管理器

掌握使用 npm 管理软件包

npm 简介链接: 是 Node.js 标准的软件包管理器,用于下载和管理 Node.js 环境中的软件包

npm 使用步骤:

  1. 初始化清单文件: npm init -y (得到 package.json 文件,有则跳过此命令)

    注意 -y 就是所有选项用默认值,所在文件夹不要有中文/特殊符号,建议英文和数字组成,因为 npm 包名限制建议用英文和数字或者下划线中划线

  2. 下载软件包:npm i 软件包名称

  3. 使用软件包

image.png

小结
  1. npm 软件包管理器作用?

    答案
    • 下载软件包以及管理版本
  2. 初始化项目清单文件 package.json 命令?

    答案
    • npm init -y
  3. 下载软件包的命令?

    答案
    • npm i 软件包名字
  4. 下载的包会存放在哪里?

    答案
    • 当前项目下的 node_modules 中,并记录在 package.json 中

6. npm安装所有依赖

问题:我们拿到了一个别人编写的项目,但是没有 node_modules,项目能否正确运行?

答案:不能,因为缺少了项目需要的依赖软件包,比如要使用 dayjs 和 lodash 但是你项目里没有这个对应的源码,项目会报错的

为何没有给我 node_modules?

  • 因为每个人在自己的本机使用 npm 下载,要比磁盘间传递要快(npm 有缓存在本机)

如何得到需要的所有依赖软件包呢?

  • 直接在项目终端运行命令:npm i 即可安装 package.json 里记录的所有包和对应版本到本项目中的 node_modules
小结
  1. 当前项目中只有 package.json 没有 node_modules 怎么办?

    答案
    • 当前项目目录下,打开终端,执行 npm i 安装所有依赖软件包
  2. 为什么 node_modules 不进行传递?

    答案
    • 因为用 npm 下载有缓存在本机,比磁盘之间传递要快

7. npm全局软件包-nodemon

掌握本地软件包和全局软件包区别,体验 nodemon 的使用

软件包区别:

  • 本地软件包:当前项目内使用,封装属性和方法,存在于 node_modules
  • 全局软件包:本机所有项目使用,封装命令和工具,存在于系统设置的位置

nodemon 作用:替代 node 命令,检测代码更改,自动重启程序

使用:

  1. 安装:npm i nodemon -g (-g 代表安装到全局环境中)
  2. 运行:nodemon 待执行的目标 js 文件
小结
  1. 本地软件包和全局软件包区别?

    答案
    • 本地软件包,作用在当前项目,封装属性和方法
    • 全局软件包,本地所有项目使用,封装命令和工具
  2. nodemon 作用?

    答案
    • 替代 node 命令,检测代码更改,自动重启程序
  3. nodemon 怎么用?

    答案
    • 先确保安装 npm i nodemon -g
    • 使用 nodemon 执行目标 js 文件

8. Node.js概念和常用命令总结

把上面学的模块化语法,包的概念,常用命令进行总结

Node.js 模块化

  • 概念:把每个文件当做一个模块,独立作用域,按需加载

  • 使用:使用特定的标准语法导出和导入使用

    CommonJS 标准:一般应用在 Node.js 项目环境中

    ECMAScript 标准:一般应用在前端工程化项目中

image.png

Node.js 包

  • 概念:把模块文件,代码文件,其他资料聚合成一个文件夹就是包

    项目包:编写项目需求和业务逻辑的文件夹

    软件包:封装工具和方法进行使用的文件夹(一般使用 npm 管理)

    • 本地软件包:作用在当前项目,封装的属性/方法,供项目调用编写业务需求
    • 全局软件包:作用在所有项目,一般封装的命令/工具,支撑项目运行

image.png

Node.js 常用命令

image.png

小结
  1. 安装本地软件包的命令是什么?

    答案
    • npm i 软件包名

3. Webpack模块打包工具

1. Webpack 简介以及体验

了解 Webpack 的概念和作用,以及使用

定义:Webpack 是一个静态模块打包工具,从入口构建依赖图,打包有关的模块,最后用于展示你的内容

静态模块:编写代码过程中的 html,css, js,图片等固定内容的文件

打包过程,注意:只有和入口有直接/间接引入关系的模块,才会被打包

Webpack 的作用/打包:把静态模块内容,压缩,整合,转译等(前端工程化)

  • 把 less/sass 转成 css 代码
  • 把 ES6+ 降级成 ES5 等
  • 支持多种模块标准语法

image.png

为何不学 vite?

  • 现在很多项目还是基于 Webpack 来进行构建的,所以还是要掌握 Webpack 的使用

需求:封装 utils 包,校验手机号和验证码长度,在 src/index.js 中使用,使用 Webpack 打包

步骤:

  1. 新建并初始化项目:新建项目文件夹 Webpack_study,初始化包环境,得到 package.json 文件

    npm init -y
    
  2. 编写业务源代码:新建 src 源代码文件夹(书写代码)包括 utils/check.js 封装用户名和密码长度函数,引入到 src/index.js 进行使用

    • src/utils/check.js

      // 封装校验手机号长度和校验验证码长度的函数
      export const checkPhone = phone => phone.length === 11
      export const checkCode = code => code.length === 6
      
    • src/index.js

      /**
       * 目标1:体验 webpack 打包过程
       */
      // 1.1 准备项目和源代码
      import { checkPhone, checkCode } from './utils/check.js'
      console.log(checkPhone('13900002020'))
      console.log(checkCode('123123123123'))
      // 1.2 准备 webpack 打包的环境
      // 1.3 运行自定义命令打包观察效果(npm run 自定义命令)
      
  3. 下载 webpack webpack-cli 到当前项目中(版本独立),并配置局部自定义命令

    npm i webpack webpack-cli --save-dev
    
    //package.json中配置 build自定义命令
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "build": "webpack"
      },
    

    注意:虽然 webpack 是全局软件包,封装的是命令工具,但是为了保证项目之间版本分别独立,所以这次比较特殊,下载到某个项目环境下,但是需要把 webpack 命令配置到 package.json 的 scripts 自定义命令,作为局部命令使用

  4. 运行打包命令:项目中运行工具命令,采用自定义命令的方式(局部命令)

    npm run build
    

    npm run 自定义命令名字

    注意:实际上在终端运行的是 build 右侧的具体命名

    注意:Node.js 默认只支持 CommonJS 标准语法,想要在当前项目环境下使用 ECMAScript 标准语法(import/export),需要在 package.json 文件设置 type: 'module'(注意浏览器也不支持ES6模块化的js代码,打包后的代码才能在浏览器上运行)

    从 webpack2 开始,已经内置了对 ES6、CommonJS、AMD 模块化语句的支持。但不包括新的ES6语法转为ES5代码,这部分工作还是留给了babel及其插件。

    webpack对于ES模块/CommonJS模块的实现,是基于自己实现的webpack_require,所以打包后的代码能跑在浏览器中。

    webpack根据webpack.config.js(wepack的配置文件,下一小节就会提到,目前用的是默认配置)中的入口文件,在入口文件里识别模块依赖,不管这里的模块依赖是用CommonJS写的,还是ES6 Module规范写的,webpack会自动进行分析,并通过转换、编译代码,打包成最终的文件。最终文件中的模块实现是基于webpack自己实现的webpack_require(es5代码),所以打包后的文件可以跑在浏览器上。

  5. 自动产生 dist 分发文件夹(压缩和优化后,用于最终运行的代码)

需求最终流程图:

image.png

小结

  1. Webpack 有什么用?

    答案
    • 压缩,转译,整合,打包我们的静态模块
  2. Webpack 怎么用?

    答案
    • 初始化环境,编写代码,安装 Webpack 软件包,配置自定义命令,打包体验查看结果
  3. 如何运行 package.json 里的自定义命令?

    答案
    • npm run 自定义命令
  4. Webpack 默认入口和出口?

    答案
    • src/index.js 和 dist/main.js

2. Webpack 修改打包入口和出口

了解 Webpack 配置文件使用,影响 Webpack 打包过程和结果

Webpack 配置:影响 Webpack 打包过程和结果

步骤:

  1. 项目根目录,新建 webpack.config.js 配置文件

  2. 导出配置对象,配置入口,出口文件路径(别忘了修改磁盘文件夹和文件的名字)

    const path = require('path')
    
    module.exports = {
      entry: path.resolve(__dirname, 'src/login/index.js'),
      output: {
        path: path.resolve(__dirname, 'dist'),
        filename: './login/index.js', //注意:这里是login文件夹下的index.js
        clean: true // 生成打包后内容之前,清空输出目录
      }
    }
    
  3. 重新打包观察

注意:只有和入口产生直接/间接的引入关系,才会被打包

小结

  1. 如何影响 Webpack 打包过程?

    答案
    • 查文档,新建配置文件和配置属性

3. 案例-用户登录-长度判断

体验前端项目代码,如何被 Webpack 打包和使用

需求:点击登录按钮,判断手机号和验证码长度是否符合要求

步骤:

  1. 准备用户登录页面:新建 public/login.html 准备网页模板(方便查找标签和后期自动生成 html 文件做准备)

  2. 编写核心 JS 逻辑代码:核心 JS 代码写在 src/login/index.js 文件

    /**
     * 目标3:用户登录-长度判断案例
     *  3.1 准备用户登录页面
     *  3.2 编写核心 JS 逻辑代码
     *  3.3 打包并手动复制网页到 dist 下,引入打包后的 js,运行
     */
    // 3.2 编写核心 JS 逻辑代码
    document.querySelector('.btn').addEventListener('click', () => {
      const phone = document.querySelector('.login-form [name=mobile]').value
      const code = document.querySelector('.login-form [name=code]').value
    
      if (!checkPhone(phone)) {
        console.log('手机号长度必须是11位')
        return
      }
    
      if (!checkCode(code)) {
        console.log('验证码长度必须是6位')
        return
      }
    
      console.log('提交到服务器登录...')
    })
    
  3. 打包并手动复制网页到 dist 下,引入打包后的 js,运行:运行自定义命令,让 webpack 打包 JS 代码;手动复制 public/login.html 到 dist 下,手动引入打包后的 JS 代码文件,运行 dist/login.html 在浏览器查看效果

    注意:webpack默认只能处理 jsjson 资源,所以这里需要将打包后的js代码引入html里面查看效果,后续可以使用webpack插件让其支持更多类型

核心:Webpack 打包后的代码,作用在前端网页中使用

image.png

小结

  1. Webpack 打包后的前端代码是如何运行的?

    答案
    • 手动引入到 html 文件中,再交给浏览器运行

4. Webpack 自动生成 html 文件

让 Webpack 拥有自动生成 html 文件能力,并引入打包后的其他资源

插件 html-webpack-plugin 作用:在 Webpack 打包时生成 html 文件,并引入其他打包后的资源

步骤:

  1. 下载 html-webpack-plugin 本地软件包到项目中

    npm i html-webpack-plugin --save-dev
    
  2. 配置 webpack.config.js 让 Webpack 拥有插件功能

    // ...
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {
      // 插件(给webpack提供更多功能)
      plugins: [
        //注意:output生成的js文件会自动加到html里面
        new HtmlWebpackPlugin({
          template: path.resolve(__dirname, "public/login.html"), // 模板文件
          filename: path.resolve(__dirname, "dist/login/index.html") // 输出文件,注意最好以output.path开头
        })
      ]
    }
    
  3. 重新打包观察效果:指定以 public/login.html 为模板复制到 dist/login/index.html,并自动引入其他打包后资源;运行打包命令,观察打包后 dist 文件夹下内容并运行查看效果

小结

  1. html-webpack-plugin 插件怎么用?

    答案
    • 找到插件文档,下载到项目中,配置到 Webpack 的配置文件中即可使用

5. Webpack-打包 css 代码

注意:Webpack 默认只识别 JS 和 JSON 文件内容,所以想要让 Webpack 识别更多不同内容,需要使用加载器

介绍需要的 2 个加载器来辅助 Webpack 才能打包 css 代码

步骤:

  1. 准备 css 文件代码引入到 src/login/index.js 中(压缩转译处理等)

    /**
     * 目标5:打包 css 代码
     *  5.1 准备 css 代码,并引入到 js 中
     *  5.2 下载 css-loader 和 style-loader 本地软件包
     *  5.3 配置 webpack.config.js 让 Webpack 拥有该加载器功能
     *  5.4 打包后观察效果
     */
    // 5.1 准备 css 代码,并引入到 js 中
    import 'bootstrap/dist/css/bootstrap.min.css'
    import './index.css'
    

    注意:这里只是引入代码内容让 Webpack 处理,不需定义变量接收在 JS 代码中继续使用,所以没有定义变量接收

  2. 下载 css-loader 和 style-loader 本地软件包

    npm i css-loader style-loader --save-dev
    
  3. 配置 webpack.config.js 让 Webpack 拥有该加载器功能

    // ...
    
    module.exports = {
      // ...
      module: { // 加载器
        rules: [ // 规则列表
          {
            test: /\.css$/i, // 匹配 .css 结尾的文件
            use: ['style-loader', 'css-loader'], // 使用从后到前的加载器来解析 css 代码和插入到 DOM
          }
        ]
      }
    };
    
  4. 打包后观察效果:打包后运行 dist/login/index.html 观察效果,看看准备好的样式是否作用在网页上

小结

  1. 加载器的作用是什么?

    答案
    • 让 Webpack 识别更多的代码内容类型

6. 优化-提取 css 代码

让 Webpack 能够提取 css 代码到独立的 css 文件中

需求:让 webpack 把 css 代码内容字符串单独提取到 dist 下的 css 文件中

需要:mini-css-extract-plugin 插件来实现

插件 mini-css-extract-plugin:提取 css 代码

步骤:

  1. 下载 mini-css-extract-plugin 插件软件包到本地项目中

    npm i --save-dev mini-css-extract-plugin
    
  2. 配置 webpack.config.js 让 Webpack 拥有该插件功能

    // ...
    const MiniCssExtractPlugin = require("mini-css-extract-plugin")
    
    module.exports = {
      // ...
      module: {
        rules: [
          {
            test: /\.css$/i,
            // use: ['style-loader', 'css-loader']
            use: [MiniCssExtractPlugin.loader, "css-loader"],
          },
        ],
      },
      plugins: [
        // ...
        new MiniCssExtractPlugin()
      ]
    };
    
  3. 打包后观察效果

注意:不能和 style-loader 一起使用

好处:css 文件可以被浏览器缓存,减少 JS 文件体积,让浏览器并行下载 css 和 js 文件

注意:

plugins: [
    // 不设置输出路径,默认是 dist/main.css
    new MiniCssExtractPlugin()
]
plugins: [
    // 若设置输出路径,需要是相对路径,否则打包时会报如下所示错误
    //相对路径,是相对dist下的路径
    new MiniCssExtractPlugin({
      filename: "./login/index.css"
    })
]

image.png

7. 优化-压缩过程

把单独提取的 css 文件内代码压缩

问题:css 代码提取后没有压缩

解决:使用 css-minimizer-webpack-plugin 插件

步骤:

  1. 下载 css-minimizer-webpack-plugin 插件软件包到本地项目中

    npm i css-minimizer-webpack-plugin --save-dev 
    
  2. 配置 webpack.config.js 让 Webpack 拥有该插件功能

    // ...
    const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
    
    module.exports = {
      // ...
      // 优化
      optimization: {
        // 最小化
        minimizer: [
          // 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 
          // `terser-webpack-plugin`),将下一行取消注释(保证 JS 代码还能被压缩处理)
          `...`,
          new CssMinimizerPlugin(),
        ],
      }
    };
    
  3. 打包后观察 css 文件内自己代码是否被压缩了

8. Webpack-打包 less 代码

加载器 less-loader:把 less 代码编译为 css 代码

注意:less-loader 需要配合 less 软件包使用

步骤:

  1. 新建 login/index.less 文件,设置背景图样式

    html {
      body {
        background: url('./assets/login-bg.png') no-repeat center/cover;
      }
    }
    
  2. less 样式引入到 src/login/index.js 中

/**
 * 目标:打包 less 代码
 *  8.1 新建 less 代码(设置背景图)并引入到 src/login/index.js 中
 *  8.2 下载 less 和 less-loader 本地软件包
 *  8.3 配置 webpack.config.js 让 Webpack 拥有功能
 *  8.4 打包后观察效果
 */
// 8.1 新建 less 代码(设置背景图)并引入到 src/login/index.js 中
import './index.less'
  1. 下载 less 和 less-loader 本地软件包

    npm i less less-loader --save-dev
    
  2. 配置 webpack.config.js 让 Webpack 拥有功能

    // ...
    
    module.exports = {
      // ...
      module: {
        rules: [
          // ...
          {
            test: /\.less$/i,
            use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"]
          }
        ]
      }
    }
    
  3. 打包后运行 观察效果

小结

  1. Webpack 支持 less 代码打包需要哪 2 个软件包?

    答案
    • 需要 less less-loader 这 2 个软件包

9. Webpack-打包图片

资源模块:Webpack5 内置资源模块(字体,图片等)打包,无需额外 loader

步骤:

  1. 配置 webpack.config.js 让 Webpack 拥有打包图片功能

    占位符 【hash】对模块内容做算法计算,得到映射的数字字母组合的字符串

    占位符 【ext】使用当前模块原本的占位符,例如:.png / .jpg 等字符串

    占位符 【query】保留引入文件时代码中查询参数(只有 URL 下生效)

  2. 打包后观察效果和区别

    注意:判断临界值默认为 8KB

    大于 8KB 文件:发送一个单独的文件并导出 URL 地址

    小于 8KB 文件:导出一个 data URI(base64字符串)

  3. 在 src/login/index.js 中给 img 标签添加 logo 图片

    /**
     * 目标9:打包资源模块(图片处理)
     *  9.1 创建 img 标签并动态添加到页面,配置 webpack.config.js
     *  9.2 打包后观察效果和区别
     */
    // 9.1 创建 img 标签并动态添加到页面,配置 webpack.config.js
    // 注意:js 中引入本地图片资源要用 import 方式(如果是网络图片http地址,字符串可以直接写)
    import imgObj from './assets/logo.png'
    const theImg = document.createElement('img')
    theImg.src = imgObj
    document.querySelector('.login-wrap').appendChild(theImg)
    
  4. 配置 webpack.config.js 让 Webpack 拥有打包图片功能

    // ...
    
    module.exports = {
      // ...
      module: {
        rules: [
          // ...
          {
            test: /\.(png|jpg|jpeg|gif)$/i,
            type: 'asset',
            generator: {
              filename: 'assets/[hash][ext][query]'
            }
          }
        ]
      }
    }
    

小结

  1. 资源模块指的是什么?

    答案
    • 图片,字体文件等等

10.案例-用户登录-完成功能

在 Webpack 环境下,使用 npm 下包作用在前端项目

  1. 需求:点击登录按钮,基于 npm 下载 axios 包,完成验证码登录功能

  2. 步骤:

    1. 使用 npm 下载 axios

      npm i axios
      
    2. 引入到 src/login/index.js 中编写业务实现

      /**
       * 目标10:完成登录功能
       *  10.1 使用 npm 下载 axios(体验 npm 作用在前端项目中)
       *  10.2 准备并修改 utils 工具包源代码导出实现函数
       *  10.3 导入并编写逻辑代码,打包后运行观察效果
       */
      // 10.3 导入并编写逻辑代码,打包后运行观察效果
      import myAxios from '../utils/request.js'
      import { myAlert } from '../utils/alert.js'
      document.querySelector('.btn').addEventListener('click', () => {
        const phone = document.querySelector('.login-form [name=mobile]').value
        const code = document.querySelector('.login-form [name=code]').value
      
        if (!checkPhone(phone)) {
          myAlert(false, '手机号长度必须是11位')
          console.log('手机号长度必须是11位')
          return
        }
      
        if (!checkCode(code)) {
          myAlert(false, '验证码长度必须是6位')
          console.log('验证码长度必须是6位')
          return
        }
      
        myAxios({
          url: '/v1_0/authorizations',
          method: 'POST',
          data: {
            mobile: phone,
            code: code
          }
        }).then(res => {
          myAlert(true, '登录成功')
          localStorage.setItem('token', res.data.token)
          location.href = '../content/index.html'
        }).catch(error => {
          myAlert(false, error.response.data.message)
        })
      })
      
    3. 打包后运行观察效果

小结

  1. npm 下载的包如何作用在前端项目上?

    答案
    • 被 Webpack 打包处理后,再引入到 html 文件中运行

10. Webpack 搭建开发环境

体验 webpack-dev-server 开发服务器,快速开发应用程序

问题:每次改动代码,都要重新打包,很麻烦,效率很低

开发环境:配置 webpack-dev-server 快速开发应用程序

作用:启动 Web 服务,打包输出源码在内存,并会自动检测代码变化,热更新到网页

步骤;

  1. 下载 webpack-dev-server 软件包到当前项目

    npm i webpack-dev-server --save-dev
    
  2. 设置打包的模式为开发模式,并配置自定义命令

    // ...
    
    module.exports = {
      // ...
      mode: 'development'
    }
    
    "scripts": {
      // ...
      "dev": "webpack serve --open
      //"dev": "webpack serve --mode=development"
    },
    
  3. 使用 npm run dev 来启动开发服务器,访问提示的域名+端口号,在浏览器访问打包后的项目网页,修改代码后试试热更新效果

    在 js / css 文件中修改代码保存后,会实时反馈到浏览器

注意

  • webpack-dev-server 借助 http 模块创建 8080 默认 Web 服务

  • 默认以 public 文件夹作为服务器根目录

  • webpack-dev-server 根据配置,打包相关代码在内存当中,以 output.path 的值作为服务器根目录(所以可以直接自己拼接访问 dist 目录下内容)

小结

  1. webpack-dev-server 的作用?

    答案
    • 启动 Webpack 开发服务器,会启动一个 Web 服务,实时检测代码变化重新打包,并快速反应最新效果到浏览器页面上

11. Webpack 打包模式

了解不同打包模式对代码和环境的影响

  1. 打包模式:告知 Webpack 使用相应模式的内置优化

  2. 分类:

    模式名称模式名字特点场景
    开发模式development调试代码,实时加载,模块热替换等本地开发
    生产模式production压缩代码,资源优化,更轻量等打包上线
  3. 如何设置影响 Webpack呢?

    • 方式1:在 webpack.config.js 配置文件设置 mode 选项

      // ...
      
      module.exports = {
        // ...
        mode: 'production'
      }
      
    • 方式2:在 package.json 命令行设置 mode 参数

      "scripts": {
        "build": "webpack --mode=production",
        "dev": "webpack serve --mode=development"
      },
      
  4. 注意:命令行设置的优先级高于配置文件中的,推荐用命令行设置

小结

  1. 两种模式的区别?

    答案
    • 开发模式注重代码热替换更快,让开发调试代码更便捷,生产模式注重项目体积更小,更轻量,适配不同的浏览器环境

12. Webpack 打包模式的应用

了解 Webpack 打包模式的应用

需求:在开发模式下用 style-loader 内嵌更快,在生产模式下提取 css 代码

  • 方案1:webpack.config.js 配置导出函数,但是局限性大(只接受 2 种模式)

  • 方案2:借助 cross-env (跨平台通用)包命令,设置参数区分环境

  • 方案3:配置不同的 webpack.config.js (适用多种模式差异性较大情况)

其中方案2 步骤:

  1. 下载 cross-env 软件包到当前项目
npm i cross-env --save-dev
  1. 配置自定义命令,传入参数名和值(会绑定到 process.env 对象下)
"scripts": {
    "build": "cross-env NODE_ENV=production webpack --mode=production",
    "dev": "cross-env NODE_ENV=development webpack serve --open --mode=development"
  },
  1. 在 webpack.config.js 区分不同环境使用不同配置
module: {
    rules: [
      {
        test: /\.css$/i,
        // use: ['style-loader', "css-loader"],
        use: [process.env.NODE_ENV === 'development' ? 'style-loader' : MiniCssExtractPlugin.loader, "css-loader"]
      },
      {
        test: /\.less$/i,
        use: [
          // compiles Less to CSS
          process.env.NODE_ENV === 'development' ? 'style-loader' : MiniCssExtractPlugin.loader,
          'css-loader',
          'less-loader',
        ],
      }
    ],
  },
  1. 重新打包观察两种配置区别

13. Webpack 前端注入环境变量

前端项目中,开发模式下打印语句生效,生产模式下打印语句失效

需求:前端项目中,开发模式下打印语句生效,生产模式下打印语句失效

问题:cross-env 设置的只在 Node.js 环境生效,前端代码无法访问 process.env.NODE_ENV

解决:使用 Webpack 内置的 DefinePlugin 插件

作用:在编译时,将前端代码中匹配的变量名,替换为值或表达式

配置 webpack.config.js 中给前端注入环境变量

// ...
const webpack = require('webpack')

module.exports = {
  // ...
  plugins: [
    // ...
    new webpack.DefinePlugin({
      // key 是注入到打包后的前端 JS 代码中作为全局变量
      // value 是变量对应的值(在 corss-env 注入在 node.js 中的环境变量字符串)
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
    })
  ]
}

14. Webpack 开发环境调错 source map

在开发环境如何精准定位到报错源码位置

问题:代码被压缩和混淆,无法正确定位源代码位置(行数和列数)

source map:可以准确追踪 error 和 warning 在原始代码的位置

设置:webpack.config.js 配置 devtool 选项

// 仅用于开发环境

module.exports = {
  // ...
  devtool: 'inline-source-map'
}

// 最好写成
//config里面是所有配置
const config = {...}
if (process.env.NODE_ENV === "development") {
  config.devtool = "inline-source-map";
}
module.exports = config;

inline-source-map 选项:把源码的位置信息一起打包在 JS 文件内

注意:source map 适用于开发环境,不要在生产环境使用(防止被轻易查看源码位置)

小结

  1. 为何打包后,在控制台无法准确定位到源码的位置信息?

    答案
    • 因为 Webpack 把代码压缩和混淆了

15. Webpack 设置解析别名路径

设置 Webpack 如何设置路径别名,方便我们引入目标模块

解析别名:配置模块如何解析,创建 import 或 require 的别名,来确保模块引入变得更简单

例如:原来路径如图,比较长而且相对路径不安全

解决:在 webpack.config.js 中配置解析别名 @ 来代表 src 绝对路径

例如:

  1. 原来路径如下:

    import { checkPhone, checkCode } from '../src/utils/check.js'
    
  2. 配置解析别名:在 webpack.config.js 中设置

    // ...
    
    const config = {
      // ...
      resolve: {
        alias: {
          '@': path.resolve(__dirname, 'src')
        }
      }
    }
    
  3. 这样我们以后,引入目标模块写的路径就更简单了

    import { checkPhone, checkCode } from '@/utils/check.js'
    
  4. 修改代码的路径后,重新打包观察效果是否正常!

小结

  1. 路径中的 '@' 符号代表什么意思?

    答案
    • 看在 webpack 配置中的别名路径是什么,就会在打包时替换成哪个路径使用

16. 优化-CDN使用

开发模式使用本地第三方库,生产模式下使用 CDN 加载引入

CDN定义:内容分发网络,指的是一组分布在各个地区的服务器

作用:把静态资源文件/第三方库放在 CDN 网络中各个服务器中,供用户就近请求获取

好处:减轻自己服务器请求压力,就近请求物理延迟低,配套缓存策略

需求:开发模式使用本地第三方库,生产模式下使用 CDN 加载引入

image.png

步骤:

  1. 在 html 中引入第三方库的 CDN 地址 并用模板语法判断
<% if(htmlWebpackPlugin.options.useCdn){ %>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
<% } %>
  1. 配置 webpack.config.js 中 externals 外部扩展选项(防止某些 import 的包被打包)
// 生产环境下使用相关配置
if (process.env.NODE_ENV === 'production') {
  // 外部扩展(让 webpack 防止 import 的包被打包进来)
  config.externals = {
    // key:import from 语句后面的字符串
    // value:留在原地的全局变量(最好和 cdn 在全局暴露的变量一致)
    'bootstrap/dist/css/bootstrap.min.css': 'bootstrap',
    'axios': 'axios'
  }
}
// ...
const config = {
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      // ...
      // 自定义属性,在 html 模板中
      // 生产模式下使用 cdn 引入的地址
      <%=htmlWebpackPlugin.options.useCdn%> 访问使用
      useCdn: process.env.NODE_ENV === 'production'
    })
  ]
}
  1. 两种模式下打包观察效果

17. Webpack 多页面打包

让 Webpack 同时打包登录和内容列表页面

概念:

  • 单页面:单个 html 文件,切换 DOM 的方式实现不同业务逻辑展示,后续 Vue/React 会学到

  • 多页面:多个 html 文件,切换页面实现不同业务逻辑展示

需求:把黑马头条-数据管理平台-内容页面一起引入打包使用

步骤:

  1. 准备源码(html,css,js)放入相应位置,并改用模块化语法导出

  2. 下载 form-serialize 包并导入到核心代码中使用

  3. 配置 webpack.config.js 多入口和多页面的设置

    // ...
    const config = {
      entry: {
        '模块名1': path.resolve(__dirname, 'src/入口1.js'),
        '模块名2': path.resolve(__dirname, 'src/入口2.js'),
      },
      output: {
        path: path.resolve(__dirname, 'dist'),
        filename: './[name]/index.js'  
      }
      plugins: [
        new HtmlWebpackPlugin({
          template: './public/页面2.html', // 模板文件
          filename: './路径/index.html', // 输出文件
          chunks: ['模块名2']
        })
        new HtmlWebpackPlugin({
          template: './public/页面2.html', // 模板文件
          filename: './路径/index.html', // 输出文件
          chunks: ['模块名2']
        })
      ]
    }
    
  4. 重新打包观察效果

18. 案例-发布文章页面打包

需求:把发布文章页面一起打包

步骤:

  1. 准备发布文章页面源代码,改写成模块化的导出和导入方式

  2. 修改 webpack.config.js 的配置,增加一个入口和出口

  3. 打包观察效果

19. 优化-分割公共代码

需求:把 2 个以上页面引用的公共代码提取

步骤:

  1. 配置 webpack.config.js 的 splitChunks 分割功能

    // ...
    const config = {
      // ...
      optimization: {
        // 代码分割
        splitChunks: {
          chunks: 'all', // 所有模块动态非动态移入的都分割分析
          cacheGroups: { // 分隔组
            commons: { // 抽取公共模块
              minSize: 0, // 抽取的chunk最小大小字节
              minChunks: 2, // 最小引用数
              reuseExistingChunk: true, // 当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用
              name(module, chunks, cacheGroupKey) { // 分离出模块文件名
                const allChunksNames = chunks.map((item) => item.name).join('~') // 模块名1~模块名2
                return `./js/${allChunksNames}` // 输出到 dist 目录下位置
              }
            }
          }
        }
    
    
    
  2. 打包观察效果

Git 版本控制系统

1. Git 初识

  • 概念:一个免费开源,分布式的代码版本控制系统,帮助开发团队维护代码

  • 作用:记录代码内容,切换代码版本,多人开发时高效合并代码内容

安装

Windows系统:exe 程序,默认下一步即可

Mac系统:dmg 程序,默认下一步即可

检验成功:

1.打开 bash 终端(git 专用)

2.命令:git -v(查看版本号)

配置用户信息

配置:用户名和邮箱,应用在每次提交代码版本时表明自己身份

命令:

git config --global user.name "itheima"

git config --global user.email "itheima@itcast.cn"

总结

  1. 为何学习 Git ?

    管理代码版本,记录,切换,合并代码

  2. Git 学习:

    现在本机自己使用

    再学习多人共享使用

  3. 如何安装使用?

    程序双击安装

    在 VSCode 中使用 bash 终端以及 git 命令

2. Git 仓库

Git 仓库(repository):记录文件状态内容的地方,存储着修改的历史记录

创建:

1.把本地文件夹转换成 Git 仓库:命令 git init

2.从其他服务器上克隆 Git 仓库

总结

  1. 什么是 Git 仓库 ?

    记录文件状态内容和历史记录的地方(.git 文件夹)

  2. 如何创建 Git 仓库?

    把本地文件夹转换成 Git 仓库:命令 git init

    从其他服务器上克隆 Git 仓库

3. Git 的三个区域

Git 使用时:

  • 工作区:实际开发时操作的文件夹

  • 暂存区:保存之前的准备区域(暂存改动过的文件)

  • 版本库:提交并保存暂存区中的内容,产生一个版本快照

命令作用
git add 文件名暂存指定文件
git add .暂存所有改动的文件
git commit -m "注释说明"提交并保存,产生版本快照

image.png

git status -s 查看文件状态 git ls-files 查看暂存区文件 git log --oneline 查看版本库提交记录

总结

  1. Git 使用时有哪些区域 ?

    工作区,暂存区,版本库

  2. 工作区的内容,最终要如何保存在版本库中?

    git add 添加到暂存区

    等待时机后 git commit 提交保存到版本库,产生一次版本快照记录

4. Git 文件状态

Git 文件 2 种状态:

  • 未跟踪:新文件,从未被 Git 管理过

  • 已跟踪:Git 已经知道和管理的文件

文件状态概念场景
未跟踪(U/??)从未被 Git 管理过新文件
新添加(A)第一次被 Git 暂存之前版本记录无此文件
未修改('')三个区域统一提交保存后
已修改(M)工作区内容变化修改了内容产生

使用:修改文件,暂存,提交保存记录,如此反复

总结

  1. Git 文件状态分为哪 2 种 ?

    未跟踪和已跟踪(新添加,未修改,已修改)

  2. 如何查看暂存区和工作区文件状态?

    git status -s

5. Git 暂存区作用

暂存区:暂时存储,可以临时恢复代码内容,与版本库解耦

暂存区 -> 覆盖 -> 工作区,命令:git restore 目标文件(注意:完全确认覆盖时使用)

从暂存区移除文件,命令:git rm --cached 目标文件

image.png

总结

  1. 如何移除暂存区已暂存的文件?

    git rm --cached 目标文件

7. Git-版本回退/切换版本

概念:把版本库某个版本对应的内容快照,恢复到工作区/暂存区

查看提交历史:git log --oneline

回退命令:

  • git reset --soft 版本号(其他文件未跟踪)

  • git reset --hard 版本号

  • git reset --mixed 版本号 (与 git reset 等价)

注意1:只有记录在版本库的提交记录才能恢复

注意2:回退后,继续修改->暂存->提交操作即可(产生新的提交记录过程)

image.png

总结

  1. 什么是 Git 回退版本?

    把版本库某个版本对应的内容快照,恢复到工作区/暂存区

  2. 强制覆盖暂存区和工作区的命令?

    git reset --hard 版本号

  3. 如何查看提交历史?

    git log --oneline

    git reflog --oneline

8. 删除文件

需求:删除 editor.js 文件,并产生一次版本记录

步骤:

1.手动删除工作区文件

2.暂存变更/手动删除暂存区文件造成变更

3.提交保存

总结:

工作区只要改变,都可以暂存提交产生新记录

image.png

9. 忽略文件

概念:.gitignore 文件可以让 git 彻底忽略跟踪指定文件

目的:让 git 仓库更小更快,避免重复无意义的文件管理

例如:

1.系统或软件自动生成的文件

2.编译产生的结果文件

3.运行时生成的日志文件,缓存文件,临时文件等

4.涉密文件,密码,秘钥等文件

创建:

1.项目根目录新建 .gitignore 文件

2.填入相应配置来忽略指定文件

注意:如果文件已经被暂存区跟踪过,可以从暂存区移除即可

image.png

10. 分支的概念

概念:本质上是指向提交节点的可变指针,默认名字是 master

注意:HEAD 指针影响工作区/暂存区的代码状态

场景:开发新需求 / 修复 Bug,保证主线代码随时可用,多人协同开发提高效率

例如:在现有代码上创建新分支完成内容列表业务,突然需要紧急修复 Bug - 单独创建分支解决 Bug

需求:创建内容列表 content 分支,并产生 3 次提交记录

步骤:

  1. 创建分支命令:git branch 分支名

  2. 切换分支命令:git checkout 分支名

  3. 工作区准备代码并暂存提交,重复 3 次

总结

  1. 什么是 Git 分支?

    指针,指向提交记录

  2. HEAD 指针的作用?

    影响暂存区和工作区的代码

  3. 如何创建和切换指针?

    git branch 分支名

    git checkout 分支名

11. 练习-登录 bug 修复

需求:新建 login-bug 分支,做 2 次提交记录(对手机号长度,验证码长度做判断)

步骤:

1.切回到主分支:git checkout master

2.创建新分支:git branch login-bug

3.切换新分支:git checkout login-bug

4.修改代码,暂存,提交产生版本记录

12. 分支-合并与删除

需求:把 login-bug 合并回到 master 分支并删除 login-bug 分支

步骤:

1.切回到要合入的分支上:git checkout master

2.合并其他分支过来:git merge login-bug

3.删除合并后的分支指针:git branch -d login-bug

13. 分支-合并与提交

合并提交:发生于原分支产生了新的提交记录后,再合并回去时发生,自动使用多个快照记录合并后产生一次新的提交

步骤:

1.切回到要合入的分支上:git checkout master

2.合并其他分支过来:git merge content

3.删除合并后的分支:git branch -d content

最后合并回到主分支上时,提交记录流程图:

注意:提交记录的顺序按照产生的先后顺序排列,而非合并的先后顺序

14. 分支-合并冲突

需求1:基于 master 新建 publish 分支,完成发布文章业务,然后修改内容页面的 html 文件的 title 标签,并提交一次

需求2:切换到 master,也在修改内容页面的 html 文件的 title 标签,并提交一次

冲突:把 publish 分支合并到 master 回来,产生合并冲突

概念:不同分支中,对同一个文件的同一部分修改,Git 无法干净的合并,产生合并冲突

解决:

1.打开 VSCode 找到冲突文件并手动解决

2.解决后需要提交一次记录

避免:(多交流)

1.按页面划分不同分支开发

2.公共代码在统一文件夹维护

3.Node等软件版本统一,npm 包统一下载

15. Git 常用命令

命令作用注意
git -v查看 git 版本
git init初始化 git 仓库
git add 文件标识暂存某个文件文件标识以终端为起始的相对路径
git add .暂存所有文件
git commit -m '说明注释'提交产生版本记录每次提交,把暂存区内容快照一份
git status查看文件状态 - 详细信息
git status -s查看文件状态 - 简略信息第一列是暂存区状态,第二列是工作区状态
git ls-files查看暂存区文件列表
git restore 文件标识从暂存区恢复到工作区如果文件标识为 . 则恢复所有文件
git rm --cached 文件标识从暂存区移除文件不让 git 跟踪文件变化
git log查看提交记录 - 详细信息
git log --oneline查看提交记录 - 简略信息版本号 分支指针 提交时说明注释
命令作用注意
git reflog --oneline查看完整历史 - 简略消息包括提交,切换,回退等所有记录
git reset 版本号切换版本代码到暂存区和工作区--soft 模式保留暂存区和工作区原本内容 --hard 模式不保留暂存区和工作区原本内容 --mixed 模式不保留暂存区,工作区保留(默认) 先覆盖到暂存区,再用暂存区对比覆盖工作区
git branch 分支名创建分支
git branch查看本地分支
git branch -d 分支名删除分支请确保记录已经合并到别的分支下,再删除分支
git checkout 分支名切换分支
git checkout -b 分支名创建并立刻切换分支
git merge 分支名把分支提交历史记录合并到当前所在分支

16. Git 远程仓库

概念:托管在因特网或其他网络中的你的项目的版本库

作用:保存版本库的历史记录,多人协作

创建:公司自己服务器 / 第三方托管平台(Gitee,GitLab,GitHub...)

需求:创建远程版本库,并把本地 Git 仓库推送上去保存

步骤:

  1. 注册第三方托管平台网站账号

  2. 新建仓库得到远程仓库 Git 地址

  3. 本地 Git 仓库添加远程仓库原点地址

    命令:git remote add 远程仓库别名 远程仓库地址

    例如:git remote add origin gitee.com/lidongxu/wo…

  4. 本地 Git 仓库推送版本记录到远程仓库

    命令:git push -u 远程仓库别名 本地和远程分支名

    例如:git push -u origin master

    完整写法:git push --set-upstream origin master:master

总结

  1. 远程版本库的作用?

    保存提交历史记录,多人共享

  2. 远程版本库使用步骤?

    创建远程版本库(自己服务器/第三方托管平台)

    本地版本库设置远程地址

    推送本地版本库到远程

  3. 推送的命令?

    git push -u origin master

17. Git 远程仓库 - 克隆

克隆:拷贝一个 Git 仓库到本地,进行使用

命令:git clone 远程仓库地址,例如:git clone gitee.com/lidongxu/wo…

效果:在运行命令所在文件夹,生成 work 项目文件夹(包含版本库,并映射到暂存区和工作区)

注意1:Git 本地仓库已经建立好和远程仓库的链接

注意2:仓库公开随意克隆,推送需要身为仓库团队成员

18. 多人协同开发

需求:小传新代码共享给小智

步骤:

1.小传开发代码 -> 工作区 -> 暂存区 -> 提交 -> 拉取(可选)-> 推送

2.小智 -> 拉取(后续也可以开发代码 -> ... -> 推送)

3.想要看到别人同步上去的最新内容

  • git pull origin master 等价于

  • git fetch origin master:master(获取远程分支记录到本地,未合并)

    +

    git merge origin/master (把远程分支记录合并到所在分支下)

19. VSCode 中使用 Git

使用:源代码管理,进行暂存,撤销,对比差异,提交等操作

注意:VSCode 打开的项目文件夹下需要有 .git 仓库

20. 案例-发布黑马头条数据管理平台

需求:把 Webpack 压缩好的 dist 分发文件夹网页,部署到码云上,开启 Page 服务在互联网中浏览

步骤:

1.初始化本地 Git 仓库(这次是非空文件夹-配套素材 dist 文件夹)

2.初始化远程 Git 仓库(这一次也是非空的)

3.本地配置远程仓库链接

4.本地拉取合并一下(确认本地要包含远程内容时使用)

5.本地推送到远程 Git 仓库

6.开启 page 网页服务得到地址浏览

21. Git 常用命令

命令作用注意
git remote add 远程仓库别名 远程仓库地址添加远程仓库地址别名唯一,地址是 .git 结尾的网址
git remote -v查看远程仓库地址
git remote remove 远程仓库别名删除远程仓库地址
git pull 远程仓库别名 分支名拉取完整写法:git pull 远程仓库别名 远程分支名:本地分支名 等价于:git fetch 和 git merge
git push 远程仓库别名 分支名推送完整写法:git push 远程仓库别名 本地分支名:远程分支名 -u:建立通道后以后可以简写 git push
git pull --rebase 远程仓库别名 分支名拉取合并合并没有关系的记录
git clone 远程仓库地址克隆从0得到一个远程的Git仓库到本地使用