Day1—Ajax
Ajax介绍
Ajax全称为Async JavaScript And XML
(异步的JS和XML)
是浏览器提供的前端数据请求的一种技术
他是异步执行的,和定时器是一样的
Ajax请求步骤
- 创建,
const xhr = new XMLHttpRequest()
- XML,数据格式
- http,超文本传输协议
- request,请求
- 连接,
xhr.open(请求方式, 请求地址, 是否异步)
- 请求有四种
get
常用于请求获取,有长度限制,在地址栏显示post
,常用于提交数据,没有长度限制,且安全put
,修改delete
,删除
- 请求地址是服务器地址,在get请求方式下参数需要在问号后使用
&
连接成字符串 - 是否异步,在我们使用过程中只使用异步,不使用同步
- 请求有四种
- 发送,
xhr = send()
- 在post请求模式下,参数需要放在send里发送
- 接收,
xhr.onload = function () { consol.log(xhr.responseText)}
xhr.responseText
是获取到的内容,是JSON串格式,因此先反序列化
Ajax参数发送
Ajax数据交互需要参数的时候分为两种情况
第一种是get请求方式,此时参数需要使用&
连接成字符串然后和请求地址一起发送xhr.open('get', 'http://localhost:8888/test/third?name=goudan&age=2', true)
第二种是post请求方式,此时参数需要和send一起发送,而且需要在请求头中定义内容格式
在创建后、发送前可以通过setRequestHeader
来发送请求头,xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
发送请求头后可以发送参数,需要使用&
连接成字符串,xhr.send('name=狗蛋&age=2')
上述中参数使用的是字符串格式发送,但实际开发中常用的是JSON串格式,因此请求头就需要写成xhr.setRequestHeader('Content-Type', 'application/json')
而参数则需要使用JSON串格式,xhr.send('{"name":"狗蛋","age":2}')
Ajax封装
在封装中参数过多的情况下,可以使用对象来传递配置项
function objectToString(obj) {
const arr = []
for (let key in obj) {
arr.push(key + '=' + obj[key]) // 拼格式 'a=1'
}
return arr.join('&') // 'a=1&b=2'
}
function getAjax(options) {
// 1. 创建
let xhr = new XMLHttpRequest()
// 2. 连接
xhr.open(options.method, `${options.url}?${objectToString(options.data)}`, true)
options.beforeSend && options.beforeSend(xhr)
// 3. 发送
xhr.send()
// 4. 接收
xhr.onload = function () {
let res = JSON.parse(xhr.responseText)
options.callback && options.callback(res) // 回调 数据 // 得到数据的时候才回调给使用者
}
}
function postAjax(options) {
// 1. 创建
let xhr = new XMLHttpRequest()
// 2. 连接
xhr.open(options.method, options.url, true)
options.beforeSend && options.beforeSend(xhr)
// 3. 发送
xhr.send(objectToString(options.data))
// 4. 接收
xhr.onload = function () {
let res = JSON.parse(xhr.responseText)
options.callback && options.callback(res) // 得到数据的时候才回调给使用者
}
}
回调函数
我们可以看到,在上述封装中我们在接受中写了回调函数,这是因为在实际开发中我们需要写很多业务逻辑,而这些不可能全写在接受函数中,因为有些时候我们并不需要这些操作,因此我们使用回调函数,将函数作为参数传递进接收函数中,这样在获取完信息后执行回调函数后就能取出接收到的数据了,此时我们就可以自定义各种业务操作而不会冲突
Day2—杂项
网络
网络协议
网络协议有两种http
和https
http
,全称超文本传输协议,端口默认为80
https
,全程有SSL安全证书的超文本传输协议,端口默认为443
三次握手
三次握手是发送、接受、返回
还有一个四次挥手,是发送、接受、返回、断开
通讯
在开始传输数据之前一定要确保网络连接
- 前端可以发送
- 后端可以接收
- 后端可以返回
- 前端可以接收
network
浏览器提供了一个网络方法,打开浏览器控制台,有一个network选项卡,其中有fetch/XHR
选项
fetch/XHR(ajax请求列表)
- header 标头
- 常规
- 地址:协议-域名-端口-query数据
- 请求方式: get/post/put/delete
- 状态码:200、304、404、403、500等
- 请求头
- token:登录状态
- content-type:请求数据类型
- cookie:缓存
- 响应头
- 常规
- preview 请求体:请求的参数
- JSON格式:’{“a”:1,”b”:2}
- urlencoded格式:’a=1&b=2&c=3’
- response响应体:后端响应的数据
跨域
协议、域名、端口有一个不一样就是跨域
JSONP:我们定义函数,对方执行调用,参数就是数据结果,利用scr的特性(可以访问外部代码)
new Proxy():正向代理,VUE常用
nginx:反向代理,Java常用
模拟百度输入框
<input type="text" class="txt">
<ul class="list"></ul>
<script>
// 获取元素
const oList = document.querySelector('.list')
const oTxt = document.querySelector('.txt')
// 数据回调函数
function show(obj) {
oList.innerHTML = ''
obj.g.map(item => {
oList.innerHTML += `<li>${item.q}</li>`
})
}
// 事件创建标签,调用回调函数
oTxt.oninput = function () {
const script = document.createElement('script')
script.src = `https://www.baidu.com/sugrec?pre=1&p=3&ie=utf-8&json=1&prod=pc&from=pc_web&sugsid=40375,40416,40445,40479,40487,40511,60045,60026,60033,60048,40080&wd=${oTxt.value}&csor=3&pwd=ad&cb=show&_=1713422215333`
document.body.appendChild(script)
}
</script>
Day3—Promise
Promise
Promise
是ES6新增的异步解决方案,他是为了解决回调地狱的
回调地狱
axios({
url: 'http:localhost:8888/test/first'
}).then(res => {
console.log(res)
axios({
url: 'http:localhost:8888/test/second'
}).then(res => {
console.log(res)
axios({
url: 'http:localhost:8888/test/third',
params: {
name: '张三',
age: 18
}
}).then(res => {
console.log(res)
})
})
})
new Promise((resolve,reject) => { 写ajax程序 }).then(res => { 接收到数据进行下一步操作 })
Promise
有两个函数参数resolve
和reject
,前者是成功,后者是失败
Promise对象有三种方法
then(res=>{})
回调函数是专门来处理成功状态,也就是当调用resolve
时
catch(res=>{})
回调函数是专门来处理失败状态,也就是调用reject
时
finally(res=>{})
,无论成功失败都是执行的回调函数
他们的使用顺序可以改变,但是建议还是按照then->catch->finally
的顺序来书写
但Promise并未完全解决回调地狱问题
Promise函数有四种静态方法
Promise.all()
:所有请求都成功才算成功
Promise.race()
:多个请求一个成功就算成功
Promise.allSettled()
:把所有请求都记录下来
Promise.any()
:无论成功失败都会继续执行
async和await
当使用async
关键字创建异步函数时,其内部就可以使用await
关键字来使程序变成同步
async function request() {
let arr = [] // 同步
await new Promise(resolve => { // 一个异步方法被改成同步
getAjax({ // ajax异步
url: 'http://localhost:8888/goods/list',
method: 'get',
data: {},
callback(res) {
resolve(res)
}
})
}).then(res => {
arr = res.list
})
console.log(arr) // [] 同步
}
request()
上例中,函数是异步函数,但其内部的程序被await转成了同步执行也就是console
会在ajax
之后执行
同时async
和await
程序的写法可以省略then
,如下例
function allAjax(options) {
if (options.method == 'get') {
getAjax(options)
} else {
postAjax(options)
}
}
function pAjax(options) {
return new Promise(resolve => { // 一个异步方法被改成同步
allAjax({ // ajax异步
url: options.url,
method: options.method,
data: options.data,
beforeSend: options.beforeSend,
callback(res) {
resolve(res)
}
})
})
}
// async 异步
// - await 同步 它只能等待一个promise
async function request() {
let userData = await pAjax({
url: 'http://localhost:8888/users/login',
method: 'post',
beforeSend(xhr) {
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded')
},
data: {
username: 'goudan',
password: '123456'
}
})
console.log(userData)
let res = await pAjax({
url: 'http://localhost:8888/goods/list',
method: 'get',
data: {}
})
console.log(res.list)
}
request()
ES7才真正解决了回调地狱问题
axios插件
axios
插件是对ajax的promise封装,它的使用方法需要配合async
和await
当请求方式是get时可以不写
axios({
url: 'http://localhost:8888/goods/list', // 请求地址
// method: 'get', // 请求方式
// params: {}, // query数据
// data: {}, // 请求体数据
}).then(res => {
console.log(res)
})
axios({
url: 'http://localhost:8888/users/login',
method: 'post',
headers: {
'content-type': 'application/x-www-form-urlencoded'
},
data: {
username: 'admin',
password: '123456'
}
}).then(res => {
console.log(res)
})
事件循环
JS的程序是在调用栈中以后进先出(LIFO)的顺序去执行的,因为JS是一门单线程语言,他只有一个调用栈,因此如果遇到需要长时间执行的任务那么进程无疑会卡死,为了不出现这种情况JS的程序分为同步和异步
同步的程序在调用栈中直接执行,而异步任务则发送给Web API
,Web API
根据程序种类将程序分配到不同的先进先出(FIFO)队列中,分别是宏任务队列和微任务队列,宏任务队列中存储的是计时器、定时器、I/O事件等,微任务队列存储的是then
等
当遇到一长串程序时,调用栈首先从第一行程序开始执行,普通同步程序流程为入栈—>执行—>出栈,当遇到函数调用时则是入栈—>调用—>函数体程序入栈—>执行—>依次出栈,而如果是异步程序则直接存到Web API
,然后Web API
根据程序类型分配给不同队列,与此同时调用栈仍在继续往下执行程序,不会出现进程卡死
当程序执行完,调用栈为空时,调用栈会去调用微任务队列中存储的异步程序然后按照之前的步骤依次执行
当微任务队列也空了的时候,调用栈去调用宏任务队列中存储的异步程序,因为这些程序中有可能包含微任务,因此每执行完一个宏任务之后就要轮询一次微任务队列,直到宏任务队列也为空
console.log('同步1')
setTimeout(function () {
console.log('timeout')
}, 0)
new Promise(resolve => {
console.log('promise')
resolve()
}).then(res => {
console.log('then')
})
console.log('同步2')
// 执行顺序:同步1 promise 同步2 then timeout
终极案例
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
new Promise(resolve => {
console.log(3)
resolve()
}).then(() => {
console.log(4)
})
setTimeout(() => {
console.log(5)
new Promise(resolve => {
console.log(6)
setTimeout(() => {
console.log(7)
})
resolve()
}).then(() => {
console.log(8)
})
}, 500)
new Promise(resolve => {
console.log(9)
resolve()
}).then(() => {
console.log(10)
setTimeout(() => {
console.log(11)
}, 0)
})
console.log(12)
// 执行顺序为:1/3/9/12/4/10/2/11/5/6/8/7
上述程序的执行顺序如下
- 第一遍调用栈执行
- 结果为1、3、9、12
- 首先第1行输出
1
,然后遇到2~4行的定时器发送给Web API,然后是5~8行的Promise输出3
,然后将8~10行的then发送给Web API,然后将11~22行的定时器发送给Web API,然后是23~26的Promise输出9
,然后将26~31行的then发送给Web API,然后第32行输出12
- 与此同时Web API对任务进行分配,将then按顺序分配到微任务队列,11~22行的定时器是500ms的延迟,因此保留,将2~4行的0ms定时器分配到宏任务队列
- 第二遍调用栈,程序执行完调用栈空,从微任务队列提取
- 结果为4、10
- 首先8~10行的then输出
4
,然后是26~31行的then输出10
,然后将28~30行的定时器发送给Web API - 以此同时Web API对任务进行分配,11~22行的定时器500ms还没到,因此保留,将刚收到的28~30行的0ms定时器分配到宏任务队列
- 第三遍调用栈,微任务队列为空,从宏任务队列提取
- 结果为2、11
- 首先2~4行的定时器输出
2
,然后轮询微任务队列,微任务队列为空,因此继续下一个宏任务,28~30行的定时器输出11
- 与此同时Web API对任务分配,11~22行定时器500ms过期分配给宏任务队列
- 第四遍调用栈,微任务队列为空,从宏任务队列提取
- 结果为5、6
- 第12行输出
5
,13~19行的Promise输出6
,然后15~17行的定时器发送到Web API,然后是19~21的then发送到Web API - 与此同时Web API开始分配任务,将15~17行定时器分配到宏任务队列,将19~21行的then分配到微任务队列
- 第五遍调用栈,执行完一个宏任务,轮询微任务队列
- 结果为8
- 19~21行的then输出
8
- 第六遍调用栈,微任务队列空,调用宏任务队列
- 结果为7
- 15~17行定时器输出
7
最终结果为:1、3、9、12、4、10、2、11、5、6、8、7
Day4—树形数据和list数据
list数据
最常见的数据,分为普通list数据和关联性list数据
let data = [
{
id: 9527
},
{
id: 9528
},
{
id: 9529
},
{
id: 95230
},
]
关联性list数据
let list = [
{
pid: 0,
id: 1,
name: '张三',
},
{
pid: 0,
id: 2,
name: '张三',
},
{
pid: 1,
id: 3,
name: '张三',
},
{
pid: 2,
id: 4,
name: '张三',
},
{
pid: 4,
id: 5,
name: '张三',
},
{
pid: 0,
id: 6,
name: '张三',
},
{
pid: 2,
id: 7,
name: '张三',
},
{
pid: 1,
id: 8,
name: '张三',
},
{
pid: 0,
id: 9,
name: '张三',
},
{
pid: 6,
id: 10,
name: '张三',
}
]
上例中pid
代表的是父节点的id
值
树形数据
树形数据中数据的关联性更强
const tree = [
{
pid: 0,
id: 1,
name: '张三',
children: [
{
pid: 1,
id: 3,
name: '张三',
},
{
pid: 1,
id: 8,
name: '张三',
}
]
},
{
pid: 0,
id: 2,
name: '张三',
children: [
{
pid: 2,
id: 4,
name: '张三',
children: [
{
pid: 4,
id: 5,
name: '张三',
}
]
},
{
pid: 2,
id: 7,
name: '张三',
},
]
},
{
pid: 0,
id: 6,
name: '张三',
children: [
{
pid: 6,
id: 10,
name: '张三',
}
]
},
{
pid: 0,
id: 9,
name: '张三',
}
]
Day5—踩过的坑
form表单
在登陆或者注册这种多个input的情况下,一般会想到使用form表单,但form表单有个不方便的地方是在提交数据时会自动跳转刷新页面,因此我们一般不使用form表单,但是如果必须使用,则一定要在提交事件中阻止默认行为