-----前置知识

在当下,我们将初步进行“前后端交互”、感知“前端工程化”;

AJAX入门——让数据“活”起来

AJAX 概念和 axios 使用

浏览器大家不陌生,服务器可以暂时理解为一台“提供数据”的电脑(一台二十四小时不关机的电脑)。

运行时,将会发出一次“请求”,数据不再直接嵌在代码里,而是从服务器返回,

axios语法(使用到promise,后续讲解)

需要使用到网络地址

直接点击网址可在浏览器访问到,返回一个对象结构的json字符串

message是提示信息,list才是省份列表数据

打印返回结果result,result为一个对象,其中data属性为服务器返回真正数据。

我们可以通过result.data.list拿到省份列表数据

便可展示到元素上

实践代码:

<!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>AJAX概念和axios使用</title>
</head>
<body>
  <!--
    axios库地址:https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js
    省份数据地址:http://hmajax.itheima.net/api/province

    目标: 使用axios库, 获取省份列表数据, 展示到页面上
    1. 引入axios库
  -->
  <p class="my-p"></p>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script>
    // 2. 使用axios函数
    axios({
      url: 'http://hmajax.itheima.net/api/province'
    }).then(result => {
      console.log(result)
      // 好习惯:多打印,确认属性名
      console.log(result.data.list)
      console.log(result.data.list.join('<br>'))
      // 把准备好省份列表,插入到页面
      document.querySelector('.my-p').innerHTML = result.data.list.join('<br>') 
    })
  </script>
</body>
</html>

认识 URL

URL的组成:协议 + 域名 + 资源路径

协议——规定了浏览器和服务器之间传输数据的格式

域名——指出访问的是哪一台服务器电脑

资源路径——标识访问文件在服务器电脑当中的具体位置

URL 查询参数

在axios中,查询参数写到params选项中

常用请求方法和数据提交

前面都是从服务器“拿”(获取)数据,

怎样把数据“存”(提交、保存)到服务器呢?

什么情况需要提交数据?当数据需要在服务器上保存

注意区分:params和data

method不区分大小写,get可以省略,属性值为字符串,记得加上引号

数据提交场景——注册账号

实操代码:

<!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>常用请求方法和数据提交</title>
</head>
<body>
  <button class="btn">注册用户</button>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script>
    /*
      注册用户: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'
        }
      })
    })
  </script>
</body>
</html>

总结

axios 错误处理

想要把错误信息展示给用户(用户不会去查看控制台)

在axios的then方法后使用catch方法,

通过catch方法捕获错误信息(错误信息传至形参error)

把错误原因取出error.response.data.message

用提示框提示用户alert

HTTP协议-报文

其中Content-Type: application/json规定的是这次请求携带的数据类型

axios在运行时会把代码中的data对象转成json字符串携带到请求报文中。

请求报文的组成部分/格式

总结:

报文一共有四个部分:请求行、请求头、空行、请求体

HTTP 协议-响应报文

响应报文也分成四部分,

其中响应行中携带HTTP响应状态码

form-serialize 插件

表单元素需要设置name属性,name属性回作为返回对象的属性名

引入插件

   <script src="form-serialize.js"></script>

使用插件

const data = serializeForm(document.getElementById('myForm'),{hash:true,empty:true})

配置对象中的hash属性用于设置获取数据的结构:

为true时,获取数据为JS对象;键是表单元素的name属性,值是对应的表单元素的值

为false时,获取数据为查询字符串。

一般使用true,表单获取的数据会被提交到服务器,将被携带在请求体,一般要求传一个对象。

empty设置为true时,序列化函数会包含所有表单元素,即使它们的值为空(如空字符串、未选中的复选框等)。如果empty设置为false(默认值),序列化函数会忽略值为空的表单元素。

图片上传

上传图片接口请求体所需数据类型不再是application-json而是form-data

为文件类型,图片文件file不能直接转成json格式。

使用input元素(type="file")供用户选择上传文件,

使用change监听事件,

通过e.target获取表单元素,

通过files属性获得一个文件伪数组,

打印文件对象:

使用FormData携带图片文件,

根据接口文档在axios中传入数据:

选择图片并发送axios请求,

到控制台查看本次请求,

点击请求,打开载荷,

图片文件浏览器进行了格式化(二进制)

再看到服务器的响应,已返回图片地址

使用图片地址,展示图片

案例 - 网站换肤

使用label标签**(样式容易修改)**的for属性关联input标签的id

设置成功后进行本地存储

注意,每次打开页面时需要判断,

本地存有背景图地址才进行设置(使用逻辑与的短路特性):

但逻辑与的运算符优先级高于赋值运算符,需要添加小括号

gbUrl && (document.body.style.backgroundImage = `url(${bgUrl})`)

实操代码:

/**
 * 目标:网站-更换背景
 *  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('img', 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})`)

关于Object.keys-返回一个包含对象自身所有可枚举属性键的数组。

const person = {
  name: 'Alice',
  age: 30,
  occupation: 'Developer'
};

const keys = Object.keys(person);
console.log(keys);  // 输出: ['name', 'age', 'occupation']

XMLHttpRequest

AJAX原理 - XMLHttpRequest

前面与服务器交互使用的不是axios吗?

ajax并不等于axios

我们使用的axios的内部,实际上对XHR对象/原理 的封装

为什么还要学习ajax?

①在一些静态网站项目中,与服务器交互只有一到两处,可以采用XHR对象,几行代码就能与服务器交互;

②也能了解axios内部是如何与服务器交互,了解axios内部原理。

使用 XMLHttpRequest

使用XMLHttpRequest实践获取数据,代码如下:

返回的是对象结构的json字符串。

以前使用axios请求返回的不是一个对象吗,这里为何是json字符串?

前面使用axios,会帮你把json字符串转成对象。

这里需要自己转化,JSON.parse()

总结:

XMLHttpRequest - 查询参数

携带查询参数

打印查询参数对象转化得来的查询参数字符串,出现的%××%××%××不是乱码,

它叫做url编码,为什么要进行url编码,因为url网址有个规定,只能出现英文数字以及特殊符号。

那为什么浏览器上边的搜索框/地址栏有中文?因为浏览器会对其进行格式化,可显示中文;

但是当在代码中,和服务器沟通时,传递的url网址需要在网络进行传输,就会进行一种编码(url编码)

可以在url解码网站中进行解码

案例实操代码/效果:

XMLHttpRequest - 数据提交

实操代码:

<!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>XMLHttpRequest_数据提交</title>
</head>
<body>
  <button class="reg-btn">注册用户</button>
  <script>
    /**
     * 目标:使用xhr进行数据提交-完成注册功能
    */
    document.querySelector('.reg-btn').addEventListener('click', () => {
      const xhr = new XMLHttpRequest()
      xhr.open('POST', 'http://hmajax.itheima.net/api/register')
      xhr.addEventListener('loadend', () => {
        console.log(xhr.response)
      })

      // 设置请求头-告诉服务器内容类型(JSON字符串)
      xhr.setRequestHeader('Content-Type', 'application/json')
      // 准备提交的数据
      const userObj = {
        username: 'itheima007',
        password: '7654321'
      }
      const userStr = JSON.stringify(userObj)
      // 设置请求体,发起请求
      xhr.send(userStr)
    })
  </script>
</body>
</html>

注意:请求类型不同,携带参数时转换方法不同

Promise

认识promise

语法:

1.借助浏览器内置的promise构造函数,并传入一个回调函数,promise提供了resolve和reject两个函数。

在回调函数中执行异步代码,异步代码有了结果之后可以做一个判断,成功调用resolve方法,失败调用reject方法。

2.promise对象使用then方法,传入回调函数,接受promise对象内成功的结果。

3.继续使用catch方法,传入回调函数,接受promise对象内失败的结果。

实操代码:

<!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>认识Promise</title>
</head>
<body>
  <script>
    /**
     * 目标:使用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)
    })
  </script>
</body>
</html>

总结:

Promise - 三种状态

迅速打开控制台查看,发现promise在控制台为“pending”

promise创建时,构造函数传入的回调函数里的代码会立刻执行,先于console.log(p)。

如果等到resolve结果打印出来再查看,状态变为fullfilled

总结:

使用Promise + XHR 获取省份列表

如果请求结果直接在promise构造函数的参数回调函数中打印

promise对象的一个好处:异步结果有成功与失败

调用resolve和reject关联结果处理函数,在then和catch回调中做后续处理

xhr如何判断响应成功还是失败?——响应状态码

打印xhr对象,我们可以看到status属性

如果响应状态码是2××,就说明响应成功

成功的情况:

失败的情况(url添加无用多余数字字符):

在浏览器控制台查看请求,

状态码变成404

响应结果为h1标签,

对于promise,reject出现的错误对象比较特殊,要用console.dir进行详细打印

①使用console.log进行打印(对象无法展开查看其属性和方法)

②使用console.dir进行打印

封装简易版 axios

简易初始版

<!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>封装_简易axios函数_获取省份列表</title>
</head>
<body>
  <p class="my-p"></p>
  <script>
    /**
     * 目标:封装_简易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
    })
  </script>
</body>
</html>

修改支持传递查询参数

<!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>封装_简易axios函数_获取地区列表</title>
</head>
<body>
  <p class="my-p"></p>
  <script>
    /**
     * 目标:封装_简易axios函数_获取地区列表
     *  1. 判断有params选项,携带查询参数
     *  2. 使用URLSearchParams转换,并携带到url上
     *  3. 使用myAxios函数,获取地区列表
    */
    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>')
    })

  </script>
</body>
</html>

修改支持传递请求体数据

点击两次按钮,发送两次请求,一次成功,一次失败。

<!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>封装_简易axios函数_注册用户</title>
</head>
<body>
  <button class="reg-btn">注册用户</button>
  <script>
    /**
     * 目标:封装_简易axios函数_注册用户
     *  1. 判断有data选项,携带请求体
     *  2. 转换数据类型,在send中发送
     *  3. 使用myAxios函数,完成注册用户
    */
    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)
      })
    })
  </script>
</body>
</html>

自我总结:

对于post方法,就是const a = JSON.stringify(要传递的数据)->添加请求type[注意设置请求type要在xhr.open()方法后面]  xhr.setRequestHeader('Content-Type', 'application/json') ->然后xhr.send(a)【作为请求体传出去】

对于get方法,就是const a = new URLSearchParams(要传递的数据)->  const b = a.toString();->然后拼接到请求的url上面,xhr.send()是空就行

同步代码和异步代码

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

回调函数地狱

缔造“回调地狱”↓

制造里层回调错误,却在最外层接收错误→无法捕获

axios源码抛出异常(未捕获)

总结:

Promise - 链式调用

实操:

<!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>Promise_链式调用</title>
</head>
<body>
  <script>
    /**
     * 目标:掌握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)
  </script>
</body>
</html>

总结:

async 和 await 使用

使用方法

在async函数中使用await直接获取Promise对象成功状态的结果值

完整代码:

<!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>async函数和await_解决回调函数地狱</title>
</head>
<body>
  <form>
    <span>省份:</span>
    <select>
      <option class="province"></option>
    </select>
    <span>城市:</span>
    <select>
      <option class="city"></option>
    </select>
    <span>地区:</span>
    <select>
      <option class="area"></option>
    </select>
  </form>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script>
    /**
     * 目标:掌握async和await语法,解决回调函数地狱
     * 概念:在async函数内,使用await关键字,获取Promise对象"成功状态"结果值
     * 注意: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()
  </script>
</body>
</html>

async函数和await_捕获错误

在area后面加了“1”,用comsole.dir打印错误信息,

接口返回的,真正的响应数据在错误对象(该对象中有着axios处理过的提供的一些其他信息)的response属性

还可以到网络里查看到

疑问:省份和城市应该获取成功,为什么没有展示?

——在try语句中,如果某句代码发生错误,后面的不会再执行。

<!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>async函数和await_错误捕获</title>
</head>
<body>
  <form>
    <span>省份:</span>
    <select>
      <option class="province"></option>
    </select>
    <span>城市:</span>
    <select>
      <option class="city"></option>
    </select>
    <span>地区:</span>
    <select>
      <option class="area"></option>
    </select>
  </form>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script>
    /**
     * 目标:async和await_错误捕获
    */
    async function getData() {
      // 1. try包裹可能产生错误的代码
      try {
        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
      } catch (error) {
        // 2. 接着调用catch块,接收错误信息
        // 如果try里某行代码报错后,try中剩余的代码不会执行了
        console.dir(error)
      }
    }

    getData()
  </script>
</body>
</html>

事件循环-EventLoop

事件循环

运行结果都是1 3 2

调用栈:可以理解为 JS代码在运行时形成的调用环境

宿主环境:浏览器(注意:浏览器是多线程的,JS是单线程的)

任务队列:内存开辟的一块空间

代码及答案:

<!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>事件循环_练习</title>
</head>
<body>
  <script>
    /**
     * 目标:阅读并回答执行的顺序结果
    */
    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
  </script>
</body>
</html>

宏任务与微任务

宏任务与微任务 - 执行顺序

什么是宏任务

——交给浏览器管理和执行的异步代码

什么是微任务**(在JS引擎中执行,先与宏任务执行)**

——JS引擎发起和管理执行的异步代码

微任务(Promise)会在宏任务(setTimeout)之前执行

经典面试题

<!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>事件循环经典_经典面试题</title>
</head>
<body>
  <script>
    // 目标:回答代码执行顺序
    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
  </script>
</body>
</html>

代码执行顺序:

  1. console.log(1):同步执行,立即输出 1
  2. setTimeout(() => {...}, 0):这里设置了一个延时为 0 的 setTimeout。虽然延迟是 0,但它会被放入任务队列中,等待当前执行栈清空后执行。因此,它不会立即执行。
  3. const p = new Promise(resolve => {...}):这是一个同步创建的 Promise,立即执行 resolve(5),并将 5 作为 Promise 的结果传递下去。
  4. p.then(result => console.log(result)):这行代码注册了一个微任务(then)。微任务的执行会在当前事件循环中的所有同步任务执行完后立即执行,因此它会在 setTimeout 的回调之前执行。
  5. const p2 = new Promise(resolve => resolve(6)):另一个同步创建的 Promise,立即将 6 作为结果传递。
  6. p2.then(result => console.log(result)):这个 then 也是一个微任务,它会在当前执行栈清空后执行,因此会在 setTimeout 的回调之前执行。
  7. console.log(7):同步执行,立即输出 7

执行顺序分析:

  • 第一步:执行所有同步代码。输出 17
  • 第二步:执行微任务队列中的任务。首先执行 p.then(...) 输出 5,然后执行 p2.then(...) 输出 6
  • 第三步:所有同步代码和微任务执行完后,进入宏任务队列,执行 setTimeout 中的回调。首先输出 2,然后创建一个新的 Promise,输出 3p.then(result => console.log(result)) 被执行)。
  • 第四步:执行 setTimeout 中的 setTimeout 回调,输出 4

Promise.all 静态方法

合并多个Promise对象:

展出数据,可使用低速网络查看是否同时展示

测试catch,在weather后面加123

进catch不进then

案例 - 商品分类

<!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>案例_分类导航</title>
  <link rel="stylesheet" href="./css/index.css">
</head>
<body>
  <!-- 大容器 -->
  <div class="container">
    <div class="sub-list">
      <div class="item">
        <h3>分类名字</h3>
        <ul>
          <li>
            <a href="javascript:;">
              <img src="http://zhoushugang.gitee.io/erabbit-client-pc-static/uploads/img/category%20(9).png" />
              <p>巧克力</p>
            </a>
          </li>
          <li>
            <a href="javascript:;">
              <img src="http://zhoushugang.gitee.io/erabbit-client-pc-static/uploads/img/category%20(9).png" />
              <p>巧克力</p>
            </a>
          </li>
          <li>
            <a href="javascript:;">
              <img src="http://zhoushugang.gitee.io/erabbit-client-pc-static/uploads/img/category%20(9).png" />
              <p>巧克力</p>
            </a>
          </li>
        </ul>
      </div>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script>
    /**
     * 目标:把所有商品分类“同时”渲染到页面上
     *  1. 获取所有一级分类数据
     *  2. 遍历id,创建获取二级分类请求
     *  3. 合并所有二级分类Promise对象
     *  4. 等待同时成功后,渲染页面
    */
    // 1. 获取所有一级分类数据
    axios({
      url: 'http://hmajax.itheima.net/api/category/top'
    }).then(result => {
      console.log(result)
      // 2. 遍历id,创建获取二级分类请求
      const secPromiseList = result.data.data.map(item => {
        return axios({
          url: 'http://hmajax.itheima.net/api/category/sub',
          params: {
            id: item.id // 一级分类id
          }
        })
      })
      console.log(secPromiseList) // [二级分类请求Promise对象,二级分类请求Promise对象,...]
      // 3. 合并所有二级分类Promise对象
      const p = Promise.all(secPromiseList)
      p.then(result => {
        console.log(result)
        // 4. 等待同时成功后,渲染页面
        const htmlStr = result.map(item => {
          const dataObj = item.data.data // 取出关键数据对象
          return `<div class="item">
        <h3>${dataObj.name}</h3>
        <ul>
          ${dataObj.children.map(item => {
            return `<li>
            <a href="javascript:;">
              <img src="${item.picture}">
              <p>${item.name}</p>
            </a>
          </li>`
          }).join('')}
        </ul>
      </div>`
        }).join('')
        console.log(htmlStr)
        document.querySelector('.sub-list').innerHTML = htmlStr
      })
    })
    
  </script>
</body>
</html>

效果:先是默认的HTML结构,全部请求完成后才重新渲染展示如下

项目介绍

项目准备

推荐使用,

每个程序员都有自己的管理方式。

代码笔记部分黑马直接免费开放了,放个自己的链接到时候方便查看,主要是看流程,真正开发已经不是这么原生了,下面的项目具体步骤部分可看可不看

https://download.csdn.net/download/m0_58537749/91250267

验证码登录

HTML结构:

<!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">
  <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css">
  <link rel="stylesheet" href="./index.css">
  <title>黑马头条-数据管理平台</title>
</head>
<body>
  <!-- 警告框 -->
  <div class="alert info-box">
    操作结果
  </div>
  <!-- 登录页面 -->
  <div class="login-wrap">
    <div class="title">黑马头条</div>
    <div>
      <form class="login-form">
        <div class="item">
          <input type="text" class="form-control" name="mobile" placeholder="请输入手机号" value="13888888888">
        </div>
        <div class="item">
          <input type="text" class="form-control" name="code" placeholder="默认验证码246810" value="246810">
        </div>
        <div class="item">
          <button type="button" class="btn btn-primary btn">登 录</button>
        </div>
      </form>
    </div>
  </div>
  <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.3.4/axios.min.js"></script>
  <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/js/bootstrap.min.js"></script>
  <script src="../../lib/form-serialize.js"></script>
  <script src="../../utils/request.js"></script>
  <script src="../../utils/alert.js"></script>
  <script src="./index.js"></script>
</body>
</html>

1.为什么要提取公共前缀地址(基地址),因为公司业务可能会更换服务器,如果你不想一条一条地修改请求地址的话。

后续使用axios时,url不需要再写前缀。

2.请求成功与失败

成功返回message“OK”还有token等(作用后续讲)

输入错误验证码,请求失败的时候,返回message“验证码不正确”

message可用于提示框文字显示

3.提示框控制

之前的案例已经做过了。把之前封装的函数放到utils下,

alert.js

// 弹窗插件
// 需要先准备 alert 样式相关的 DOM
/**
 * BS 的 Alert 警告框函数,2秒后自动消失
 * @param {*} isSuccess 成功 true,失败 false
 * @param {*} msg 提示消息
 */
function myAlert(isSuccess, msg) {
  const myAlert = document.querySelector('.alert')
  myAlert.classList.add(isSuccess ? 'alert-success' : 'alert-danger')
  myAlert.innerHTML = msg
  myAlert.classList.add('show')

  setTimeout(() => {
    myAlert.classList.remove(isSuccess ? 'alert-success' : 'alert-danger')
    myAlert.innerHTML = ''
    myAlert.classList.remove('show')
  }, 2000)
}

成功的提示

错误的提示

验证码登录-流程

token 的介绍

token的正确打开方式:

判断无token令牌字符串,则强制跳转到登录页

强制访问内容页面

被踢回登录页面

登录成功后保存token到本地

登录成功后跳转到内容页面

延时跳转,展示alert警告框

个人信息设置和 axios 请求拦截器

问题:很多接口,都需要携带 token 令牌字符串

解决:在请求拦截器统一设置公共 headers 选项

第二个函数的应用场景非常少,一般也可以不写第二个函数体

请求拦截器统一携带token令牌

如此使用

内容管理页面和发布文章页面的html文件都引入了auth.js

个人信息显示成功

axios 响应拦截器和身份验证失败

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

Axios中文文档 | Axios中文网

请求拦截器,响应拦截器,拦截器:在做出对应动作前(在执行发送请求或 响应回到then/catch之前)进行拦截,补充额外的统一的操作

TEST

先登录进去

修改token

刷新界面,发出请求

点击确定后跳转,缓存被清空

实操代码(request.js):

// axios 公共配置
// 基地址
axios.defaults.baseURL = 'http://geek.itheima.net'

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  // 统一携带 token 令牌字符串在请求头上
  const token = localStorage.getItem('token')
  token && (config.headers.Authorization = `Bearer ${token}`)
  return config;
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error);
});

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
  // 2xx 范围内的状态码都会触发该函数。
  // 对响应数据做点什么
}, function (error) {
  // 超出 2xx 范围的状态码都会触发该函数。
  // 对响应错误做点什么,例如:统一对 401 身份验证失败情况做出处理
  console.dir(error)
  if (error?.response?.status === 401) {
    alert('身份验证失败,请重新登录')
    localStorage.clear()
    location.href = '../login/index.html'
  }
  return Promise.reject(error);
});

优化-axios 响应结果

背景:axios中返回的result(请求成功的结果对象)如右上,为axios内部封装的结果对象,它把服务器返回的数据对象挂载到了data属性下。

目标:axios 直接接收服务器返回的响应结果

注意:响应拦截器成功函数中的response参数即result结果对象

实践代码:

// axios 公共配置
// 基地址
axios.defaults.baseURL = 'http://geek.itheima.net'

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  // 统一携带 token 令牌字符串在请求头上
  const token = localStorage.getItem('token')
  token && (config.headers.Authorization = `Bearer ${token}`)
  return config;
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error);
});

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
  // 2xx 范围内的状态码都会触发该函数。
  // 对响应数据做点什么,例如:直接返回服务器的响应结果对象
  const result = response.data
  return result;
}, function (error) {
  // 超出 2xx 范围的状态码都会触发该函数。
  // 对响应错误做点什么,例如:统一对 401 身份验证失败情况做出处理
  console.dir(error)
  if (error?.response?.status === 401) {
    alert('身份验证失败,请重新登录')
    localStorage.clear()
    location.href = '../login/index.html'
  }
  return Promise.reject(error);
});

修改之前使用服务器返回数据的写法(去掉一个.data)

发布文章-富文本编辑器

什么叫富文本呢?——带样式、多格式的文本

使用插件wangEditor实现富文本编辑器

 123步骤在官方文档-快速开始:wangEditor

JS创建编辑器语句逐句解析:(独立存放于editor.js)

// 富文本编辑器
// 创建编辑器函数,创建工具栏函数
const { createEditor, createToolbar } = window.wangEditor

// 编辑器配置对象
const editorConfig = {
  // 占位提示文字
  placeholder: '发布文章内容...',
  // 编辑器变化时回调函数
  onChange(editor) {
    // 获取富文本内容
    const html = editor.getHtml()
    // 也可以同步到 <textarea>
    // 为了后续快速收集整个表单内容做铺垫
    document.querySelector('.publish-content').value = html
  }
}

// 创建编辑器
const editor = createEditor({
  // 创建位置
  selector: '#editor-container',
  // 默认内容
  html: '<p><br></p>',
  // 配置项
  config: editorConfig,
  // 配置集成模式(default 全部)(simple 简洁)
  mode: 'default', // or 'simple'
})

// 工具栏配置对象
const toolbarConfig = {}

// 创建工具栏
const toolbar = createToolbar({
  // 为指定编辑器创建工具栏
  editor,
  // 工具栏创建的位置
  selector: '#toolbar-container',
  // 工具栏配置对象
  config: toolbarConfig,
  // 配置集成模式
  mode: 'default', // or 'simple'
})

4.监听内容改变,保存在隐藏文本域(便于后期收集)

因为该富文本编辑器输入内容的地方实际上是p标签,而不是在textarea(隐藏)内

浏览器中检查可发现

需要准备textarea用于收集文本内容,将其隐藏

发布文章-封面设置

目标:点击加号框(或“封面”文字)上传图片作为封面

input type属性值为file 的表单元素很难改成加号框

所以,加号盒子(label,所以监听的是input元素的change事件),img封面盒子(img)和input表单元素(input)为三个标签

封面回显的时候,需要使加号盒子隐藏,img显示(rounded类选择器使其隐藏)

下面是提前写好的有关隐藏和显示的相关样式

上传图片接口要求传入表单数据FormData

上传请求的响应结果包含图片的URL地址,用于回显

优化:点击 img 可以重新切换封面

实践代码(publish/index.js):

/**
 * 目标1:设置频道下拉菜单
 *  1.1 获取频道列表数据
 *  1.2 展示到下拉菜单中
 */
// 1.1 获取频道列表数据
async function setChannleList() {
  const res = await axios({
    url: '/v1_0/channels'
  })
  // 1.2 展示到下拉菜单中
  const htmlStr = `<option value="" selected="">请选择文章频道</option>` + res.data.channels.map(item => `<option value="${item.id}">${item.name}</option>`).join('')
  document.querySelector('.form-select').innerHTML = htmlStr
}
// 网页运行后,默认调用一次
setChannleList()

/**
 * 目标2:文章封面设置
 *  2.1 准备标签结构和样式
 *  2.2 选择文件并保存在 FormData
 *  2.3 单独上传图片并得到图片 URL 网址
 *  2.4 回显并切换 img 标签展示(隐藏 + 号上传标签)
 */
// 2.2 选择文件并保存在 FormData
document.querySelector('.img-file').addEventListener('change', async e => {
  const file = e.target.files[0]
  const fd = new FormData()
  fd.append('image', file)
  // 2.3 单独上传图片并得到图片 URL 网址
  const res = await axios({
    url: '/v1_0/upload',
    method: 'POST',
    data: fd
  })
  // 2.4 回显并切换 img 标签展示(隐藏 + 号上传标签)
  const imgUrl = res.data.url
  document.querySelector('.rounded').src = imgUrl
  document.querySelector('.rounded').classList.add('show')
  document.querySelector('.place').classList.add('hide')
})
// 优化:点击 img 可以重新切换封面
// 思路:img 点击 => 用 JS 方式触发文件选择元素 click 事件方法
document.querySelector('.rounded').addEventListener('click', () => {
  document.querySelector('.img-file').click()
})

注意:此时,图片(封面)地址临时存储在 img 标签上,并未和文章关联保存

发布文章-收集并保存

实现代码:

/**
 * 目标3:发布文章保存
 *  3.1 基于 form-serialize 插件收集表单数据对象
 *  3.2 基于 axios 提交到服务器保存
 *  3.3 调用 Alert 警告框反馈结果给用户
 *  3.4 重置表单并跳转到列表页
 */
// 3.1 基于 form-serialize 插件收集表单数据对象
document.querySelector('.send').addEventListener('click', async e => {
  if (e.target.innerHTML !== '发布') return
  const form = document.querySelector('.art-form')
  const data = serialize(form, { hash: true, empty: true })
  // 发布文章的时候,不需要 id 属性,所以可以删除掉(id 为了后续做编辑使用)
  delete data.id
  console.log(data)
  // 自己收集封面图片地址并保存到 data 对象中
  data.cover = {
    type: 1, // 封面类型
    images: [document.querySelector('.rounded').src] // 封面图片 URL 网址
  }

  // 3.2 基于 axios 提交到服务器保存
  try {
    const res = await axios({
      url: '/v1_0/mp/articles',
      method: 'POST',
      data: data
    })
    // 3.3 调用 Alert 警告框反馈结果给用户
    myAlert(true, '发布成功')
    // 3.4 重置表单并跳转到列表页
    form.reset()
    // 封面需要手动重置
    document.querySelector('.rounded').src = ''
    document.querySelector('.rounded').classList.remove('show')
    document.querySelector('.place').classList.remove('hide')
    // 富文本编辑器重置
    editor.setHtml('')

    setTimeout(() => {
      location.href = '../content/index.html'
    }, 1500)

  } catch (error) {
    myAlert(false, error.response.data.message)
  }
})

await错误捕捉,使用try...catch语句进行

捕捉错误并打印

内容管理-文章列表展示

实践代码(content/index.js):

/**
 * 目标1:获取文章列表并展示
 *  1.1 准备查询参数对象
 *  1.2 获取文章列表数据
 *  1.3 展示到指定的标签结构中
 */
// 1.1 准备查询参数对象
const queryObj = {
  status: '', // 文章状态(1-待审核,2-审核通过)空字符串-全部
  channel_id: '', // 文章频道 id,空字符串-全部
  page: 1, // 当前页码
  per_page: 2 // 当前页面条数
}
let totalCount = 0 // 保存文章总条数

// 获取并设置文章列表
async function setArtileList() {
  // 1.2 获取文章列表数据
  const res = await axios({
    url: '/v1_0/mp/articles',
    params: queryObj
  })
  // 1.3 展示到指定的标签结构中
  const htmlStr = res.data.results.map(item => `<tr>
  <td>
    <img src="${item.cover.type === 0 ? `https://img2.baidu.com/it/u=2640406343,1419332367&amp;fm=253&amp;fmt=auto&amp;app=138&amp;f=JPEG?w=708&amp;h=500`: item.cover.images[0]}" alt="">
  </td>
  <td>${item.title}</td>
  <td>
    ${item.status === 1 ? `<span class="badge text-bg-primary">待审核</span>` : `<span class="badge text-bg-success">审核通过</span>`}
  </td>
  <td>
    <span>${item.pubdate}</span>
  </td>
  <td>
    <span>${item.read_count}</span>
  </td>
  <td>
    <span>${item.comment_count}</span>
  </td>
  <td>
    <span>${item.like_count}</span>
  </td>
  <td data-id="${item.id}">
    <i class="bi bi-pencil-square edit"></i>
    <i class="bi bi-trash3 del"></i>
  </td>
</tr>`).join('')
  document.querySelector('.art-list').innerHTML = htmlStr

内容管理-分页功能

实践代码:

/**
 * 目标3:分页功能
 *  3.1 保存并设置文章总条数
 *  3.2 点击下一页,做临界值判断,并切换页码参数并请求最新数据
 *  3.3 点击上一页,做临界值判断,并切换页码参数并请求最新数据
 */
// 3.2 点击下一页,做临界值判断,并切换页码参数并请求最新数据
document.querySelector('.next').addEventListener('click', e => {
  // 当前页码小于最大页码数
  if (queryObj.page < Math.ceil(totalCount / queryObj.per_page)) {
    queryObj.page++
    document.querySelector('.page-now').innerHTML = `第 ${queryObj.page} 页`
    setArtileList()
  }
})
// 3.3 点击上一页,做临界值判断,并切换页码参数并请求最新数据
document.querySelector('.last').addEventListener('click', e => {
  // 大于 1 的时候,才能翻到上一页
  if (queryObj.page > 1) {
    queryObj.page--
    document.querySelector('.page-now').innerHTML = `第 ${queryObj.page} 页`
    setArtileList()
  }
})

内容管理-删除最后一条

代码:

内容管理-编辑文章-回显

实践代码:

/**
   * 目标4:编辑-回显文章
   *  4.1 页面跳转传参(URL 查询参数方式)
   *  4.2 发布文章页面接收参数判断(共用同一套表单)
   *  4.3 修改标题和按钮文字
   *  4.4 获取文章详情数据并回显表单
   */
//自触发函数
  ; (function () {
    // 4.2 发布文章页面接收参数判断(共用同一套表单)
    const paramsStr = location.search
    const params = new URLSearchParams(paramsStr)
    params.forEach(async (value, key) => {
      // 当前有要编辑的文章 id 被传入过来
      if (key === 'id') {
        // 4.3 修改标题和按钮文字
        document.querySelector('.title span').innerHTML = '修改文章'
        document.querySelector('.send').innerHTML = '修改'
        // 4.4 获取文章详情数据并回显表单
        const res = await axios({
          url: `/v1_0/mp/articles/${value}`
        })
        console.log(res)
        // 组织我仅仅需要的数据对象,为后续遍历回显到页面上做铺垫
        const dataObj = {
          channel_id: res.data.channel_id,
          title: res.data.title,
          rounded: res.data.cover.images[0], // 封面图片地址
          content: res.data.content,
          id: res.data.id
        }
        // 遍历数据对象属性,映射到页面元素上,快速赋值
        Object.keys(dataObj).forEach(key => {
          if (key === 'rounded') {
            // 封面设置
            if (dataObj[key]) {
              // 有封面
              document.querySelector('.rounded').src = dataObj[key]
              document.querySelector('.rounded').classList.add('show')
              document.querySelector('.place').classList.add('hide')
            }
          } else if (key === 'content') {
            // 富文本内容
            editor.setHtml(dataObj[key])
          } else {
            // 用数据对象属性名,作为标签 name 属性选择器值来找到匹配的标签
            document.querySelector(`[name=${key}]`).value = dataObj[key]
          }
        })
      }
    })
  })();

内容管理-编辑文章-保存

给按钮绑定了两个函数,都会走一遍

但需要进行判断

/**
 * 目标5:编辑-保存文章
 *  5.1 判断按钮文字,区分业务(因为共用一套表单)
 *  5.2 调用编辑文章接口,保存信息到服务器
 *  5.3 基于 Alert 反馈结果消息给用户
 */
document.querySelector('.send').addEventListener('click', async e => {
  // 5.1 判断按钮文字,区分业务(因为共用一套表单)
  if (e.target.innerHTML !== '修改') return
  // 修改文章逻辑
  const form = document.querySelector('.art-form')
  const data = serialize(form, { hash: true, empty: true })
  // 5.2 调用编辑文章接口,保存信息到服务器
  try {
    const res = await axios({
      url: `/v1_0/mp/articles/${data.id}`,
      method: 'PUT',
      data: {
        ...data,
        cover: {
          type: document.querySelector('.rounded').src ? 1 : 0,
          images: [document.querySelector('.rounded').src]
        }
      }
    })
    console.log(res)
    myAlert(true, '修改文章成功')
  } catch (error) {
    myAlert(false, error.response.data.message)
  }
})

退出登录

/**
 * 目标3:退出登录
 *  3.1 绑定点击事件
 *  3.2 清空本地缓存,跳转到登录页面
 */
// 3.1 绑定点击事件
document.querySelector('.quit').addEventListener('click', e => {
  // 3.2 清空本地缓存,跳转到登录页面
  localStorage.clear()
  location.href = '../login/index.html'
})

NODE.JS入门

01.Nodejs安装与使用

什么是 Node.js?

什么是前端工程化?

Node.js 为何能执行 JS?

查看当前使用的Node.js版本:node -v

执行JS:node 01.js

Node.js 安装

建议直接安装nvm

总结

02.fs 模块 - 读写文件

加载fs模块对象到自己编写的js文件中,进行写与读

数据流对象默认为十六进制,使用toString方法转化

03.path 模块 - 路径处理

我们在nodejs环境中执行js代码,它的所有相对路径是以终端文件夹作为起点。

使用path模块的join方法,结合_dirname生成的绝对路径,后面可用../这种定义路径

可在任意位置开启的终端找到所需文件。

04.案例 - 压缩前端 html

实践代码:

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

写入的代码只有一行

05.URL 中的端口号

注意:http 协议,默认访问 80 端口(所以之前访问接口的时候没有加端口号,也能获取到数据)

06.http 模块-创建 Web 服务

执行时server.js,与之前不同,没有停止执行,它会在终端里启动一个进程,进程持续存在,监听有无请求,有请求就会触发处理函数,返回一个响应内容。

之前执行完后,会立刻出现路径,再让你输入下一条命令

request事件是标准事件名,用于表示服务器接收到一个新的HTTP请求。这个事件名是固定的,不能被替换,因为它是Node.js API的一部分

实践代码:

/**
 * 目标:基于 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 服务启动成功了')
})

07.案例-浏览时钟

这里的req就是浏览器搜索框请求的路径

08.什么是模块化?

CommonJS 标准

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

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

11.包的概念

实操:

utils/lib/index.js

server.js

utils/package.json

12.npm - 软件包管理器

13.npm - 安装所有依赖

从别处(网上、其他人手中)拿到写好的项目,一般不携带node_modules文件夹(所占存储空间大)

但有package.json文件,里面记载了当前项目下载过的包

还有package-lock.json文件,固定软件包的版本

导入模块/包,除了自己创建的模块、包需要写地址,nodejs自带的模块以及使用npm安装的包,都直接写名字

14.npm - 全局软件包 nodemon

安装nodemon,添加了32个包,使用npm安装软件包,依赖的其他包一并下载

修改数组的值,ctrl+s保存,程序重新执行

15.Node.js 总结

16.什么是webpack?

压缩:把代码文件的体积缩小

整合:把多个CSS文件、JS文件整合成一个,减少用户浏览器的http请求次数,从而让用户更快访问我们的网页

转译:less、sass转换成css,高版本js降级处理等

打包之后的代码

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

默认的入口文件:src/index.js

默认的出口文件:dist/main.js

概念 | webpack 中文文档 (docschina.org)

配置文件的代码在node环境中执行,为了保证在任何地方执行都能找到文件,使用绝对路径。

path.resolve和path.join

5.2.0版本的webpack可以通过设置clean:true清空输出目录后打包生成,其实就是删掉之前打包的dist,只留下最新的一版dist

18.用户登录 - 长度判断

19.自动生成 html 文件

关于插件:

HtmlWebpackPlugin | webpack 中文文档 (docschina.org)

template

filename

下包(找包)

配置包

如果是写相对路径,会以出口目录作为起点

打包

生成的html文件中js文件引入路径书写

插件源代码会根据filename的路径分析,

去匹配出口路径(output.path), 这个出口目前来看是指js文件的出口

匹配到后,在该基础(../)拼接output.filename

如果filename路径中不包含出口路径?

一直向上层级匹配检索

建议:打包输出内容(filename)都在出口文件夹下面

可正常运行

20.打包 css 代码

css-loader | webpack 中文文档 (docschina.org)直接看里面的快速开始

style-loader | webpack 中文文档 (docschina.org)

加载器从右到左依次使用,所以先是css,后style,顺序不能颠倒

为什么不用变量和from去接收?

—— 引入,webpack就会处理,下面不需要使用,可以省略,在打包的入口文件引入

下载css-loader和style-loader

配置webpack.config.js

执行npm run build进行打包,出现下面的图不要紧,只是warn ,不是error

打包后的html文件并没有引入css文件

css代码被打包到出口文件index.js

(代码经过压缩,是给浏览器运行,不是给程序员阅读)

运行后,样式生效

下载bootstrap

引入bootstrap

先引入官方的,再引入自己的

表单等样式生效

21.优化-提取 css 代码

将原本嵌入在js文件中的css代码提取出来作为独立的一个文件

单独的css文件可以让浏览器缓存起来,减少js文件体积,让浏览器下载更快,

浏览器可以并行下载css和js,让网页更快显示在用户眼前。

该插件在css-loader文档中有关联

MiniCssExtractPlugin | webpack 中文文档 (docschina.org)

打包进了main.css

上面的是bootstrap的css代码

下面是index.css代码

我们在webpack的入口文件引入的相关css代码被单独提取到一个css文件中

并且在html文件中被引入

生效

22.优化-压缩过程

bootstrap的css代码本就被压缩过,而手写的css代码提取后没有经过压缩

CssMinimizerWebpackPlugin | webpack 中文文档

CssMinimizerWebpackPlugin | webpack 中文文档 (docschina.org)

下包->配置包->打包后,看到新包中的手写css代码被压缩

js代码不受影响,保持压缩

样式生效

23.打包 less 代码

less-loader | webpack 中文文档

使用miniCssExtractPlugin.loader代替style.loader

注意:想要把那些文件单独打包都要把这些文件引入到webpack配置的入口文件

24.打包图片

资源模块 | webpack 中文文档

小于8kb会打包进入js代码里面

目的:减少http请求次数

准备一张大图,一张小图

不能直接给img的src属性赋值路径,webpack会当成普通字符串,原封不动打包

注意:js 中引入本地图片资源要用 import 方式(如果是网络图片http地址,字符串可以直接写)

打包后,大图发送一个文件,小图导出data URI(base64字符串)

25.用户登录 - 完成功能

request.js导出(默认导出)

alert.js导出(命名导出)

将其他js代码导入到index.js

打包后的js代码:

检验:

登陆成功

错误验证码

26.搭建开发环境

开发环境 | webpack 中文文档 (docschina.org)

之前运行时出现的警告和mode配置有关

目标11:配置开发服务器环境 webpack-dev-server

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

   11.2 设置打包的模式为开发模式,配置自定义命令

   11.3 使用 npm run dev 来启动开发服务器,试试热更新效果

注意1:webpack-dev-server 借助 http 模块创建 8080 默认 Web 服务

注意2:默认以 public 文件夹作为服务器根目录

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

内存有,磁盘也有

output.path 的值

神奇操作:因为会默认打开public文件夹下的index.html文件,所以直接新建一个index.html文件,然后在里面直接写要跳转的文件地址

可自动跳转

可观察到自动打包及热更新

27.打包模式

不推荐配置项的方式(写完代码,还得记得修改值,不如记住命令)

代码被极致压缩

修改build命令

分成一段段的好处,某个文件的代码发生变化,直接替换对应名字对应的代码,让我们更快速看到开发项目的运行效果(模块热替换更快速)

补充:打包的main.css想要放进login文件夹中

MiniCssExtractPlugin()里面必须填绝对路径,./指的就是dist文件夹

28.打包模式的应用

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

在开发模式下用 style-loader把css代码内嵌在js文件中,开发时如果样式发生修改,可以更快地替换到我们网页上

在生产模式下,想要把css单独提取成一个文件,为了让打包输出后的代码,在用户的浏览器上可以并行下载css和js代码

需要判断当前运行时所在的环境

方案1:webpack.config.js 配置导出函数

模式(Mode) | webpack 中文文档 (docschina.org)

只接受2种模式,但项目开发过程中,配置可能不止区分于两个环境,可能有测试环境,预发布环境,还有一些其他环境

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

安装,配置自定义命令之后,在webpack,config,js区分不同环境使用不同变量

独立文件

内嵌

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

生产环境 | webpack 中文文档 (docschina.org)

29.前端-注入环境变量

环境变量作用:根据不同环境,执行不同配置

webpack 中文文档 (docschina.org)

webpack为内置

该插件会在webpack打包源代码时,把代码视作字符串,匹配到对应的key,会替换成value

(cross-env 设置的只在 Node.js 环境生效,前端代码无法访问 process.env.NODE_ENV,但打包后使用该插件打包会留下相应的字符串值)

不能直接使用表达式(字符串)

process.env.NODE_ENV

插件内部源码发现value为字符串,会将其视作变量

30.开发环境调错 - source map

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

开发环境 | webpack 中文文档 (docschina.org)

Devtool | webpack 中文文档 (docschina.org)

用config包裹住全部,再导出config之前做判断是否为开发环境,以防在生产环境使用source-map

31.解析别名 alias

使用别名

别名生效

32.优化-CDN使用

打包时禁止对第三方库打包,而是使用cdn实现第三方库

1.模板语法(html-webpack-plugin插件要求使用的语法)

学习过程中,使用免费的cdn服务

1.在 html 中引入第三方库的 CDN 地址 并用模板语法判断

(在网站里找到响应的网址)

2. 配置 webpack.config.js 中 externals 外部扩展选项

(防止某些 import 的包被打包)

如果为开发环境,cdn则不生效

3. 两种模式下打包观察效果

开发

查看源代码,没有cdn地址

生产

bootstrap样式没有一起被打包进来

axios的import语句变成了一个全局变量留在原地

网页样式正常,请求可发送

知识补充:

逻辑代码先于axios代码引入,为何正常执行?

——因为添加了defer属性

面试官:说一下script 标签中 defer(推迟) 和 async(异步) 的区别_script的async和defer应用场景-CSDN博客

33.多页面打包

1.之前手写的js代码统一外部导入html使用,现在需要使用模块化导入入口文件

2.下载 form-serialize 包并导入到核心代码中使用(略过,该部分目前暂时没用上,表单搜集已经逐一手写过)

3.配置 webpack.config.js 多入口和多页面的设置(如图)

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

无论在本地开发,或打包到生产模式,都可以用cdn引入css,

不需要变量接收,用不到css代码里面的内容,

自己编写的代码(index.js)需要被处理,要引到入口js中

如果

本地开发用npm下载,

上线再用cdn要使用模板语法判断

wangeditor只能用require的方式代入(webpack两种导入方式都能识别)

35.优化-分割公共代码

生成公共代码:

Git版本控制系统

1.Git 初识

2.掌握 Git 仓库

3.Git 的三个区域

git ls-files 查看当前暂存区有哪些文件

4.Git 文件状态

5.Git 暂存区使用

使用git restore命令恢复修改过的index.css

适用于A版代码已提交上git,然后在A基础上更改为B后,想要返回A代码,B代码不要了,可以执行git restore 文件路径将当前代码还原为工作区的A代码

使用git rm --catched命令从暂存区移除index.css文件

6.练习-登录页面

查看提交历史git log --oneline

7.Git 回退版本

关于git reset --hard命令 

使用之前的提交记录恢复(可以使用git relpg --online可以查看完整日志)

8.删除文件

清空终端控制台命令:clear

9.忽略文件(.gitignore)

忽略文件呈现为灰色

10.分支

创建并切换分支

切换回master分支,代码变化为master版本

11.练习-登录 bug 修复

新建login-bug分支进行bug修复

12.分支-合并与删除

13.分支-合并冲突

vscode给出提示,分支显示正在合并过程中

解决冲突后,提交记录

提交记录太多,按回车键展示剩余,或者q键退出

15.Git 常用命令

16.Git 远程仓库

第一次使用需要输入gitee的账号密码

上传成功

可以选择开源,让大家都可以下载

如果当前电脑需要访问其他账号的仓库,删除旧的凭据,再次进入vscode触发填写用户名和密码的弹窗

17.Git 远程仓库-克隆

使用终端,

打开准备好的文件夹,可以在路径输入cmd,也可以右键选择使用git bash

cd git-study进入文件夹(仓库,含.git文件)

18.多人协同开发

相当于执行了两条命令

19.VSCode 中使用 Git

更改代码,工具栏的源代码管理图标右下角出现数字

打开会展示修改处的对比

可以选择点击图标,放弃更改

还可以暂存更改

还可以提交记录

git log --oneline查看本地版本记录

可以点击按钮提交到远程仓库

还有更多远程仓库操作:

先pull再push

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

本地push上传前需要pull

拒绝合并“不想关”的历史记录(当前,本地仓库和远程仓库的分支没有任何关系)

强制进行合并

查看合并效果

再push到远程仓库

开启page服务

因为仅支持公开仓库开通

管理->基本信息->开源->勾选

使用根路径(可以访问整个仓库的内容)

21.Git 常用命令

完结

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐