AJAX
Nuyoah Axios
使用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <div class="box"></div>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script> axios({ url : 'http://ajax-api.itheima.net/api/province' }).then (result =>{ document.querySelector(".box").innerHTML = result.data.data.join("</br>"); }) </script>
|
查询参数
浏览器可以给服务器提供额外的参数,让服务器返回浏览器想要的数据
语法:http://xxx.com/xxx/xxx?参数名1=值1&参数名2=值2
axios中使用params选项来传递参数
1 2 3 4 5 6 7 8
| axios({ url : '', params:{ 键:值 } }).then(result =>{ console.log(result) })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <div class="box"></div>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script> axios({ url : 'http://ajax-api.itheima.net/api/city', params : { pname: '河北省' } }).then (result =>{ document.querySelector(".box").innerHTML = result.data.data.join("</br>"); }) </script>
|
请求方法
axios请求配置
URL:请求的URL网址
method: 请求的方法,GET可以省略
data:提交数据
1 2 3 4 5 6 7 8 9
| axios({ url : '', method : '请求方法', data : { 参数名 : 值 } }).then(result =>{ })
|
- GET 获取数据
- POST 提交数据
- PUT 修改数据(全部)
- DELETE 删除数据
- PATCH 修改数据(部分)
错误处理
通过catch捕获错误
1 2 3 4 5 6 7
| axios({ }).then(result =>{ }).catch(error =>{ })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| document.querySelector(".btn").addEventListener("click", () =>{ axios({ url : "http://hmajax.itheima.net/api/register", method : 'post', data : { username : "NuyoahII", password : "123456" } }).then(result=>{ console.log(result.data) }).catch(error =>{ alert(error.response.data.message) }) })
|
HTTP协议-请求报文
HTTP报文:规定了浏览器发送即服务器返回内容的格式
请求报文:浏览器按照HTTP协议要求的格式,发送给服务器的内容
请求报文组成部分:
- 请求行:请求方法,URL,协议
- 请求头:以键值对的格式携带的附加信息,比如Content-Type
F12中网络中的Fetch/XHR是专门查看AJAX请求信息的
HTTP协议-相应报文
HTTP报文:规定了浏览器发送即服务器返回内容的格式
相应报文:服务器按照HTTP协议要求的格式,返回给浏览器的内容
响应报文内容
- 响应行 (状态行) :协议、HTTP 响应状态码、状态信息
- 响应头:以键值对的格式携带的附加信息,比如: Content-Type
- 空行:分隔响应头,空行之后的是服务器返回的资源
- 响应体:返回的资源
响应状态码:表明这次请求是否成功
- 1xx 信息
- 2xx 成功
- 3xx 重定向消息
- 4xx 客户端错误
- 5xx 服务端错误
接口文档
描述接口的文章(后端管)
接口:使用AJAX和服务器通讯时,使用的URL,请求方法,以及参数
黑马AJAX接口文档
作用:快速收集表单元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <script src="./lib/form-serialize.js"></script> <script> document.querySelector('.btn').addEventListener('click', () => {
const form = document.querySelector('.example-form') const data = serialize(form, { hash: true, empty: true }) console.log(data) }) </script>
|
图书管理
Bootstrap弹窗
- 引入bootstrap.css和bootstrap.js
- 准备弹窗标签,确认结构
- 通过自定义属性,控制弹窗的显隐
- data-bs-toggle=“modal” // 通过该属性控制弹窗的显隐
- data-bs-target=“css选择器” // 通过该属性控制那个弹窗
控制显隐
-
通过属性控制,弹框显示或隐藏
通过添加data-bs-toggle与data-bs-target来控制弹框的显隐
1 2 3
| <button type="button" class="btn btn-primary mybtn" data-bs-toggle="modal" data-bs-target=".my-box"> 显示弹框 </button>
|
-
通过JS控制弹框显示或隐藏
创建弹窗对象
const modalDom = document.querySelector(“css选择器”)
const modal = new bootstrap.Modal(modalDom)
显示弹窗
modal.show()
关闭弹窗
modal.hide()
获取图书
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const creator = "老张"
function getBooksList(){ axios({ url:"http://hmajax.itheima.net/api/books", params:{ creator } }).then(result =>{ document.querySelector(".list").innerHTML = result.data.data.map(({id,bookname, author, publisher},index) => { return `<tr> <td>${index+1}</td> <td>${bookname}</td> <td>${author}</td> <td>${publisher}</td> <td data-id="${id}"> <span class="del">删除</span> <span class="edit">编辑</span> </td> </tr>`}).join("")
}).catch(error =>{ console.log(error) }) }
|
添加图书
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| const addModal = new bootstrap.Modal(document.querySelector(".add-modal"))
function addBooks(){ const addFrom = document.querySelector(".add-form") const {bookname, author, publisher} = serialize(addFrom, {hash:true, empty:true})
if(!bookname){ alert("书名不能为空") }else if(!author){ alert("作者不能为空") }else if(!publisher){ alert("出版商不能为空") }
axios({ url:"http://hmajax.itheima.net/api/books", method:"post", data:{ bookname, author, publisher, creator } }).then(result =>{ getBooksList() addModal.hide() })
}
|
删除图书
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const addBtn = document.querySelector(".add-btn") addBtn.addEventListener("click", addBooks)
document.querySelector(".list").addEventListener("click", e =>{ if(e.target.className === "del"){ axios({ url:`http://hmajax.itheima.net/api/books/${e.target.parentNode.dataset.id}`, method:"delete", }).then(result=>{ getBooksList() })
} })
|
修改图书
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| const editModal = new bootstrap.Modal(document.querySelector(".edit-modal"))
document.querySelector(".list").addEventListener("click", e=>{ if(e.target.className === "edit"){ axios({ url:`http://hmajax.itheima.net/api/books/${e.target.parentNode.dataset.id}`, method:'get' }).then(result=>{ const data = result.data.data Object.keys(data).forEach((key)=>{ document.querySelector(`.edit-form .${key}`).value = data[key] }) }) editModal.show() } })
document.querySelector(".edit-btn").addEventListener("click", ()=>{ const editFrom = document.querySelector(".edit-form") const {id, bookname, author, publisher} = serialize(editFrom, {hash:true, empty:true})
axios({ url:`http://hmajax.itheima.net/api/books/${id}`, method:"put", data:{ bookname, author, publisher, creator } }).then(result=>{ getBooksList() }) editModal.hide() })
|
上传图像
图像上传一般使用FormData类型上传
-
获取图片对象
-
使用FormData携带图片文件
1 2
| const fd = new FormData() fd.append("参数名", "值")
|
-
提交表单数据到服务器,使用图片URL网址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <!-- 文件选择元素 --> <input type="file" class="upload"> <img src="" alt="" class="my-img">
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script> document.querySelector(".upload").addEventListener("change", (e)=>{ 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=>{ // 取出返回的url网址并展示到页面上 const imgUrl = result.data.data.url document.querySelector(".my-img").src = imgUrl }) }) </script>
|
用户管理
获取用户
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const creator = "老张"
function renderInfo(){ axios({ url:"http://hmajax.itheima.net/api/settings", method:"get", params:{ creator, } }).then(result=>{ const {avatar, email, nickname, gender, desc} = result.data.data document.querySelector(".email").value = email document.querySelector(".nickname").value = nickname document.querySelectorAll(".gender")[gender].checked = true document.querySelector(".desc").value = desc document.querySelector(".prew").src = avatar }) }
renderInfo()
|
头像修改
1 2 3 4 5 6 7 8 9 10 11 12 13
| document.querySelector("#upload").addEventListener("change", (e)=>{ const fd = new FormData() fd.append("avatar", e.target.files[0]) fd.append("creator", creator) axios({ url:"http://hmajax.itheima.net/api/avatar", method:"put", data: fd }).then(result=>{ document.querySelector(".prew").src = result.data.data.avatar }) })
|
信息修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| document.querySelector(".submit").addEventListener("click", ()=>{ const userObj = serialize(document.querySelector(".user-form"), {hash:true, empty:true}) userObj.gender = +userObj.gender userObj.creator = creator axios({ url:"http://hmajax.itheima.net/api/settings", method:"put", data:userObj }).then(result=>{ const {email, nickname, gender, desc} = result.data.data document.querySelector(".email").value = email document.querySelector(".nickname").value = nickname document.querySelectorAll(".gender")[gender].checked = true document.querySelector(".desc").value = desc
const toastDom = document.querySelector(".my-toast") const toast = new bootstrap.Toast(toastDom) toast.show() }) })
|
AJAX原理
AJAX的原理就是XMLHttpRequest对象
XMLHttpRequest基本用法
用户可以通过XMLHttpRequest在不刷新页面的情况下请求特定的URL获取数据
使用方法
-
创建XMLHttpRequest 对象
1
| const xhr = new XMLHttpRequest()
|
-
配置请求方法和请求 url 地址
1
| xhr.open("请求方法", "请求URL网址")
|
-
监听 loadend 事件,接收响应结果
1 2 3 4
| xhr.addEventListener("loadend", ()=>{ const data = JSON.parse(xhr.response) })
|
-
发起请求
XMLHttpRequest查询参数
XML查询参数是放在网址中的
1 2 3 4 5 6
| const xhr = new XMLHttpRequest() xhr.open("GET", "http://hmajax.itheima.net/api/city?panme=河北省") xhr.addEventListener("loadend", ()=>{ document.querySelector("p").innerHtml = JSON.parse(xhr.response).join("<br>") }) xhr.send()
|
使用模板字符串
1 2 3 4 5 6 7 8 9 10
| const xhr = new XMLHttpRequest() document.querySelector(".sel-btn").addEventListener("click", ()=>{ const pname = document.querySelector(".province").value const cname = document.querySelector(".city").value xhr.open("get", `http://hmajax.itheima.net/api/area?pname=${pname}&cname=${cname}`) xhr.addEventListener("loadend", ()=>{ document.querySelector(".list-group").innerHTML = JSON.parse(xhr.response).list.map(item=>`<li class="list-group-item">${item}</li>`).join("") }) xhr.send() })
|
使用URLSearchParams 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| document.querySelector(".sel-btn").addEventListener("click", ()=>{ const pname = document.querySelector(".province").value const cname = document.querySelector(".city").value const qObj = { pname, cname } const paramObj = new URLSearchParams(qObj) const queryString = paramObj.toString() const xhr = new XMLHttpRequest() xhr.open("get", `http://hmajax.itheima.net/api/area?${queryString}`) xhr.addEventListener("loadend", ()=>{ document.querySelector(".list-group").innerHTML = JSON.parse(xhr.response).list.map(item=>`<li class="list-group-item">${item}</li>`).join("") }) xhr.send() })
|
XMLHttpRequest传递数据
需要设置请求头和请求体
请求头:Content-Type: application/json
请求体携带JSON字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const xhr = new XMLHttpRequest() xhr.open("请求方式", "请求网址") xhr.addEventListener("loadend", ()=>{ console.log(xhr.response) })
xhr.setRequestHeader("Content-Type", "application/json")
const user = {usename: "zhang", password:"123456"} const userStr = JSON.stringify(user)
xhr.send(userStr)
|
Promise
promise:对象用于表示一个异步操作最终完成或失败及其结果值
好处
- 逻辑更加清晰
- 了解axios函数内部运作机制
- 能解决回调函数地狱问题
基本用法
1 2 3 4 5 6 7 8 9 10 11 12 13
| const p = new Promise((resolve, reject)=>{ })
p.then(result=>{ }).catch(error=>{ })
|
Promise三种状态
- 待定:pending :初始状态,既没有被兑现,也没有别拒绝,刚创建
- 已兑现:fulfilled:操作成功,调用resolve()
- 已拒绝:rejected:操作失败,调用reject()
一旦成为兑现,拒绝状态,promise就无法变为其他状态
Promise+XHR获取省份列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| const p = new Promise((resolve, reject) => { const xhr = new XMLHttpRequest() xhr.open("GET", `http://hmajax.itheima.net/api/province`) xhr.addEventListener("loadend", ()=>{ if(xhr.status >= 200 && xhr.status < 300){ resolve(JSON.parse(xhr.response)) } else { reject(new Error(xhr.response)) } }) xhr.send() })
p.then(result =>{ document.querySelector(".list-group").innerHTML = result.list.map(item => `<li class="list-group-item">${item}</li>`).join("") }).catch(error =>{ console.dir(error) })
|
封装axios获取省份列表(无传参)
promise+xhr封装成myAxios函数
- 定义myAxios函数,接收配置对象,返回Promise对象
- 发起XHR请求,默认请求方法为GET
- 调用成功/失败的处理程序
- 使用myAxios函数,获取省份列表展示
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function myAxios(config){ return new Promise((resolve, reject)={ }) }
myAxios({ }).then(result={ }).catch(error=>{ })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| function myAxios(config){ return new Promise((resolve, reject)=>{ const xhr = new XMLHttpResponse() 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() }) }
myAxios({ url:"http://hmajax.itheima.net/api/province" method:"get" }).then(result =>{ }).catch(error =>{ })
|
封装axios(有传参)
传递的参数通过URLSearchParams将参数转换成对应的键值字符串
- 调用myAxios函数,传入Params参数
- 基于URLSearchParams转换查询参数为字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| function myAxios(config){ return new Promise((resolve, reject)=>{ const xhr = new XMLHttpResponse() 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)) } }) xhr.send() }) } myAxios({ url:"http://hmajax.itheima.net/api/area", params:{ pname:"河北省" cname:"邯郸市" } }).then(result=>{ }).catch(error=>{ })
|
封装axios(有传值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| function myAxios(config){ return new Promise((resolve, reject)=>{ const xhr = new XMLHttpResponse() 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)) } }) if(config.data){ xhr.setRequestHeader("Content-Type", "application/json") xhr.send(JSON.stringify(config.data)) }else { xhr.send() } }) }
|
天气案例-展示天气
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| function getWeather(cityCode){ myAxios({ url:"http://hmajax.itheima.net/api/weather", params:{ city:cityCode } }).then(result=>{ console.log(result) const {dateShort, dateLunar, area, psPm25, psPm25Level, temperature, windDirection, windPower} = result.data const {humidity, sunriseTime, sunsetTime, temDay, temNight, ultraviolet, weather,} = result.data.todayWeather document.querySelector(".title").innerHTML = ` <span class="dateShort">${dateShort}</span> <span class="calendar">农历 <span class="dateLunar">${dateLunar}</span> </span>` document.querySelector(".area").innerHTML = area document.querySelector(".weather-box").innerHTML = ` <div class="tem-box"> <span class="temp"> <span class="temperature">${temperature}</span> <span>°</span> </span> </div> <div class="climate-box"> <div class="air"> <span class="psPm25">${psPm25}</span> <span class="psPm25Level">${psPm25Level}</span> </div> <ul class="weather-list"> <li> <img src="./imgs/小雨-line.png" class="weatherImg" alt=""> <span class="weather">${weather}</span> </li> <li class="windDirection">${windDirection}</li> <li class="windPower">${windPower}</li> </ul> </div>`
document.querySelector(".today-weather").innerHTML = ` <div class="range-box"> <span>今天:</span> <span class="range"> <span class="weather">${weather}</span> <span class="temNight">${temNight}</span> <span>-</span> <span class="temDay">${temDay}</span> <span>℃</span> </span> </div> <ul class="sun-list"> <li> <span>紫外线</span> <span class="ultraviolet">${ultraviolet}</span> </li> <li> <span>湿度</span> <span class="humidity">${humidity}</span>% </li> <li> <span>日出</span> <span class="sunriseTime">${sunriseTime}</span> </li> <li> <span>日落</span> <span class="sunsetTime">${sunsetTime}</span> </li> </ul>`
document.querySelector(".week-wrap").innerHTML = result.data.dayForecast.map(item => `<li class="item"> <div class="date-box"> <span class="dateFormat">${item.dateFormat}</span> <span class="date">${item.date}</span> </div> <img src="./imgs/多云.png" alt="" class="weatherImg"> <span class="weather">${item.weather}</span> <div class="temp"> <span class="temNight">${item.temNight}</span>- <span class="temDay">${item.temDay}</span> <span>℃</span> </div> <div class="wind"> <span class="windDirection">${item.windDirection}</span> <span class="windPower"><${item.windPower}</span> </div> </li>`).join("")
}).catch(error=>{
}) }
|
天气案例-用户输入检测并查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| function throttle(fn, time){ let timer = null return function(){ if(!timer){ timer = setTimeout(function(){ fn() timer = null }, time) } } }
function checkInput(){ const city = document.querySelector(".search-city").value if(city){ myAxios({ url : "http://hmajax.itheima.net/api/weather/city", params:{ city } }).then(result=>{ console.log(result.data) document.querySelector(".search-list").innerHTML = result.data.map(item => `<li class="city-item" data-code="${item.code}">${item.name}</li>`).join("") }).catch(error=>{ console.log(error) }) }
} document.querySelector(".search-city").addEventListener("input", throttle(checkInput, 1000))
document.querySelector(".search-list").addEventListener("click", e => { console.log(e.target.innerText) if(e.target.tagName === "LI"){
document.querySelector(".search-city").value = e.target.innerText getWeather(e.target.dataset.code) }
})
|
同步代码异步代码
同步代码:逐行执行,需原地等待结果后,才继续向下执行
异步代码:调用后耗时,不阻塞代码继续执行(不必原地等待),在将来完成后触发一个回调函数
回调函数地狱
在回调函数中嵌套回调函数,一直嵌套下去就形成了回调函数地狱
缺点:可读性差,异常无法捕获,耦合性严重,牵一发动全身,内部axios出错外部无法捕获
1 2 3 4 5 6 7
| axios({url:"xxx"}).then(result=>{ axios({url:"xxx"}).then(result=>{ axios({url:"xxx"}).then(result=>{ }) }) })
|
Promise-链式调用
promise链式调用能够有效的解决回调函数地狱
promise函数的then函数会返回一个promise函数对象,通过返回的promise对象来进行下一个异步函数的调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const p = new Promise((resolve, reject) =>{ setTimeout(()=>{ resolve("北京") }, 2000) })
const p2 = p.then(result=>{ return new Promise((resolve, reject) =>{ setTimeout(()=>{ resolve(result+"北京市") }, 2000) })
})
|
通过promise对象的返回值,将axios函数嵌套,转变为线性关系
1 2 3 4 5 6 7 8
| axios({url:"xxx"}).then(result=>{ return axios({url:"xxx"}) }).then(result=>{ return axios({url:"xxx"}) }).then(result=>{ })
|
async函数和await
async函数是使用async关键字声明的函数,其中允许await关键字的使用,async和await关键字可以让我们使用更为简介的方式写出promise中的异步
基本用法
1 2 3 4 5 6 7 8 9 10
| async function getDefaultArea(){ const pObj = await axios({url:"xxx"}) const pname = pObj.data.list[0] const cObj = await axios({url:"xxx"}) const cname = cObj.data.list[0] const aObj = await axios({url:"xxx"}) const aname = aObj.data.list[0] }
|
捕获错误
try…catch
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| async function getDefaultArea(){ try{ const pObj = await axios({url:"xxx"}) const pname = pObj.data.list[0] const cObj = await axios({url:"xxx"}) const cname = cObj.data.list[0] const aObj = await axios({url:"xxx"}) const aname = aObj.data.list[0] } catch(error){ console.log(error) } }
|
事件循环
原因:JS是单线程的,为了让耗时代码不阻塞其他代码运行,设计了事件循环
什么是事件循环
执行代码和收集异步任务,在调用栈空闲时,返回调用任务队列里面的回调函数机制
JS执行过程
先执行同步代码,遇到异步代码交给宿主浏览器环境执行
异步有结果之后,将代码交给任务队列
当调用栈空闲时,反复调用任务队列中的回调函数
宏任务微任务
宏任务:由浏览器环境执行的异步代码
- JS脚本执行事件
- setTimeout/setInterval
- AJAX请求完成事件
- 用户交互事件
微任务:由JS引擎环境执行的异步代码
Promise对象.then()
Promise对象本身是同步的,但是then和catch回调函数是异步的
调用栈,先执行微任务队列,在执行宏任务队列
promise.All静态方法
概念:合并多个 Promise 对象,等待所有同时成功完成(或某一个失败)做后续逻辑
使用方法
1 2 3 4 5 6
| const p = Promise.all([promise对象, promise对象]) p.then(result=>{ }).catch(error =>{ })
|
1 2 3 4 5 6 7 8 9 10
| const arrCode = ["110100", "310520", "640213"] const arrPromise = arrCode.map(item => { return axios({url:"http://hmajax.itheima.net/api/weather", params:{city:item}) }) const p = Promise.all(arrPromise) p.then(result=>{
}).catch(error =>{
})
|
商品分类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| axios({ url: "http://hmajax.itheima.net/api/category/top" }).then(result=>{ const arrSubGoods = result.data.data.map(item => axios({url:"http://hmajax.itheima.net/api/category/sub", params:{id:item.id}})) const p = Promise.all(arrSubGoods) p.then(result=>{ const strResult = result.map(item => { return ` <div class="item"> <h3>${item.data.data.name}</h3> <ul> <!-- 可以再模板字符串中在进行map遍历 --> ${item.data.data.children.map(sub => `<li> <a href="javascript:;"> <img src="${sub.picture}" /> <p>${sub.name}</p> </a> </li>`).join("")} </ul> </div>` }).join("") document.querySelector(".sub-list").innerHTML = strResult })
})
|
学习反馈项目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| function getProvince(){ axios({ url:"http://hmajax.itheima.net/api/province" }).then(result=>{ document.querySelector(".province").innerHTML = "<option value=''>省份</option>" + result.data.list.map(item => `<option value="${item}">${item}</option>`).join("") document.querySelector(".city").innerHTML = "<option value=''>城市</option>" document.querySelector(".area").innerHTML = "<option value=''>地区</option>" }) }
function getCity (province){ axios({ url:"http://hmajax.itheima.net/api/city", params:{ pname:province } }).then(result=>{ document.querySelector(".city").innerHTML = "<option value=''>城市</option>" + result.data.list.map(item => `<option value="${item}">${item}</option>`).join("") document.querySelector(".area").innerHTML = "<option value=''>地区</option>" }) }
function getArea (province, city){ axios({ url:"http://hmajax.itheima.net/api/area", params:{ pname:province, cname:city } }).then(result=>{ document.querySelector(".area").innerHTML = "<option value=''>地区</option>" + result.data.list.map(item => `<option value="${item}">${item}</option>`).join("") }) }
getProvince()
document.querySelector(".province").addEventListener("change", (e)=>{ getCity(e.target.value) }) document.querySelector(".city").addEventListener("change", e=>{ getArea(document.querySelector(".province").value, e.target.value) })
document.querySelector(".submit").addEventListener("click", e=>{ const form = document.querySelector(".info-form") const data = serialize(form, { hash: true, empty: true }) axios({ url:"http://hmajax.itheima.net/api/feedback", method:"post", data:{ ...data } }).then(result=>{ alert("提交成功") }).catch(error=>{ alert(error.response.data.message) }) })
|
黑马头条
- 基于Bootstrap 搭建网站标签和样式
- 集成 wangEditor 插件实现富文本编辑器
- 使用原生jS 完成增删改查等业务
- 基于axios与黑马头条线上接口交互
- 使用axios 拦截器进行权限判断
验证码登陆
- 点击发送验证码
- 给后端发送请求,并携带手机号,调用后端验证码的接口
- 后端为手机号生成验证码,并记录在服务器
- 后端携带手机号给运营商发送请求
- 通过基站给指定的手机发送验证码短信
- 基站返回给服务器发送成功信息
- 服务器给前端发送,验证码发送成功请求
- 根据手机收到的验证码,填入到页面中
token
概念:访问权限的令牌,本质上是一串字符串
创建:正确登录后,由后端签发并返回
作用:判断是否有登录状态,控制访问权限
注意:前端只能判断token的有无,后端才能判断token的有效性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| document.querySelector(".btn").addEventListener("click", ()=>{ const form = document.querySelector(".login-form") const params = serialize(form, {hash:true, empty:true}) axios({ url:"/v1_0/authorizations", method:"post", data:params }).then(result=>{ myAlert(true, "登陆成功") localStorage.setItem("token", result.data.data.token) setTimeout(() => { location.href = "../content/index.html" }, 1500);
}).catch(error=>{ myAlert(false, error.response.data.message) }) })
const token = localStorage.getItem("token") if(!token){ location.href = "../login/index.html" }
|
个人信息设置和axios拦截器
需求:根据登录信息设置个人页面展示
语法:axios可以再headers选项中设置请求头参数
1 2 3 4 5 6
| axios({ url:"目标资源地址网址", headers:{ Authorization: `Bearer ${localStorage.getItem("token")}` } })
|
问题:每一次axios请求都需要携带token
解决:在请求拦截器统一设置公共headers选项
**axios请求拦截器:**发起请求之前,触发配置函数,对请求参数进行额外配置
1 2 3 4 5 6 7 8 9
| axios.interceptors.request.use(function(config){ const token = location.getItem("token") token && (config.headers.Authorization = `Bearer ${token}`) return config }, function(error){ return Promise.reject(error) })
|
axios相应拦截器:axios在相应回到then和catch之前触发的拦截函数,对相应结果统一处理
1 2 3 4 5 6 7 8 9 10 11 12
| axios.interceptors.response.use(function(response){ return response }, function(error){ if(error?.response?.status === 401){ alert("登录状态过期,请从新登录") localStorage.clear() location.href = "../login/index.html" } return Promise.reject(error) })
|
优化axios响应结果:让相应结果直接是我们想要的数据,而不包括其他杂七杂八的东西
还是使用axios相应拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| axios.interceptors.response.use(function(response){ const result = response.data return result }, function(error){ if(error?.response?.status === 401){ alert("登录状态过期,请从新登录") localStorage.clear() location.href = "../login/index.html" } return Promise.reject(error) })
|
富文本编辑器
富文本:带样式,多格式的文本编辑器
使用插件:wangEditor插件
步骤:参考wangEditor插件
-
引入Css定义样式
1 2 3 4 5 6 7 8 9
| <link href="https://unpkg.com/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet"> <style> #editor—wrapper { border: 1px solid #ccc; z-index: 100; /* 按需定义 */ } #toolbar-container { border-bottom: 1px solid #ccc; } #editor-container { height: 500px; } </style>
|
-
定义HTML结构,控制文本编辑器的位置
1 2 3 4
| <div id="editor—wrapper"> <div id="toolbar-container"></div> <div id="editor-container"></div> </div>
|
-
引入JS创建编辑器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <script src="https://unpkg.com/@wangeditor/editor@latest/dist/index.js"></script> <script> const { createEditor, createToolbar } = window.wangEditor
const editorConfig = { placeholder: 'Type here...', onChange(editor) { const html = editor.getHtml() console.log('editor content', html) // 也可以同步到 <textarea> } }
const editor = createEditor({ selector: '#editor-container', html: '<p><br></p>', config: editorConfig, mode: 'default', // or 'simple' })
const toolbarConfig = {}
const toolbar = createToolbar({ editor, selector: '#toolbar-container', config: toolbarConfig, mode: 'default', // or 'simple' }) </script>
|
-
监听内容改变,保存在隐藏文本域
1 2 3 4 5 6 7 8 9 10 11 12
| const editorConfig = { placeholder: 'Type here...', onChange(editor) { const html = editor.getHtml() document.querySelector(".publish-content").value = html } }
|
发布文章页面
频道列表
1 2 3 4 5 6 7
| async function setChannleList() { const res = await axios({ url:"/v1_0/channels" }) document.querySelector(".form-select").innerHTML = '<option value="" selected>请选择文章频道</option>' + res.data.channels.map(item => `<option value="${item.id}">${item.name}</option>`).join("") } setChannleList()
|
文章封面
label 中的for属性可以绑定对应的input标签,通过ID值绑定
1 2 3 4 5 6
| <div class="cover"> <label for="img">封面:</label> <label for="img" class="place">+</label> <input class="img-file" type="file" name="img" id="img" hidden> <img class="rounded"> </div>
|
- 准备标签结构和样式
- 选择文件并保存在FromData
- 单独上传图片并得到图片URL地址
- 回显并切换img标签显示(隐藏+号,上传图片)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| document.querySelector(".img-file").addEventListener("change", async e=>{ const file = e.target.files[0] const fd = new FormData() fd.append("image", file)
const res = await axios({ url:"/v1_0/upload", method:"post", data:fd }) document.querySelector(".rounded").src = res.data.url
document.querySelector(".rounded").classList.add("show") document.querySelector(".place").classList.add("hide") })
document.querySelector(".rounded").addEventListener("click", ()=>{ document.querySelector(".img-file").click() })
|
收集并保存
收集文章内容并提交保存
- 使用form-serialize插件收集表单数据对象
- 基于axios提交到服务器保存
- 调用Alert警告框反馈结果给用户
- 重置表单并跳转到列表页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| document.querySelector(".send").addEventListener("click", async ()=>{ const form = document.querySelector(".art-form") const data = serialize(form, {hash: true, empty:true})
delete data.id
data.cover = { type: 1, images: [document.querySelector(".rounded").src] }
const alert = document.querySelector(".info-box") console.log(data)
try { const res = await axios({ url:"/v1_0/mp/articles", method:"post", data }) myAlert(true, "发布成功") 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) }
})
|
内容管理
文章列表显示
-
准备查询参数对象
-
获取文章列表数据
-
展示到指定的标签结构中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| const queryObj = { status: "", channel_id: "", page: 1, per_page: 2, }
async function setArticlesList(){ const res = await axios({ url:"/v1_0/mp/articles", params:queryObj })
console.log(res) document.querySelector(".art-list").innerHTML = res.data.results.map(item => `<tr> <td> <img src="${item.cover.type === 0 ? `https://img2.baidu.com/it/u=2640406343,1419332367&fm=253&fmt=auto&app=138&f=JPEG?w=708&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-primary">待审核</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> <i class="bi bi-pencil-square edit"></i> <i class="bi bi-trash3 del"></i> </td> </tr>`).join("") }
|
筛选功能
- 设置频道列表数据
- 监听筛选条件改变,保存查询信息到查询参数对象
- 点击筛选时,传递查询参数对象到服务器
- 获取匹配数据,覆盖到页面展示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| async function setChannleList() { const res = await axios({ url:"/v1_0/channels" }) document.querySelector(".form-select").innerHTML = '<option value="" selected>请选择文章频道</option>' + res.data.channels.map(item => `<option value="${item.id}">${item.name}</option>`).join("") } setChannleList()
document.querySelector(".sel-btn").addEventListener("click", ()=>{ const form = document.querySelector(".sel-form") const data = serialize(form, {hash:true, empty:true}) queryObj.channel_id = data.channel_id queryObj.status = data.status setArticlesList() })
|
翻页功能
- 保存并设置文章总条数
- 点击下一页,做临界值判断,并切换页码参数并请求最新数据
- 点击上一页,做临界值判断,并切换页码参数并请求最新数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| document.querySelector(".last").addEventListener("click", function(){ if(queryObj.page > 1){ queryObj.page = queryObj.page - 1 setArticlesList() } })
document.querySelector(".next").addEventListener("click", function(){ if(queryObj.page < Math.ceil(totalCount / queryObj.per_page)){ queryObj.page = queryObj.page+1 setArticlesList() } })
|
删除功能
- 关联文章 id 到删除图标
- 点击删除时,获取文章 id
- 调用删除接口,传递文章 id 到服务器
- 重新获取文章列表,并覆盖展示
- 删除最后一页的最后一条,需要自动向前翻页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| document.querySelector(".art-list").addEventListener("click", async e=>{ if(e.target.classList.contains("del")){ const res = await axios({ url:`/v1_0/mp/articles/${e.target.parentNode.dataset.id}`, method:"DELETE" })
if(document.querySelector(".art-list").childNodes.length === 1 && queryObj.page !== 1){ queryObj.page-- }
setArticlesList() } })
|
编辑回显
- 页面跳转传参(URL 查询参数方式)
- 发布文章页面接收参数判断(共用同一套表单)
- 修改标题和按钮文字
- 获取文章详情数据并回显表单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| document.querySelector(".art-list").addEventListener("click", async e=>{ if(e.target.classList.contains("edit")){ location.href = `../publish/index.html?id=${e.target.parentNode.dataset.id}`
} })
;(function(){ const paramsStr = location.search const params = new URLSearchParams(paramsStr) params.forEach(async (value, key)=>{ console.log(key, value) if(key === "id"){ document.querySelector(".title span").innerHTML = "编辑文章" document.querySelector(".send").innerHTML = "修改"
const res = await axios({ url:`/v1_0/mp/articles/${value}` })
const dataObj= { title:res.data.title, rounded:res.data.cover.images[0], channels_id:res.data.channels_id, 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{ document.querySelector(`[name=${key}]`).value = dataObj[key] }
}) } }) })();
|
编辑提交
- 判断按钮文字,区分业务(因为共用一套表单)
- 调用编辑文章接口,保存信息到服务器
- 基于 Alert 反馈结果消息给用户
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 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})
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] } } }) myAlert(true, "修改成功") setTimeout(()=>{ location.href = "../content/index.html" }, 1500) } catch (error) { console.log(error) myAlert(false, error.response.data.message) }
})
|
退出功能
- 绑定点击事件
- 清空本地缓存,跳转到登录页面
1 2 3 4
| document.querySelector(".quit").addEventListener("click", ()=>{ localStorage.clear() location.href="../login/index.html" })
|
Node.js
Node.js是什么:基于Chrome的v8引擎封装,独立执行JavaScript代码环境
Node.js与浏览器环境的JS最大区别:Node.js中没有BOM和DOM
Node.js有什用:编写后端程序,提供网页资源,前端工程化:集成开发中使用的工具和技术
Node.js如何执行代码:node xxx.js
fs模块-读写文件
-
加载fs模块
1
| const fs = require("fs")
|
-
写入文件内容
1 2 3
| fs.writeFile('文件路径', '写入内容', err=>{ })
|
-
读取文件内容,读出来的是Buffer数据流,需要使用toString()转换成字符串
1 2 3
| fs.readFile("文件路径", (err, data)=>{ })
|
path模块-路径处理
建议:在Node.js代码中,使用绝对路径
补充:(两个下划线)__dirname内置变量(获取当前模块目录-绝对路径)
windows和mac的路径分隔符是反斜杠与斜杠
path.join() 会使用特定于平台的分隔符作为定界符,将所有给定的路径片段连接在一起
语法
-
加载path模块
1
| const path = require("path")
|
-
使用path.join方法拼接路径
1 2 3 4
| fs.readFile(path.join(__dirname, "../test.txt"), (err, data)=>{ if(err) console.log(err) else console.log(data.toString()) })
|
压缩前端
将回车\r和换行符\n去掉,写入新的html文件
步骤:
- 读取源HTML中的内容
- 正则替换字符串
- 写入到新的html文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
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() const resultStr = htmlStr.replace(/[\r\n]/, "") fs.writeFile(path.join(__dirname, "dist/index.html"), resultStr, err=>{ if(err) console.log(err) else console.log('写入成功') }) } })
|
HTTP模块-创建web服务
URL端口号
URL:统一资源定位符,用于访问服务器里面的资源
端口号:标记服务器里不同的服务程序
端口号范围:0-65535之间的任意整数
HTTP端口默认是80
web服务程序:用于提供网上信息浏览的功能,0-1023和一些特定的端口号被占用,编写程序的时候注意绕开
Node.js创建Web服务
创建web服务并相应
-
加载Http模块,创建web服务对象
1 2
| const http = require('http') const server = http.createServer()
|
-
监听request请求事件,设置请求头和相应体
1 2 3 4 5 6
| server.on('request', (req, res) => { res.setHeader("Content-Type", "text/html;charset=utf-8") res.end("欢迎使用Node.js") })
|
-
配置端口号并启动Web服务
1 2 3
| server.listen(3000, () => { console.log("Web 服务已启动") })
|
-
浏览器请求http://localhost:3000 测试
模块化标准
概念:项目是由多个模块文件组成
好处:提高了代码的复用性,按需加载,独立作用域
使用:需要标准语法导出和导入
CommonJS标准
需求:定义utils.js模块,封装基地址和求数组总和的函数
使用:
- 导出:module.exports = {}
- 导入:require(“模块名或路径”) 内置模块直接写模块名,自定义模块需要写路径
utils.js:
1 2 3 4 5 6 7 8 9 10 11 12
| const baseURL = "http://localhost:8080" const getArrSum = function (arr){ return arr.reduce(function(prev, current){ return prev + current }, 0) }
module.exports = { url:baseURL, arraySum: getArrSum }
|
index.js
1 2 3
| const obj = require("./utils.js") console.log(obj)
|
ECMAScript标准
默认导出和导入
方式:
-
导出:export default{}
1 2 3 4
| export default{ 对外属性名:baseURL, 对外属性名:getArraySum }
|
-
导入:import 变量名 from ‘模块名或路径’
1
| import obj from '模块名或路径'
|
**注意:**Node.js默认支持CommonJS标准语法
如果需要使用ECMAScript标准语法,在运行模块所在文件夹新建package.json文件,并设置{“type”:“module”}
utils.js:
1 2 3 4 5 6 7 8 9 10 11 12
| const baseURL = "http://localhost:8080" const getArrSum = function (arr){ return arr.reduce(function(prev, current){ return prev + current }, 0) }
export default = { url:baseURL, arraySum: getArrSum }
|
index.js
1 2 3 4
| import obj from "./utils.js" console.log(obj.url) console.log(obj.getArrSum([1,2,3]))
|
命名导出和导入
方式:
-
导出:export修饰定义语句
1 2
| export const baseURL = 'http://localhost:8080' export const getArraySum = arr => arr.reduce((sum, val) => sum+val, 0)
|
-
导入:import {同名变量} from ‘模块名或路径’
1
| import {baseURL, getArraySum} from '模块名或路径'
|
包
概念:将模块,代码,其他资料聚合成一个文件夹
分类:
项目包:主要用于编写项目和业务逻辑
软件包:封装工具和方法进行使用
要求:根目录中,必须要有package.json 文件
1 2 3 4 5 6 7 8
| { "name":"utils", "version":"1.0.0", "description":"对包的描述", "main":"index.js", "author":"Nuyoah", "license":"MIT" }
|
**注意:**导入软件包时,引入的默认是index.js模块文件/main属性指定的模块文件
arr.js
1 2 3 4 5
| const getArraySum = arr => arr.reduce((sum, val) => sum += val, 0)
module.exports = { getArraySum }
|
str.js
1 2 3 4 5 6 7 8 9 10 11 12
| const checkUserName = username => { return username.length >= 8 }
const checkPassword = password => { return password.length >= 8 }
module.exports = { checkUser:checkUserName, checkPwd:checkPassword }
|
index.js
1 2 3 4 5 6 7 8 9 10 11
| const { getArraySum } = require("./libs/arr.js") const { checkUser, checkPwd } = require("./libs/str.js")
module.exports = { getArraySum, checkPwd, checkUser }
|
package.json
1 2 3 4 5 6 7 8
| { "name":"utils", "version":"1.0.0", "description":"工具包", "main":"index.js", "author":"Nuyoah", "license":"MIT" }
|
npm包管理器
使用方法:
- 初始化清单文件 npm init -y
- 下载软件包 npm i 软件包名称
- 使用软件包
npm-安装所有依赖
当项目中缺少node_modules的时候,项目无法正确运行,所以需要我们手动下载
npm i 下载 package.json中记录的所有软件包
npm-全局软件包 nodemon
软件包的区别:
- 本地软件包:当前项目使用,封装属性和方法,存在于node_modules
- 全局软件包:本机所有项目使用,封装命令和工具,存在于系统设置的位置
使用nodemon运行的server.js项目并不会关闭,当server.js中的内容修改后,nodemon会自动重启并跟新对应内容
Webpack
静态模块打包工具
静态模块:指的是编写代码过程中的,html,css,js,图片等固定内容的文件
打包:把静态模块内容,压缩,整合,转译等(前端工程化)
步骤
-
新建并初始化项目,编写业务源代码
初始化文件夹,并使用npm init -y 初始化package.json文件
建立src文件夹,并创建utils/check.js文件和index.js文件
-
下载webpack webpack-cli到当前项目中,并配置局部自定义命令
1
| npm i webpack webpack-cli --save-dev
|
1 2 3
| "scripts" : { "build" : "webpack" }
|
-
运行打包命令,自动产生dis分发文件夹
修改Webpack打包入口和出口
步骤:
-
在项目根目录创建webpack.config.js配置文件
-
导出配置对象,配置入口,出口文件的路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const path = require("path")
module.exports = { entry:path.resolve(__dirname, "src/login/index.js"), output : { path:path.resolve(__dirname, 'dist'), filename:"./login/index.js", clean:true } }
|
webpack自动生成html文件
使用插件html-webpack-plugin,在webpack打包文件的时候自动生成html文件
插件网址
安装
1
| npm install --save-dev html-webpack-plugin
|
步骤
-
下载html-webpack-plugin本地软件包
1
| npm install --save-dev html-webpack-plugin
|
-
配置webpack.config.js让webpack拥有插件国能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const HtmlWebpackPlugin = require('html-webpack-plugin'); const path = require('path');
module.exports = { entry: 'index.js', output: { path: path.resolve(__dirname, './dist'), filename: 'index_bundle.js', }, plugins: [ new HtmlWebpackPlugin({ template:'./public/login.html', filename:'./login/index.html' }) ], };
|
webpack打包css代码
将css代码打包到js中
引入文件css-loader:解析css代码
下载
1
| npm install --save-dev css-loader
|
引入文件 style-loader:把解析后的css代码插入到DOM中
下载
1
| npm install --save-dev style-loader
|
步骤
-
准备css文件代码到src/login/index.js中
在index.js中使用
1
| import css from "file.css";
|
-
下载css-loader 和 style-loader本地软件包
-
配置webpack.config.js让webpack拥有该加载器功能
1 2 3 4 5 6 7 8 9 10 11
| module.exports = { module: { rules: [ { test: /\.css$/i, use: ["style-loader", "css-loader"], }, ], }, };
|
-
webpack打包
webpack提取css代码
将css代码单独提取出来,方便加载
插件:mini-css-extract-plugin提取css代码
下载:
1
| npm install --save-dev mini-css-extract-plugin
|
步骤
-
现在mini-css-extract-plugin本地软件包
-
配置webpack.config.js让Webpack拥有该插件功能
需要创建声明变量MiniCssExtractPlugin
并在plugins中new MiniCssExtractPlugin
在module中使用
1 2 3 4 5 6 7 8 9 10 11 12 13
| const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = { plugins: [new MiniCssExtractPlugin()], module: { rules: [ { test: /\.css$/i, use: [MiniCssExtractPlugin.loader, "css-loader"], }, ], }, };
|
-
打包
注意:不能和style-loader一起使用
webpack压缩css
使用css-minimizer-webpack-plugin来压缩css
下载方式
1
| npm install css-minimizer-webpack-plugin --save-dev
|
配置文件webpack.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = { module: { rules: [ { test: /.s?css$/, use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"], }, ], }, optimization: { minimizer: [ new CssMinimizerPlugin(), ], }, plugins: [new MiniCssExtractPlugin()], };
|
webpack打包less代码
加载器less-loader:把less代码编译为css代码
安装:
1
| npm install less less-loader --save-dev
|
配置,在webpack.config.js中配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| module.exports = { module: { rules: [ { test: /\.less$/i, use: [ 'style-loader', 'css-loader', 'less-loader', ], }, ], }, };
|
webpack打包图片
资源模块:Webpack5内置资源模块(字体图片)打包,无需额外的loader
步骤:
-
配置webpack.config.js让Webpack拥有打包图片的功能
占位符【hash】对模块内容做算法计算,得到映射的数字字母组合的字符串
占位符【ext】使用当前模块原本的占位符,例如:.png / .jpg等字符串
占位符【query】保留引入文件时代码中的查询参数(只有URL下生效)
1 2 3 4 5 6 7 8 9 10 11 12 13
| module.exports = { module: { rules: [ { test: /\.(png|jpg|jpeg|gif)$/i, type: 'asset', generator: { filename: 'assets/[hash][ext][query]' } }, ], }, };
|
用户登录功能
步骤
-
现在axios包
-
准备修改utils工具包源代码导出实现函数
request.js导入axios,外加导出方便外部使用
1 2 3
| import axios from 'axios'
export default axios
|
alert.js 通过export导出
1 2 3 4 5 6 7 8 9 10 11 12 13
| export 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) }
|
-
导入并边界写逻辑代码,打包后运行观察效果
搭建开发环境
开发环境官方文档
注意:只适合开发环境使用
问题:之前改代码,需重新打包才能运行查看,效率很低
开发环境:配置webpack-dev-server 快速开发应用程序
作用:启动 Web 服务,自动检测代码变化,热更新到网页
步骤
-
下载webpack-dev-server软件包到当前项目
1
| npm install --save-dev webpack-dev-server
|
-
设置模式为开发者模式,并配置自定义命令
webpack.config.js
1 2 3
| module.exports = { mode:'development' }
|
package.json
1 2 3
| "scripts": { "dev":"webpack serve --open" },
|
-
使用npm run dev来启动开发者服务器
注意:
- webpack-dev-server 借助 http 模块创建 88 默认 Web 服务
- 默认以 public 文件夹作为服务器根目录
- webpack-dev-server 根据配置,打包相关代码在内存当中,作为服务器根目录,以output.path的值作为服务器的根目录,可以直接访问dis目录下的内容
打包模式
模式区别
告诉webpack使用相应模式的内置优化
模式名称 |
模式名字 |
特点 |
场景 |
开发模式 |
development |
调试代码,实时加载,模块热替换 |
本地开发 |
生产模式 |
production |
压缩代码,资源优化,更轻量 |
打包上线 |
设置
-
在 webpack.config.js 配置文件设置 mode 选项
1 2 3
| module.exports = { mode:'production' }
|
-
在 packagejson 命令行设置 mode 参数
1 2 3
| "scripts": { "dev":"webpack serve --mode=dev" },
|
命令行配置优先级高于配置文件中的,推荐使用命令行
模式应用
需求:在开发模式下用 style-loader 内嵌更快,在生产模式下提取 css代码
方案1: webpack.config.is 配置导出函数,但是局限性大 (只接受 2种模式)
方案2:借助 cross-env (跨平台通用)包命令,设置参数区分环境
步骤:
-
下载cross-env软件包到当前项目
1
| npm i corss-env --save-dev
|
-
配置自定义命令,传入参数名和值(会绑定到process.env对象下)
cross-env 创建env对象
NODE_ENV:参数名
1 2 3 4 5
| "scripts":{ "test": "echo "Error: no test specified\" && exit 1", "build" : "cross-env NODE_ENV=production webpack --mode=production", "dev" : "cross-env NODE_ENV=development webpack serve --open --mode=development" }
|
根据不同的模式来进行不同的模块
需求:在开发模式下用 style-loader 内嵌更快,在生产模式下提取 css代码
1 2 3 4 5 6 7 8 9
| module: { rules: [ { test: /\.css$/i, use: [process.env.NODE_ENV === 'development' ? 'style-loader' : MiniCssExtractPlugin.loader, "css-loader"] }, ], },
|
注入环境变量
需求:
前端项目中,开发模式下打印语句生效,生产模式下打印语句失效
问题:cross-env 设置的只在 Nodejs 环境生效,前端代码无法访问 process.env.NODE ENV
解决:使用Webpack 内置的 DefinePlugin 插件
作用:在编译时,将前端代码中匹配的变量名,替换为值或表达式
1 2 3 4 5 6 7 8 9
| const webpack = require("webpack")
module.exports = { plugins : [ new webpack.DefinePlugin({ 'process.env.NODE_ENV' : JSON.stringify(process.env.NODE_ENV) }) ] }
|
在出口文件index.js文件中
1 2 3 4
| if(process.env.NODE_ENV === 'production'){ console.log = function(){} } console.log("开发模式下使用,生产模式下失效")
|
开发环境调错-source map
问题:代码被压缩和混淆,无法正确定位源代码位置(行数和列数
source map:可以准确追踪 error和 warning 在原始代码的位置
设置:webpack.config.js配置devtool选项
1 2 3
| model.exports = { devtool:'inline-source-map' }
|
inline-source-map选项:把源码的位置信息一起打包在js文件内
注意:source map仅适用于开发环境,不要再生产环境中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const config = { entry:path.resolve(__dirname, "src/login/index.js"), output : { path:path.resolve(__dirname, 'dist'), filename:"./login/index.js", clean:true }, plugins : [ new HtmlWebpackPlugin({ template:path.resolve(__dirname, "public/login.html"), filename:path.resolve(__dirname, "dist/login/index.html") }), new MiniCssExtractPlugin() ] }
if(process.env.NODE_ENV === 'development'){ config.devtool = 'inline-source-map' } module.exports = config
|
解析别名alias
解析别名:配置模块如何解析,创建 import 引入路径的别名,来确保模块引入变得更简单
例如:原来路径如图,比较长而且相对路径不安全
1
| import {checkCode, checkPhone} from "../utils/check.js"
|
解决:在webpack.config.js 中配置解析别名@来代表 src绝对路径
1 2 3 4 5 6 7
| const config = { resolve : { alias: { '@' : path.resolve(__dirname, "src") } } }
|
1
| import {checkCode, checkPhone} from "@/utils/check.js"
|
优化:CDN使用
CDN定义:内容分发网络,指的是一组分布在各个地区的服务器
作用:把静态资源文件/第三方库放在CDN 网络中各个服务器中,供用户就近请求获取
好处:减轻自己服务器请求压力,就近请求物理延迟低,配套缓存策略
git
配置个人信息
命令:
- git -v 查看git版本
配置用户信息,每次使用git提交的时候需要说明你的身份
- git config --global user.name “用户名”
- git config --global user.email “用户邮箱”
管理个人仓库
个人仓库是一个名字为.git的文件夹
Git仓库:记录文件状态内容的地方,存储着修改的历史记录
创建
- 把本地文件夹转换成 Git 仓库: 命令 git init
- 从其他服务器上克隆 Git 仓库
git三个区域
-
工作区:
实际开发时操作的文件夹, 自己创建的文件夹
-
暂存区
保存之前的准备区域, .git/index文件夹
-
版本库
提交并保存暂存区中的内容,产生一个版本快照 .git/objects
命令 |
作用 |
git add 文件名 |
暂存指定文件 |
git add . |
暂存所有改动过的文件 |
git commit -m “注释说明” |
提交并保存,产生版本快照 |
git ls-files |
查找当前暂存区中的文件 |
通过git add 文件名和git add . 将工作区中的内容保存到暂存区中
通过git commit -m “注释说明” 将暂存区中的内容保存到版本库中
git 文件状态
两种:
未跟踪:新文件,从未被Git管理过
已跟踪:Git已经知道和管理的文件
新文件需要通过git add 文件路径,来称为已跟踪状态
文件状态 |
概念 |
场景 |
未跟踪(U) |
从未被Git管理过 |
新文件 |
未添加(A) |
第一次被Git暂存 |
之前版本记录无此文件 |
未修改(’’) |
三个区域统一 |
提交保存后 |
已修改(M) |
工作区内容变化 |
修改内容产生 |
使用git status -s查看文件状态
暂存区的使用
暂时存储,可以临时恢复代码内容,与版本库解耦
可以从暂存区恢复到工作区,命令git restore 目标文件
从暂存区移除文件,命令:git rm --cached 目标文件
步骤:
先使用git ls-files 获取当前文件的目录
找到我们想要恢复的文件
使用git restore 文件路径 恢复文件
查看git提交版本:命令 git log --oneline
git 回退版本
概念:把版本库某个版本对应的内容快照,恢复到工作区/暂存区
查看提交历史: git log --oneline
1 2 3
| $ git log --oneline 5feae4c (HEAD -> master) 2.新增内容页面,修改login页面的位置 487e712 生成登录页
|
回退命令
-
git reset --soft 版本号 保留当前文件,恢复版本库中的文件,如版本库中只含有html文件,则使用soft恢复之后,只会修改html中的内容,不会修改别的已经生成的内容
-
git reset --hard 版本号 覆盖当前文件
-
git reset --mixed 版本号 保留工作区的文件
如果回退过多想要回退到上一步的,但是已经找不到原先的版本号可以使用 git reflog --oneline
删除文件
删除某一个文件,并产生一次版本记录
步骤
- 手动删除工作区文件
- 使用git add .暂存变更
- 提交保存
总结:只要工作区变更,都可以暂存提交产生新纪录
忽略文件
概念:.gitignore 文件可以让git 彻底忽略跟踪指定文件
目的:让git 仓库更小更快,避免重复无意义的文件管理
在项目根目录创建 .gitignore文件
例如:
-
系统或软件自动生成的文件
如node_modules
-
编译产生的结果文件
如:dist
-
运行时生成的日志文件,缓存文件,临时文件等
如:.vscode *.pem *.cer *.log
-
涉密文件,密码,秘钥等文件
1 2 3 4 5 6 7
| node_modules dist .vscode *.prm *.cer *.log password.txt
|
分支
创建分支
概念:本质上是指向提交节点的可变指针,默认名字是 master
注意:HEAD 指针影响工作区/暂存区的代码状态
场景:开发新需求/修复bug,保证主线代码可以随时使用,多人协作同时开发提交效率
例如:
在现有代码上创建新分支完成内容列表业务
突然需要紧急修复 Bug- 单独创建分支解决Bug
步骤:
- 创建分支命令:git branch 分支名
- 切换分支命令:git checkout 分支名
- 工作区准备代码暂存提交,重复三次
- git branch 可以查看当前项目有多少个分支
分支合并与删除
步骤
- 切回要合并的分支上:git checkout master
- 合并其他分支过来:git merge publish
- 删除合并之后的指针:git branch -d login-bug
分支合并与提交
合并提交:发生于原分支产生了新的提交记录后,再合并回去时发生,自动使用多个快照记录合并后产生一次新的提交
- 切回要合并的分支上:git checkout master
- 合并其他分支过来:git merge publish
- 删除合并之后的指针:git branch -d login-bug
分支合并冲突
需求1:基于 master 新建 publish 分支,完成发布文章业务,然后修改内容页面的 html文件的 title 标签,并提交一次
需求2:切换到 master,也在修改内容页面的 html文件的 title 标签,并提交一次
冲突:把 publish 分支合并到 master 回来,产生合并冲突
概念:不同分支中,对同一个文件的同一部分修改,Git 无法干净的合并,产生合并冲突
解决:
- 打开VSCode找到冲突文件并手动解决
- 解决后需要提交一次记录
git远程仓库
概念:托管在因特网或其他网络中的你的项目的版本库
作用:保存版本库的历史记录,多人协作
步骤
-
创建远程仓库账号
-
新建仓库,获取远程仓库git地址
-
给本地Git仓库添加远程仓库原点地址
命令:git remote add 远程仓库别名 远程仓库地址
-
将本地Git仓库推送版本记录到远程仓库
命令:git push -u 远程仓库别名 本地和远程分支名
例如:git push -u origin master
删除远程连接 git remote remove 远程仓库别名
克隆
从无到有的进程
克隆:拷贝一个Git仓库到本地,进行使用
命令:git clone 远程仓库地址
效果:在运行命令所在文件夹,生成对应的仓库名文件夹(包含版本库,并映射到暂存区和工作区)
更新
更新文件夹中的内容
命令:git pull origin(远程仓库) master(远程仓库分支)
gitee创建网页
- 初始化本地 Git 仓库(这次是非空文件夹-配套素材 dist 文件夹)
- 初始化远程 Git 仓库(这一次也是非空的)
- 本地配置远程仓库链接
- 本地拉取合并一下(确认本地要包含远程内容时使用)
- 本地推送到远程Git仓库
- 开启page网页服务得到地址浏览
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 分支名 |
把分支提交的历史记录合并到当前分支 |
|
远程
命令 |
作用 |
注意 |
git remote add 远程仓库别名 远程仓库地址 |
添加远程仓库地址 |
唯一别名,地址是以.git结尾的网址 |
git remote -v |
查看远程仓库地址 |
|
git remote remove 远程仓库别名 |
删除远程仓库地址 |
|
git pull 远程仓库别名 分支名 |
拉取 |
完整写法:git pull 远程仓库别名 远程仓库分支名:本地分支名 等价于:git fetch 和 git merge |
git push 远程仓库名 分支名 -u |
推送 |
完整写法:git pull 远程仓库别名 远程仓库分支名:本地分支名 -u 建立通道后可以简写成 git push |
git pull --rebase 远程仓库别名 分支名 |
拉取合并 |
合并没有关系的记录 |
git clone 远程仓库地址 |
克隆 |
从0到一的 |