vue数据响应式原理

  • 在vue初始化的时候,会对data当中的每一个属性进行遍历,创建对应的setter和getters,并创建与这个key对应的dep,这个dep是一个数组,然后当对模板进行编译的时候,发现有使用到这个data当中的某一个属性的时候,就会创建一个watcher并添加到与data当中属性对应的dep当中

  • 当数据发生变化的时候,observe监视到了,就会通知dep当的所有watcher,然后watcher触发更新

内联样式和其他优先级计算

样式类别

  • 行内样式(内联样式): 直接写在标签上的属性

    • <标签名 style="属性1:属性值1;属性2:属性值2;">内容</标签名>
  • **内部样式:**将css样式写在style当中,比如下面代码

1
2
3
4
5
6
7
8
9
<head>
<style type="text/css">
div{
color: red;
font-size: 12px;
}
</style>
</head>

  • 外部样式: 通过link标签引入的
    • 比如<link rel="stylesheet" type="text/css" href="css路径"/>

优先级

根据权重来计算出来的

  • 没有行内样式的情况下是权重来计算,比如通配符*的权重为1 , 标签选择器的权重为10,类选择器的权重为100,id选择器的权重为1000来进行计算的(权重值可能不一样,但是是这样子来算的),内联样式最高
  • !import会将至最高级!

数组的遍历

具体看mdnweb吧~这里只举例子

forEach

for循环

map

filter

for…of

for…in

vue的生命周期

  • VUE的生命周期钩子函数:就是指在一个组件从创建到销毁的过程自动执行的函数,在组件的整个生命周期内,钩子函数都是可被自动调用的,且生命周期函数的执行顺序与书写的顺序无关
  • vue2的生命周期
1
2
3
4
5
6
7
8
9
beforeCreate 创建前
created 创建后
beforeMount 挂载前
mounted 挂载后

beforeUpdate 更新前
updated 更新后
beforeDestroy 销毁前
destroyed 销毁后
  • 需要重点关注的4个生命周期

  • beforeCreate 创建前

    • vue实例刚刚被创建(刚执行new的操作),其他什么都没有做
  • created 创建后

    • 此时data,methods被绑定到了实例身上,但是依旧获取不到DOM
  • beforeMount

    • 此时DOM为虚拟的DOM,无法操作虚拟的DOM
  • mounted

    • 此时DOM已经生成并且被替换为了具体的数据,可以操作DOM了

v-mode的实现原理和在自定义组件中怎么实现

v-model原来写法

1
<input type="text" v-model="msg"/>

v-model拆解写法

1
<input type="text" :value="msg" @input="msg = $event.target.value"/>

v-model当中在自定义组件要怎么实现呢?

父组件当中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div>
<One v-model="money"></One>
<div>父容器的money{{ money }}</div>
</div>
</template>

<script>
export default {
name: "",
data() {
return {
money: 1000,
};
},
};
</script>

子组件当中:

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
<input :value="value" @input="$emit('input',$event.target.value)" />
<div>儿子当中的值{{ value }}</div>
</div>
</template>

<script>
export default {
name: "One",
props:["value"]
};
</script>

效果

路由导航守卫

  • beforeEach: 全局前置守卫(在所有的路由到来之前都需要经过,所以是Each这个单词)
  • **beforeEnter:**路由独享守卫
  • 还有
    • **beforeResolve:**全局解析守卫
    • afterEach:全局后置钩子
  • 还有组件内的守卫
    • beforeRouteEnter:
    • beforeRouteUpdate:
    • beforeRouteLeave:
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
//官方版
const UserDetails = {
template: `...`,
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
}

//注释版
beforeRouteEnter(to,from,next){
console.log("路由进入之前",to,from);
//手动调用next()方法,确保路由跳转继续执行
next();
}
beforeRouteUpdate(to,from,next){
console.log("路由更新之前",to,from);
next();
}
beforeRouteLevae(to,from,next){
console.log("路由离开之前",to,from);
next();
}

伪类和伪元素

  • 伪类作用对象是整个元素,比如整个链接,整个段落,容器div等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
a:link {
color: #111
}

a:hover {
color: #222
}

div:first-child {
color: #333
}

div:nth-child(3) {
color: #444
}
等等
  • 而伪元素作用于元素的一部分,一个段落的第一行或者第一个字母
1
2
3
4
5
6
7
8
9
10
11
12
13
14
p::first-line {
color: #555
}

p::first-letter {
color: #666
}
a::after {
content: "helloWorld"
}

a::before {
content: "helloWorld"
}

用promise写个ajax(仿照axios)

  • 首先了解下xhr发送时候的状态码
    • 0 代表还没有调用open
    • 1 代表还没有调用send方法
    • 2 代表还没有收到响应(刚刚发送)
    • 3 代表收到了部分数据
    • 4 代表收到了完整的数据
  • xhr的onreadystatechange是异步的~(因为有回调函数)

代码:

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
<script>
//类似于axios({...})
const axios = (options) => {
//获取method 和 url
let {
method,
url
} = options;
if (method && url) {
var returnPromise = new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status >= 200 & xhr.status < 300) {
resolve(xhr.response);
}
};
//监听错误的
xhr.onerror = function () {
reject(new Error(xhr.statusText))
}
});
return returnPromise;
} else {
return Promise.reject("请输入完整的参数");
}
}
axios({
method: "get",
url: "https://api.oick.cn/dog/ap1i.php"
}).then(data => {
console.log(data);
});
</script>

css画一个梯形和三角形

基本原理都是利用边框

三角形:

@在线演示

关键是设置width为0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<head>

<style>
*{
margin: 0;
padding: 0;
}
#app{
width: 0;
border-top: 100px solid transparent;
border-bottom: 100px solid green;
border-left: 100px solid transparent;
border-right: 100px solid transparent;
}
</style>
</head>
<body>
<div id="app"></div>
</body>
</html>

梯形:

@在线演示

关键是设置width和height的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<head>
<style>
*{
margin: 0;
padding: 0;
}
#app{
width: 50px;
height: 50px;
border-top: 100px solid transparent;
border-bottom: 100px solid green;
border-left: 100px solid transparent;
border-right: 100px solid transparent;
}
</style>
</head>
<body>
<div id="app"></div>
</body>
</html>

http和https响应的状态码有哪些

  • 200~300一般是请求成功的
    • 204代表服务器处理了请求,但没有返回任何数据(也就是没有内容)
  • 404 (not found) 找不到页面
  • 403 服务器拒绝了请求
  • 503 服务不可用
  • cookie: 存储的数据比较小,4kb左右,在有效期之前一直存在,一般用于验证用户,比如说token,并且每次都会携带在HTTP请求头中
  • localStorage: 可以长期存储在用户电脑并且所有的网页都可以访问,存储大小大概5m
  • sessionStorage: 有效期存储在当前页面,页面关闭后sessionStorage就失效(不违法同源策略的情况下),存储大小大概5m

TCP三次握手,四次挥手

TCP三次握手

  • 目的就是为了确认双方的接收能力和发送能力是否正常

  • 客户端向服务端发送一个SYN(同步序列),等待服务器确认

  • 服务端收到客户端的SYN后进行确认客户端的SYN包,然后也发送一个自己的SYN包,发送(SYN+ACK)包给客户端

  • 客户端收到SYN+ACK包后,向服务端发送确认包ACK,发送完成则建立连接,开始传输数据

四次挥手

  • 客户端发出连接释放的报文,并 进入终止等待1 状态
  • 服务器收到连接释放报文,发出确认报文,并且服务器进入关闭等待状态
  • 客户端收到服务器确认请求后,进入 终止等待2 状态,(管子里面还有数据,要流完!)
  • 服务器将最后的数据传输完成的时候,就告诉客户端,向客户端发送连接释放报文,并且服务器进入最后确认状态
  • 客户端收到服务器的连接释放报文后,必须发出确认,此时客户端进入时间等待状态,注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态
  • 服务器收到客户端发送的确认,立即进入关闭状态

大概过程

for … in 和for … of

简单来说一句话的博主

for…in是es5的用于遍历key

for…of是es6的用于遍历value

  • 先有in,再由of
1
2
3
4
5
6
7
8
9
10
11
12
<script>
var arr = ["李白", "诗人", "陋室铭"];
for (var key in arr) {
//0 1 2
console.log(key);
}

for (var value of arr) {
//李白 诗人 陋室铭
console.log(value);
}
</script>

一个比较神奇的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Object.prototype.objCustom = function () {}; 
Array.prototype.arrCustom = function () {};

let iterable = [3, 5, 7];
iterable.foo = "hello";

for (let i in iterable) {
console.log(i); // 0, 1, 2, "foo", "arrCustom", "objCustom"
}
//arrCustom是继承自Array的属性,objCustom是继承自Object的属性。

for (let i of iterable) {
console.log(i); // 3, 5, 7
}

nginx正向代理,反向代理

正向代理:

​ 面向客户,帮助客户解决问题,比如说我要访问YouTube,配置nginx后就可以访问了,这就是正向代理

反向代理:

​ 面向服务器,帮助服务器解决问题,比如配置代理转发请求,原来是本地的请求转发到远程

css九宫格

弹性盒布局笔记

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
<!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>Document</title>
<style>
*{
margin: 0;
padding: 0;
}
#wrap{
width: 306px;
height: 304px;
display: flex;
border: 1px solid red;
/* 关键是设置弹性盒是否换行,不然的话就会压缩 */
flex-wrap: wrap;
}
#wrap div{
width: 100px;
height: 100px;
border: 1px solid blue;
}
</style>
</head>
<body>
<div id="wrap">
<div id="one"></div>
<div id="two"></div>
<div id="three"></div>
<div id="four"></div>
<div id="five"></div>
<div id="six"></div>
<div id="seven"></div>
<div id="eight"></div>
<div id="nine"></div>
</div>
</body>
</html>

效果图

微任务和宏任务

宏任务:定时器

微任务:promise

微任务的优先级大于宏任务

computed和methods区别

  • computed是带缓存的,只有当引用的数据发生变化的时候才会重新计算,而methods是每次调用都会重新执行

  • computed是响应式的,methods不是响应式的

    • 当computed当中靠依赖的值计算出来的结果发生改变的时候,会引发computed重新计算,而methods不会
  • computed可以具有getter和setter方法,因此可以赋值,而methods是不行的。

    • 比如computed
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
data(){
return {
name:"李",
lastName:"白"
}
},
computed:{
fullName(){
return this.name+this.lastName;
},
//等同于
fullName:{
get(){
return this.name+this.lastName
}
},
//写全点这样子写
fullName:{
get(){
return this.name+this.lastName;
},
set(newValue){
//处理newValue
}
}
}

闭包和原型和原型链

闭包

  • 内部函数有对上层作用域的引用
  • 内部函数在所在定义域外保持引用并进行访问
  • 这位博主写的很详细@地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
function father(){
var a = 100;
var b = function(){
//内部函数有对上层作用域的引用
console.log(a);
a++;
}
return b;
}
// 内部函数在所在定义域外保持引用并进行访问
var temp = father();
temp();
temp();
temp();
</script>

原型和原型链

原型

  • 每一个函数(比如说构造函数),都具有一个属性叫prototype,这个属性指向的是一个对象,我们叫这个对象叫原型对象
  • 每一个实例化对象,都具有一个属性叫__proto__,这个属性指向其构造函数的prototype属性,并且有如下关系(实例化对象.__proto__ === 构造函数.prototype)

原型链

  • 当对象在自身找不到被调用的属性或者方法的时候,就会去原型链身上寻找
  • 以下代码寻找属性sex过程
    • 1.在自身上寻找sex属性,如果有,就返回,没有就接着下一步寻找
    • 2.在其构造函数身上寻找是否有sex属性(通过实例化对象.__proto__来访问),有就返回,没有就下一步寻找
    • 3.在其构造函数身上的上一层再次寻找(因为原型对象也是一个对象,是一个实例对象),通过实例化对象.__proto__.__proto__来进行访问并寻找是否有sex属性
    • 发现没有,就返回undefined,
1
2
3
4
5
6
7
8
9
10
11
12
<script>
function Dog(name, color) {
this.name = name;
this.color = color;
}
//为原型链上添加一个属性'sex';
Dog.prototype.sex = "未知";
//建立一个实例化对象
var xiaobai = new Dog("小白", "白色");
//输出结果为 '未知'
console.log(xiaobai.age);
</script>
证明在原型链上寻找
  • hasOwnProperty查找某一个对象自身是否有某一个属性,不会去查找他的原型链
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
function Dog(name, color) {
this.name = name;
this.color = color;
}
//为原型链上添加一个属性'sex';
Dog.prototype.sex = "未知";
//建立一个实例化对象
var xiaobai = new Dog("小白", "白色");
//搜索实例化对象身上是否有属性'sex'
var result = xiaobai.hasOwnProperty("sex");
//输出结果为false
console.log(result);
</script>

说一说new会发生什么?

  1. 创建一个新的实例化对象

  2. 该构造函数的this指向新的实例化对象

  3. 执行该构造函数体

  4. 返回这个this(如果没有返回值的情况下)

  5. 创建一个新对象,并在内存当中开辟一个新对象

  6. 将新对象的__proto__(隐式原型链)指向构造函数的prototype(显示原型链)

  7. 将构造函数的this指向新的实例化对象

  8. 返回这个新对象

项目中的难点

  1. 编程式路由往同一地址跳转时会报错的情况,很莫名其妙,虽然不影响,但是控制台总是会报错,找github找百度才解决

router配置文件中添加如下代码

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
const originalPush = VueRouter.prototype.push;
//解决重复提交相同链接报错
VueRouter.prototype.push = function push(location, onResolve, onReject) {
if (onResolve || onReject)
return originalPush.call(this, location, onResolve, onReject)
return originalPush.call(this, location).catch((err) => {
if (VueRouter.isNavigationFailure(err)) {
// resolve err
return err
}
// rethrow error
return Promise.reject(err)
})
}
const originalReplace = VueRouter.prototype.replace;
VueRouter.prototype.replace = function replace(location, onResolve, onReject) {
if (onResolve || onReject){
//回调函数里面会用到this的指向,所以就要使用call
return originalReplace.call(this, location, onResolve, onReject)
}
return originalReplace.call(this, location).catch((err) => {
if (VueRouter.isNavigationFailure(err)) {
//如果为相同链接引发的错误,返回错误原因,promise状态为resolve
// resolve err
return err
}
// rethrow error
return Promise.reject(err)
})
}
  1. 轮播图问题,数据显示正常,但是轮播图好像有问题,后面发现是数据在到达之前就设置swiper轮播图,所以添加$nextTick即可,保证数据有了后,dom被渲染了后在执行swiper初始化操作
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
watch:{
/* 监视bannerList数据更新 */
bannerList(){
/* 等待页面更新完成后执行回调 */
this.$nextTick(()=>{
//不应该使用类选择器的,这样子后期生成会选择所有相同的类!!
var mySwiper = new Swiper(this.$refs.mySwiper, {
// direction: 'vertical', // 垂直切换选项
loop: true, // 循环模式选项
// 如果需要分页器
pagination: {
el: ".swiper-pagination",
},
autoplay: {
//触碰后不会停止自动切换
disableOnInteraction: false,
},
// 如果需要前进后退按钮
navigation: {
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev",
},
});
});
}
},

数组的去重

  • set构造函数去重
    • 调用set构造函数转换为一个集合,在进行转换回去数组(扩展运算符…或者Array.from)就实现了去重
1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
var tempArray = [1,2,3,4,5,5,6,7];
//转化为set
var tempSet = new Set(tempArray);
//set转换回来数组 - 方法1
var tempAfterArray1 = [...tempSet];
///set转换回来数组 - 方法2
var tempAfterArray2 = Array.from(tempSet);
//[1, 2, 3, 4, 5, 6, 7]
console.log(tempAfterArray1);
//[1, 2, 3, 4, 5, 6, 7]
console.log(tempAfterArray2);
</script>
  • 普通方法去重1-双重for循环
    • 通过双重for循环,第一层循环为i的时候,第二层循环就从i+1位置开始遍历,如果发现相同的,则通过splice来删除,(splice会改变原数组)
1
2
3
4
5
6
7
8
for(var i = 0;i<tempArray.length;i++){
for(var j = i+1;j<tempArray.length;j++){
if(tempArray[i] == tempArray[j]){
tempArray.splice(j,1);
j--;
}
}
}
  • 普通方法去重-filter和indexOf结合
    • indexOf在数组中,返回该数组中第一个找到的索引位置,若未找到,则返回-1
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
var tempArray = [1, 2, 5, 5, 6, 6, 7];

//item为当前遍历的项
//index为当前遍历项的索引
var a = tempArray.filter((item, index) => {
return tempArray.indexOf(item) == index;
})
//[1, 2, 5, 6, 7]
console.log(a);

//遍历过程
item = 1,index=0
tempArray.indexOf(item) 返回 0
return 0 == 0 ;//为true,存储'1'

item = 2,index=1
tempArray.indexOf(item) 返回 1
return 1 == 1 ;//为true,存储'2'


item = 5,index=2
tempArray.indexOf(item) 返回 2
return 2 == 2 ;//为true,存储'5'

item = 5,index=3
tempArray.indexOf(item) 返回 2
return 2 == 3 ;//为false,不存储


item = 6,index=4
tempArray.indexOf(item) 返回 4
return 4 == 4 ;//为true,存储'6'

item = 6,index=5
tempArray.indexOf(item) 返回 4
return 4 == 5 ;//为false,不存储

item = 7,index=6
tempArray.indexOf(item) 返回 6
return 6 == 6 ;//为true,存储'7'
  • 使用indexOf
1
2
3
4
5
6
let temp = [];
tempArray.forEach(item=>{
if(temp.indexOf(item)===-1){
temp.push(item);
}
})

ES6+的常见语法

let

特点:

  • 没有变量提升的功能
  • 块级作用域
  • 不可以重复声明
    • 不可以出现let a = 100;然后又出现let a = 90;但是var变量可以
  • 具有暂时性死锁

解构赋值

数组的解构赋值

1
2
3
4
5
6
7
8
var str = "动感超人&18";
let[name,age] = str.split("&");
console.log(name,age);//动感超人 18

也可以跳过接收
var str = "动感超人&18";
let[,age] = str.split("&");
console.log(age);// 18

对象的解构赋值

1
2
3
4
5
6
var objName = {
name:"李白",
age:2000,
}
let {name,age}=objName;
console.log(name,age);//李白 2000

箭头函数

  • this始终指向函数声明时所在作用域下的this的值(在什么环境下,什么this执行,不会受其他改变)

Promise

promise常用的方法

  • Promise.reject(reason)方法返回一个带有拒绝原因的Promise对象。
  • Promise.resolve(value)方法返回一个以给定值解析后的Promise 对象
  • Promise.all(iterable)方法获取这个可迭代对象的promise的结果,
    • 当iterable有一个是reject,那么就会触发catch,返回的值就是这个reject传递过来的值
    • 当iterable全部为resolve,那么就会触发then,返回的值就是由resolve传递过来的值组成的数组
    • 一句话,有错误立即输出错误的reject,没有错误就输出resolve返回的值
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
//iterable中有一个是reject
<script>
var p1 = Promise.resolve("promise1");
var p2 = Promise.reject("promise2");
var p3 = Promise.resolve("promise3");
Promise.all([p1, p2, p3]).then((data) => {
//不会被执行
console.log(data);
}).catch(function (error) {
//catch方法将会被执行,输出结果为:"promise2"
console.log(error);
});
</script>

//iterable均为resolve
<script>
var p1 = Promise.resolve("promise1");
var p2 = Promise.resolve("promise2");
var p3 = Promise.resolve("promise3");
Promise.all([p1, p2, p3]).then((data) => {
//输出['promise1', 'promise2', 'promise3']
console.log(data);
}).catch(function (error) {
//不会被执行
console.log(error);
});
</script>

  • Promise.race(iterable)
    • 一句话概括:这个iterable竞争,只要有一个完成了,那么这个Promise.race的返回值就是这个第一个完成的值,不管结果本身是成功状态还是失败状态。

class类

可选链操作符

  • ?. 为可选链操作符
  • 有一段数据,我们是通过服务器来请求的,但是可以这段数据会嵌套很多层,有时候为了不报错,我们不得不去判断是否有数据才会去接着寻找下一层
  • 比如下面这层嵌套
1
2
3
4
5
6
7
8
9
var a = {
b:{
c:{
d:{
name:"李白的师傅"
}
}
}
}
  • 在这之前,我们需要找寻字段d的数据,就要这样子,才不会发生报错
1
var dataD = a && a.b && a.b.c && a.b.c.d && a.b.c.d.name
  • 有了可选链操作符,就不用这么麻烦了,当某一段/某一层数据不存在的时候,会返回undefined
1
var dataD = a?.b?.c?.d?.name

ES6模块化

async await

  • 一句话概括作用:使得异步的代码像同步一样实现

async的返回值

  • async函数的返回值都是promise
  • 如果返回一个Promise类型的对象,由这个返回的Promise对象决定是resolve或者reject
1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
async function getResult(){
return Promise.resolve("解决了");
}
console.log(getResult());
</script>

<script>
async function getResult(){
return Promise.reject("失败了");
}
console.log(getResult());
</script>

resolve-输出一个状态为pending的promise

resolve-输出一个状态为pending的promise

reject-输出一个状态为pending的promise 并引发报错提示

reject-输出一个状态为pending的promise

  • 如果返回一个非Promise类型,返回的结果js会自动帮助封装成为一个promise对象
1
2
3
4
5
6
<script>
async function getResult(){
return "看看我是什么"
}
console.log(getResult());
</script>

返回一个状态为fulfiled的promise

返回一个状态为fulfiled的promise

  • 抛出错误 ,返回的结果是一个失败的Promise对象
1
2
3
4
5
6
<script>
async function getResult(){
return new Error('出错啦')
}
console.log(getResult());
</script>

抛出错误的async返回值

await

有时候在项目当中,经常使用await来发送ajax请求获取数据后的操作,因为await可以阻塞进程,等待这个promise有结果后才开始后面的代码!!!

await遇上成功的promise(也就是resolve了)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
function testAwait() {
return new Promise((resolve, reject) => {
resolve("成功解决问题")
})
}

async function getResult() {
var result;
try {
result = await testAwait();
console.log("成功了,结果为-", result);
} catch (error) {
console.log("捕捉到失败,原因为-", error);
}
console.log("我可不可以执行到这里")
}
console.log(getResult());
</script>

执行结果

await遇上失败的promise(也就是reject了)

例子1: 遇上失败的promise并且使用try...catch捕捉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script>
function testAwait() {
return new Promise((resolve, reject) => {
reject("失败了")
})
}

async function getResult() {
var result;
try {
result = await testAwait();
console.log("成功了,结果为",result);
} catch (error) {
console.log("捕捉到失败,原因为",error);
}
//输出结果证明可以
console.log("我可不可以执行到这里")
}
console.log(getResult());
</script>

例子1输出结果

遇上失败的promise并且使用`try...catch`捕捉

例子2: 遇上失败的promise没有使用,没有使用try..catch捕捉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script>
function testAwait() {
return new Promise((resolve, reject) => {
reject("失败了")
})
}

async function getResult() {
var result = await testAwait();;
console.log("获取到的结果为",result);
console.log("我可不可以执行到这里");//输出结果证明不会执行到这里
// try {
// result = await testAwait();
// console.log("成功了,结果为",result);
// } catch (error) {
// console.log("捕捉到失败,原因为",error);
// }
}
console.log(getResult());
</script>

例子2输出结果

会发现报错了,并且console.log("我可不可以执行到这里") 没有执行,因为promise失败导致程序中断!

遇上失败的promise没有使用,没有使用`try..catch捕捉`

总结: await遇上失败的promise(也就是reject了)
  • await一旦遇到reject,后面代码就不会被执行了
  • 可以使用try...catch来解决,并且catch捕捉到的原因为reject传递的参数值

模板字符串

tab上面按键的符号 里面可以用${变量名}来使用变量

1
`你的名字为${name},年龄为${age}`

什么是执行上下文

​ 执行上下文是指函数调用时在执行栈中产生的当前函数的执行环境,该环境如隔绝外部世界的容器边界,保管可访问的变量、this对象等。(说通俗点就是函数执行时候的一个环境)

执行上下文

分类

  • 分为全局执行上下文(我理解为刚执行js脚本时候的一些初始化操作,比如我们没有写window对象我们却可以使用window对象,并且却可以直接输出this)
  • 函数执行上下文

注意

不管是全局执行上下文,还是函数执行上下文,上下文的创建过程都是如下

  • 第一步:创建函数上下文,压入栈
    1. 收集变量,形成变量对象(里面也包含了this这个变量)
    2. 确定this的指向(全局执行上下文的this指向的是window)
    3. 确定作用域链(作用域链,查找变量的过程,如果在作用域链上找不到就报错),作用域链是数组的形式
  • 第二步: 执行上下文(也就是执行里面的代码)

使用这一段代码来作为全局执行上下文和函数执行上下文的例子

1
2
3
4
5
6
7
<script>
function Dog(name,age){
this.name = name;
this.age = age;
}
var xiaobai = new Dog("小白",10);
</script>

1.先全局执行上下文

第一步:创建全局执行上下文,压入栈

1.收集变量,形成变量对象

收集变量,形成变量对象

2.确定this的指向

2确定this的指向

3.确定作用域链

如图

第二步:执行全局上下文 比如说赋值

要执行的代码如下,注意注释

  • 执行可以执行的代码,比如说函数的调用,变量的赋值之类的
1
2
3
4
5
6
7
8
9
<script>
//函数定义,不执行
function Dog(name,age){
this.name = name;
this.age = age;
}
//执行
var xiaobai = new Dog("小白",10);
</script>

执行var xiaobai = new Dog("小白",10);代码的过程 这里是在调用函数的过程,所以可以理解为在调用函数执行上下文,所以我们跳转到函数执行上下文的过程

2.函数执行上下文

第一步:创建函数执行上下文

1.收集变量,形成变量对象,2.确定this的指向 3.确定作用域链

如图

第二步:执行函数上下文

1
2
3
4
5
function Dog(name,age){
//执行里面的代码
this.name = name;
this.age = age;
}

如图

注意: 执行函数上下文完成,所属的栈会被丢弃!

但是,丢弃之前返回了this指向,并存储在了变量xiaobai当中

1
2
3
4
5
6
7
8
9
function Dog(name,age){
//执行里面的代码,执行完成后销毁
this.name = name;
this.age = age;
//你不添加系统也会自动加一个
return this;
}

var xiaobai = new Dog("小白",10);

如图,销毁前返回了this并保存在了变量xiaobai当中

如图

数据绑定原理

需要解决的2个问题

  • 如何知道data的属性发生了变化的 (重要)
    • 也就通过observer为data当中的每一个属性通过数据劫持添加gettersetter
    • 原来是没有gettersetter的,通过数据劫持去添加了set,get
  • 那么要如何知道当前这个数据变化要更新哪些节点呢?
    • 订阅者/发布者模式

img

1.发布者observer

  • 给data中的所有层次的属性都添加settergetter(也就是数据劫持)
  • 同时为data当中的每一个属性创建一个对应的dep对象
  • dep对象data当中的每一个属性为一一对应的对象

1.5中间还需要一个dep(订阅器),去通知订阅者

  • dep和watcher的关系是初始化的时候就建立起来的

2.订阅者(watcher)

  • 解析每一个模板语法都创建一个watcher(比如模板当中有一个标签使用了插值语法,那就会创建一个watcher,v-bind也会触发创建watcher)
  • 并且创建watcher的时候最后一个参数为用于更新节点的回调函数
  • 订阅者需要知道数据变了,从而去更新界面

webpack

说说看什么是webpack

  • webpack就是一个模块化打包工具,它将所有的文件看成是模块,它会分析项目目录下的模块(比如说less,sass等),并将其转换和打包为合适的格式供浏览器使用

什么是loader,什么又是plugin

  • loader: 是文件加载器,可以加载资源文件,并对这些文件进行一些处理,比如:编译,压缩等,
  • plugin:webpack在运行的生命周期会广播出许多事件,plugin可以监听这些事件,在合适的时机中通过webpack提供的api改变输出结果

区别:

  • loader是将A文件进行编译形成B文件,这里操作的是文件,A.less => A.css
  • plugin是用于在webpack打包编译过程里,在对应的事件节点里执行自定义操作,比如资源管理、bundle文件优化等操作

有哪些常见的Loader,他们都是解决什么问题的

  • 原本的webpack只能处理js/json的模块,有了loader或者plugin后,就可以处理更多的模块了

常见的模块

  1. css-loader: 加载.css文件(也就是可以使用css模块了)
  2. style-loader:使用<style>将css-loader内部样式注入到我们的HTML页面
    1. 也就是将css-loader内部样式通过js在页面head部分创建style标签并将样式放入style标签内
  3. bable-loader:es6转化为es6(js语法转换)
  4. mini-css-extract-plugin: 提取css为单独的文件
  5. postcss(完整的应该为postcss postcss-loader postcss-preset-env) :处理css兼容性问题

解释下引用数据类型的赋值和基本数据类型的赋值

  • 二种赋值都是将变量保存的内容赋值给另外一个变量
  • 只不过基本数据类型的变量保存的是值,所以基本数据类型赋值给别人的是值
  • 而引用数据类型变量保存的是地址,所以赋值的是一个地址给别的变量

nodejs的引入/暴露和es6的引入/暴露-简记

  • 这里只记录了下简单的,新遇到的问题,具体想看es6的暴露和引入的具体的,可以看我之前写的文章,@地址

es6

引入

  • es6的引入需要在模块的最前面,不可以出现如下代码(使用静态的import会出现的问题)
1
2
3
4
5
import a from "./a.js"

function getNumber(){
import b from "./b.js";
}

否者出现就会报错,提示导入声明只能在模块的顶层使用

  • 那是因为你使用了静态的import,标准用法的 import 导入的模块是静态的,会使所有被导入的模块,在加载时就被编译(无法做到按需编译,降低首页加载速度)。有些场景中,你可能希望根据条件导入模块或者按需导入模块,这时你可以使用动态导入代替静态导入
    • 在vue路由的时候,经常使用这种动态的导入方法
1
2
3
4
5
6
7
8
9
10
import('/modules/my-module.js')
.then((module) => {
// Do something with the module.
});
//这种使用方式也支持 await 关键字。
async function getNumber(){
const b = await import("./b.js");
console.log(b);
}
getNumber();

暴露

  • es6的暴露出去的都是对象
    • 在引入的时候除了默认暴露,其他的基本上都需要解构赋值才可以

nodejs

引入

  • nodejs是commojs的规范,他可以在使用到的时候再引入
  • 比如下面有一个需求,就是是当用户请求某一个路由的时候,我才去读取数据并返回给用户,这个时候就可以体现出nodejs引入的好处了,是代码执行到这一段的时候才去引入
1
2
3
4
5
6
// 分类页
router.get("/getCategoryData",(ctx)=>{
//读取分类页数据,代码执行到这一行才开始引入
const categoryData = require("../datas/categoryDatas.json");
ctx.body = categoryData
})

暴露

  • node提供了二种暴露方式

    • module.exports
    • exports
  • 不想看下面的可以记住这句话就可以,不要使用exports去暴露,使用module.exports去暴露

  • exportsmodule.exports是一个引用

    • 当通过exports去改变内存块里内容时,module.exports的值也会改变
    • 当通过module.exports去改变内存块里内容时,exports的值也会改变
    • 当module.exports所指向的地址被改变的时候,exports不会被改变
    • 当exports所指向的地址被改变的时候,module.exports不会被改变
  • node的暴露可以简单理解为暴露什么,引入的就是什么

暴露引入如下-暴露什么,引入的就是什么

说说三级联动

  • 最典型的三级联动比如说地址选择的时候,叫我们依次选择省-市-区
  • 省-市-区在数据设置的时候就是这样子设置去实现三级联动的
    • 除了省外,每一级的数据都包括上一级的id信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"province": [
{
"name": "江西省",
"id": 1
}
],
"city": [
{
"name": "南昌市",
"id": 10,
"parentId": 1
}
],
"area": [
{
"name": "高新区",
"id": 100,
"parentId": 1
}
]
}

网页的seo优化

  • meta标签,比如添加keywords属性和description属性

  • 比如我的@网站就添加下面二个meta优化seo收录

    1
    2
    <meta name="keywords" content="前端,JavaScript,nodejs,es5,es6,vue">
    <meta name="description" content="小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程">
  • 也可以通过一些语义化标签比如

    • header
    • footer
    • title
    • img中的alt属性
  • 更多关于SEO可以看这个wiki网站

图片的alt和title

  • alt
    • 网络错误、内容被屏蔽或链接过期时,会显示alt 属性中的文本
  • title
    • 鼠标悬停时候的提示信息
    • title 属性不是 alt 属性可接受的替代品。并且,避免将 alt 属性的值直接复制到同一幅图片的title 属性上。这样可能会让一些屏幕阅读器把同一段描述读两遍,造成一定程度上的困扰。

更改this的执行的方法

假设有一个函数fn需要改变this执行,要怎么做

  • call,apply,bind方法作用
    • 让任意的函数,成为对象的方法,从而改变this的指向

call方法

  • @mdnwebDoc - call

  • fn.call(this的指向,参数1,参数2,参数3,….)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    function Product(name, price) {
    this.name = name;
    this.price = price;
    }

    function Food(name, price) {
    Product.call(this, name, price);
    this.category = 'food';
    }

    //等同于,至于为什么等于
    //这就要涉及到创建一个构造函数的时候究竟做了什么
    //创建构造函数其实就是在this(新实例化对象上)上添加赋值并返回这个this
    //所以你看构造函数都没有返回值,那是因为系统自动添加了返回值
    //自动添加了return this
    function Food(name, price) {
    this.name = name;
    this.price = price;
    this.category = 'food';
    }
    console.log(new Food('cheese', 5).name);
    // expected output: "cheese"

  • 备注:该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组

语法

1
function.call(thisArg, arg1, arg2, ...)

参数

thisArg

  • 可选的。在 function 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。

arg1, arg2, ...

  • 指定的参数列表

apply方法

语法

1
2
apply(thisArg)
apply(thisArg, argsArray)

参数说明

thisArg

  • func 函数运行时使用的 this 值。请注意,this 可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。

argsArray

  • 一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 nullundefined,则表示不需要传入任何参数。从 ECMAScript 5 开始可以使用类数组对象。浏览器兼容性请参阅本文底部内容。

bind方法

  • fn.bind(this的指向,[参数1,参数2,…])

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const module = {
    x: 42,
    getX: function() {
    return this.x;
    }
    };

    const unboundGetX = module.getX;
    console.log(unboundGetX()); //里面的this指向window
    //window里面没有x变量,所以返回undefined

    //更改this的执行,返回一个函数,并赋值给boundGetX
    const boundGetX = unboundGetX.bind(module);
    //执行更改this后的函数
    console.log(boundGetX());//输出42
  • bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

语法

1
function.bind(thisArg[, arg1[, arg2[, ...]]])

参数

thisArg

  • 调用绑定函数时作为 this 参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。当使用 bindsetTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object。如果 bind 函数的参数列表为空,或者thisArgnullundefined,执行作用域的 this 将被视为新函数的 thisArg

arg1, arg2, ...

  • 当目标函数被调用时,被预置入绑定函数的参数列表中的参数。

布局 - 圣杯布局

  • 圣杯布局-左右两边宽度固定,中间自适应

示例1 - float布局

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
<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>Document</title>
<style>
*{
margin: 0;
padding: 0;
}
.left{
width: 100px;
height: 100%;
background-color: red;
float: left;
}
.content{
width: calc(100% - 200px);
height: 100%;
background-color: green;
float: left;
}
.right{
width: 100px;
height: 100%;
background-color: blue;
float: right;
}
</style>
</head>
<body>
<!-- 圣杯布局 左右两边宽度固定,中间自适应-->
<div class="left">

</div>
<div class="content">

</div>
<div class="right">

</div>
</body>
</html>
  • 或者上面一个在套一层外壳
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
<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>Document</title>
<style>
*{
margin: 0;
padding: 0;
}
.container{
/* 解决高度塌陷 */
overflow: hidden;
}
.left{
width: 100px;
height: 100%;
background-color: red;
float: left;
}
.content{
width:calc(100% - 200px);
height: 100%;
background-color: green;
float: left;
}
.right{
width: 100px;
height: 100%;
background-color: blue;
float: right;
}
</style>
</head>
<body>
<!-- 圣杯布局 左右两边宽度固定,中间自适应-->
<div class="container">


<div class="left">

</div>
<div class="content">

</div>
<div class="right">

</div>
</div>
</body>
</html>

示例2-display布局

  • 设置外层父元素display:flex,中间设置增长系数为flex-grow:1即可,左右二边的宽度依旧是固定
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
<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>Document</title>
<style>
*{
margin: 0;
padding: 0;
}
.container{
width: 100%;
height: 100%;
display: flex;
}
.left{
width: 100px;
height: 100%;
background-color: red;
}
.content{
background-color: green;
flex-grow: 1;
}
.right{
width: 100px;
height: 100%;
background-color: blue;
}
</style>
</head>
<body>
<!-- 圣杯布局 左右两边宽度固定,中间自适应-->
<div class="container">

<div class="left">

</div>
<div class="content">
display:flex;
flex-grow:1;
</div>
<div class="right">

</div>
</div>
</body>
</html>

效果图

每一个js都需要导入Vue,并且多次去使用Vue.use(),会造成重复吗?

  • 不会,因为webpack打包的时候会有缓存
  • webpack的机制是允许多次引用,只会进行一次打包的。
  • @思否回答

Vue.use都做了那些事情

  • Vue.use我们可以传入一个对象和函数
  • 如果是一个对象,那么会去寻找对象当中的install方法,并会默认传入Vue参数给install方法

test.js文件

1
2
3
4
5
6
const reg = {
install(Vue){
console.log("传入的参数",Vue);
}
}
export default reg;

输出结果

main.js文件

1
2
3
4
import Vue from 'vue';

import Reg from "@/components/test.js";
Vue.use(Reg,"参数1");
  • 如果是一个函数,那么就会被视为install方法去执行这个函数
  • 那么Elementui是怎么做到的只需要引入并注册就可以全局使用自定义组件的呢?通过install方法
    • 如下示例,通过这样子,就可以全局使用了

test.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import MyButton from "./MyButton.vue";//引入自定义组件
const components = [
MyButton,
]
const reg = {
install(Vue){
//循环遍历注册组件
components.forEach(component=>{
//Vue.component("要注册的注册名称",注册的组件);
Vue.component(component.name,component);
})
}
}
export default reg;

main.js文件

1
2
3
4
import Vue from 'vue';

import Reg from "@/components/test.js";
Vue.use(Reg,"参数1");

说说vue3和vue2区别

  • vue3: 速度更快,体积减少,更易维护,更接近原生,更易使用,
  • vue3:组合式api
  • 巴拉巴拉巴拉,太复杂了这部分,具体百度

下面代码输出怎么输出2,怎么输出1

1
2
3
4
5
6
7
var a = 1;
var obj = {
a:2,
fn(){
console.log(this.a)
}
}
  • 输出2的情况
    • 当执行obj.fn的时候,this指向obj对象,所以输出obj对象当中的2
1
2
3
4
5
6
7
8
var a = 1;
var obj = {
a: 2,
fn() {
console.log(this.a);
},
};
obj.fn();//输出2
  • 输出1的情况
    • 需要改下,把函数改为箭头函数,因为箭头函数是没有this的所以这个时候的this执行上层作用域的this,也就是window,所以输出全局变量当中的1
      • 箭头函数中的this引用的是最近作用域中的this,即向外层作用域中逐级查找this,直到有this的定义。
1
2
3
4
5
6
7
8
var a = 1;
var obj = {
a: 2,
fn:()=>{
console.log(this.a);
},
};
obj.fn(); //输出1

写出如下代码要求

  • 要求书写一个函数如下
1
2
3
4
5
6
a=fn()
console.log(a()) //1
console.log(a()) //2

a(2) //2
a(3) //5
  • 写法
1
2
3
4
5
6
7
8
9
10
11
<script>
function fn() {
let a = 0;
return function (number = 1) {
return (a += number);
};
}
let a = fn();
console.log(a(2));
console.log(a(3));
</script>

要如何实现如下图布局

代码

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
<!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>Document</title>
<style>
*{
margin: 0;
padding: 0;
}
.container{
display: flex;
justify-content: space-between;
}
.container>div{
width: 100px;
height: 100px;
border: 3px solid red;
}
</style>
</head>
<body>
<div class="container">
<div></div>
<div></div>
<div></div>
</div>
</body>
</html>

效果

说说自定义组件怎么实现v-model(双向绑定)

  • 大概过程就是父组件给子组件添加一个属性v-model="要绑定的值"
    • 这其实是简写,全称为<自定义组件 :value="要绑定的值" @input="value = $event"></自定义组件>
    • 在自定义组件当中,$event就是$emit当中第二个参数传递过来的数据
  • 子组件接收到后使用props去接收这个绑定的值,并且是以属性名为value的情况下接收(也就是props:['value'])
  • 子组件在需要修改的地方去触发自定义事件input,并传递更新后的值

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>
我是父组件:<br/>
{{account}}
<Self v-model="account"></Self>
</div>
</template>

<script>
import Self from "./self.vue"
export default {
name: '',
data(){
return {
account:100
}
},
components:{
Self,
}
}
</script>

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div>
<div>我是自定义组件</div>
<div>
{{value}}
<!-- 修改值 -->
<input type="text" :value="value" @input="$emit('input',$event.target.value)"/>
</div>
</div>
</template>

<script>
export default {
name: '',
props:["value"]
}
</script>

Promise.all当失败的时候会返回失败的结果,有没有办法都返回成功的,不会失败

情况说明

  • 下面的代码只会输出执行结果-失败,因为Promise.all是当里面有一个是失败的时候,会只会返回那个失败的结果
  • 那么下面我就来说说看怎么解决这种情况,让我们可以获取不管失败还是成功的返回结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var a = new Promise((resolve,reject)=>{
// 模拟ajax请求
setTimeout(()=>{
resolve("执行结果-成功");
},1000)
});
var b = new Promise((resolve,reject)=>{
// 模拟ajax请求
setTimeout(()=>{
reject("执行结果-失败");
},1000)
});
Promise.all([a,b])
.then(response=>{
console.log(response);
document.write(response);
})
.catch(reason=>{
console.log(reason);
document.write(reason);
})

方法1: reject改为resolve

  • reject改为resolve,这样子不管成功还是失败都会返回结果了
  • @在线演示地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var a = new Promise((resolve,reject)=>{
// 模拟ajax请求
setTimeout(()=>{
resolve("执行结果-成功");
},1000)
});
var b = new Promise((resolve,reject)=>{
// 模拟ajax请求
setTimeout(()=>{

resolve("执行结果-失败");
},1000)
});
Promise.all([a,b])
.then(response=>{
console.log(response);
document.write(response);
})
.catch(reason=>{
console.log(reason);
document.write(reason);
})

方法2——-别人的

  • 首先我们需要知道Promise的状态具有可传递性,其次catch方法在执行后也会返回一个状态为resolved的新Promise实例,所以我们只要将可能reject的Promise实例先catch一遍就可以了,就像做一次状态预加工:
  • @原文链接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
});
var p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(2);
}, 1000);
});

var promiseArr = [p1, p2];
var promiseArr_ = promiseArr.map(function (promiseItem) {
return promiseItem.catch(function (err) {
return err;
})
});

Promise.all(promiseArr_)
.then((resp) => {
console.log(resp);//[1,2]
}).catch((err) => {
console.log(err);
});

Promise.then

  • Promise.then(成功回调函数,失败回调函数)(用的比较多的可能是只填写一个成功回调函数,然后通过catch来执行失败时候的回调的)
1
2
3
4
5
6
7
8
9
10
11
12
var p1 = new Promise((resolve, reject) => {
resolve('成功!');
// or
// reject(new Error("出错了!"));
});

p1.then(value => {
console.log(value); // 输出 成功!
}, reason => {
//执行reject,就会跳转到这里
console.error(reason);
});

输出结果如图

  • then在前面,并且then里面具有失败回调函数,那么执行then里面的不执行catch里面的失败回调函数
1
2
3
4
5
6
7
8
9
10
11
12
var p1 = new Promise((resolve, reject) => {
reject(new Error("出错了!"));
});
p1.then(value => {
console.log(value);
}, reason => {
//执行reject,就会跳转到这里
console.error(reason); // 出错了!
}).catch(res=>{
//不会执行这里的代码
console.log(res);
});

输出结果如图

  • 并且,Promise.then()Promise.catch()都会返回一个Promise,因为是方法嘛,会有返回值很正常

    • 我们知道,在没有书写返回值的情况下,不在特殊情况下(特殊情况下比如构造函数,就是返回this),会返回undefined,那么我们可以看看说这句话说的对不对

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      var p1 = new Promise((resolve, reject) => {
      reject(new Error("出错了!"));
      });
      p1
      .catch(res=>{
      //执行reject,就会跳转到这里
      console.log(res);//输出 出错了!
      return "123";
      })
      .then(
      // 成功回调
      (value) => {
      console.log(value);
      },
      // 失败回调
      (reason) => {
      //不会
      console.error(reason);
      }
      );
    • 执行上方代码,会输出出错了123

    • 那么我们改下那个return "123 ,把它改为return "456"

    二次return

  • 那么为什么上面代码只会执行成功回调呢?原因如下

    • return非promise,则PromiseState永远为resolve,且值为对应的回调函数(第一个或者第二个)的返回值
    • return 为Promise,则PromiseState为return当中的Promise执行结果,PromiseValue为return当中的Promise当中的resolve还是reject当中参数的值
    • 说简单点就是返回的不是Promise,那么就是resolve,会去执行resolve的回调,如果返回的是一个Promise,那么就是根据这个Promise成功或者失败的结果去执行对应的回调

Promise的链式调用

  • 看上方的Promise.then,原理就是Promise.thenPromise.catch会返回一个Promise

Promise.then有没有办法返回传递给下一个值到下面?

  • 有,自己返回一个Promise来代替默认的返回就可以

  • 示例1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var a = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("我是数据");
}, 1000);
});
a.then((res) => {
console.log("第一个then拿到的-" + res); //输出 第一个then拿到的-我是数据
//传递给下方
res += "-第一个then已读";
return Promise.resolve(res);
})
.then((res) => {
console.log("第二个then拿到的-" + res); //输出 第二个then拿到的-我是数据-第一个then已读
})
.catch((reason) => {
console.log("捕捉到错误", reason);
});

示例1-输出结果

  • 示例2
1
2
3
4
5
6
7
8
9
10
11
12
13
var a = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("我是数据")
}, 1000);
});
a
.then(res=>{
console.log("拿到的数据结果-"+res);//输出 拿到的数据结果-我是数据
return Promise.reject("出错啦")
})
.catch(reason=>{
console.log("捕捉到错误",reason);// 输出 捕捉到错误 出错啦
})

示例2-输出结果

var和let的区别

  • var是函数作用域,let是块级作用域(也就是一个个花括号)

  • 在函数中声明了var,整个函数内都是有效的,比如说在for循环内定义的一个var变量,实际上其在for循环以外也是可以访问的。

  • 而let由于是块作用域,所以如果在块作用域内定义的变量,比如说在for循环内,在其外面是不可被访问的,所以for循环推荐用let。

  • let不能在定义之前访问该变量,但是var可以(这个是let的暂时性死锁)

  • let不能被重新定义,但是var是可以的

    1
    2
    3
    4
    5
    let a = 10;
    let a = 100;//报错

    var a = 10;
    var a = 1000;//不报错,正确

说说css动画

  • animation@keyframs的结合使用
  • 过渡效果transition
  • 转变,转换效果transform,比如旋转(rotate),平移(translate)

说说computed和watch

computed

  • computed为一个计算属性,是通过data当中的属性值计算出来的一个新值,并且只有在依赖的属性值发生变化的时候才会重新计算并且默认第一次加载的时候就会调用一次computed
  • computed是同步的,不可以异步
  • 支持缓存,相依赖的数据发生改变才会重新计算

watch

  • watch是监听一个值的变化,然后执行相应的回调函数,并且默认第一次加载是不做监听的,如果需要第一次加载做监听,就需要设置immediate属性为true
  • watch可以是异步的,也可以是同步的,
  • watch不支持缓存

BFC

BFC是什么

  • BFC(Block Formatting Context)是块级格式上下文的缩写
    • BFC是指浏览器中创建了一个独立的渲染区域,并且拥有一套渲染规则,他决定了其子元素如何定位,以及与其他元素的相互关系和作用
    • 具有 BFC 特性的元素可以看作是隔离了的独立容器,容器里面的元素不会在布局上影响到外面的元素,并且 BFC 具有普通容器所没有的一些特性。通俗一点来讲,可以把 BFC 理解为一个封闭的大箱子,箱子内部的元素无论如何翻江倒海,都不会影响到外部
    • BFC,就是一个与世隔绝的独立区域,不会互相影响
  • BFC用处/应用场景
    • 清除子元素浮动带来的高度塌陷
    • 清除盒子垂直方外边距合并的问题( 外边距的塌陷问题 ( 垂直塌陷 ) )
      • 盒子垂直方向的距离由margin决定。属于同一个BFC的两个相邻盒子垂直方向的margin会发生重叠。
      • 或者是设置内部盒子的margin影响到了外部盒子

如何开启BFC

  • 根元素(自动开启)
  • 设置元素float不为none(比如设置float:left; float:right等) @webDoc-float
  • 设置元素overflow属性不为(visible)(比如设置overflow:hidden)
  • 设置元素displayflex,inline-block,table-cell,table-caption
  • 其他开启方法@webDoc-BFC

BFC用处/应用举例

  • BFC特征
    • BFC是页面上的一个独立容器,容器里面的子元素不会影响外面的元素
    • BFC内部的块级盒会在垂直方向上一个接一个排列(这个我不懂,后面用到再说~)
    • 同一BFC下的相邻块级元素可能发生外边距折叠,创建新的BFC可以避免外边距折叠
    • 每个元素的外边距的左边与包含块边框盒的左边相接触(从右向左的格式的话,则相反),即使存在浮动
    • 浮动盒的区域不会和BFC重叠(也就是BFC之间不会重叠)
    • 计算BFC的高度时,浮动元素也会参与计算

计算BFC的高度时,浮动元素也会参与计算

  • 一句话,高度塌陷造成的父元素高度问题
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<style type="text/css">
.parent {
width: 300px;
border: 1px solid black;
overflow: hidden;
}
.child {
width: 100px;
height: 100px;
border: 1px solid red;
float: left;
}
</style>
</head>
<body>
<div class="parent">
parent
<div class="child">child</div>
<div class="child">child</div>
</div>
<script>
const parentHeight = document.querySelector('.parent').clientHeight;
//父元素没有开启包含块的情况下输出21px;(可能不同浏览器有误差)
//因为内部元素浮动了造成父元素高度塌陷了
console.log(parentHeight);

//父元素开启包含块的情况下输出102px(可能不同浏览器有误差)
console.log(parentHeight);
</script>
</body>
</html>

浮动盒的区域不会和BFC重叠(也就是BFC之间不会重叠)

  • 如图,左边div浮动,右边没有,可以看到,右边div和左边div产生了重叠,我们可能不想要这种效果,只需要设置右边开启BFC即可(左边浮动已经开启了BFC);

  • 代码(解决重叠问题的代码)
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
<head>
<style>
* {
margin: 0;
padding: 0;
}
.left {
float: left;
height: 200px;
margin-right: 10px;
background-color: red;
}
.right {
/* 解决重叠关键代码,开启BFC */
overflow: hidden;
height: 300px;
background-color: green;
}
</style>
</head>
<body>
<div>
<div class="left">浮动元素,无固定宽度\</div>
<div class="right">自适应</div>
</div>
</body>
</html>

清除子元素浮动带来的高度塌陷

  • 例子略,这个应该都知道,就不举例子了

内部盒子margin影响到了外部盒子

  • 只需要设置外层父亲(红色区域)的开启BFC即可
    • 因为父元素开启BFC,那么不管内部元素怎么设置,都影响不到外部元素

清除盒子垂直方向外边距合并

  • 问题的本质就是兄弟元素之间的相邻外边距会去最大值而不是取和

    • 也就是说只需要通过某元素,某文字,某边框进行隔开也可以处理这种情况
    • 更多解决方法请看这篇博客@地址
  • 最典型的就是二个div,一个设置margin-bottom:100px,一个设置margin-top:100px,可是发现两者之间的垂直距离只有100px,而不是100+100 = 200px

  • @在线演示-BFC-清除盒子垂直方向上外边距合并

  • 如下图所示

示例代码

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
<!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>Document</title>
<style>
*{
margin: 0;
padding: 0;
}
.brother1{
width: 200px;
height: 200px;
background-color: red;
margin-bottom: 100px;
}
.brother2{
width: 200px;
height: 200px;
background-color: green;
margin-top: 100px;
}
</style>
</head>
<body>
<div class="brother1"></div>
<div class="brother2"></div>
</body>
</html>
  • 解决办法二个元素都设置为一个内部的元素,并将外部包裹的着开启BFC

示例代码

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
<!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>Document</title>
<style>
*{
margin: 0;
padding: 0;
}
.brother1{
width: 200px;
height: 200px;
background-color: red;
margin-bottom: 100px;
}
.brother2{
width: 200px;
height: 200px;
background-color: green;
margin-top: 100px;
}
.bfc{
overflow: hidden;
}
</style>
</head>
<body>
<div class="bfc">
<div class="brother1"></div>
</div>
<div class="bfc">
<div class="brother2"></div>
</div>
</body>
</html>

参考文章

https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flow_Layout/Intro_to_formatting_contexts

https://www.itcast.cn/news/20201016/16152387135.shtml

https://blog.csdn.net/qq_43205326/article/details/109789742

为什么要对axios进行二次封装?

  • 可以在ajax请求发送前和数据到达的时候做一些处理,比如发送前可以使用nprogress进行一个假加载进度条
  • 我们可以使用axios里面的请求拦截器对发送前的请求做处理,也有响应拦截器对传递回来的数据做处理
  • 也可以通过二次封装来设置默认发送的前缀,这样子就不用每一次发送ajax都添加域名了,也方便后期修改

koa模块的使用

  • Koa相比于express模块,koa更加方便和轻便的,有点像Python和c++他们之间的区别吧~

koa的一个完整示例

router/index.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const Router = require("koa-router");
const bodyParser = require('koa-bodyparser');

//1.生成路由实例
const router = new Router();
router.use(bodyParser()); //使用中间件(这样子就可以解析post参数)

router.get('/package/:aid/:cid',(ctx)=>{
//获取动态路由的传值
console.log(ctx.request.body);
ctx.body="随便返回东东";
})

module.exports = router;

server.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
const Koa = require("koa");


//1.生成应用实例
const app = new Koa();
const router = require("./router/index.js");


//2.使用路由中间件
//为什么还要通过routes方法呢?
//我猜是因为上一步的router.get 或者router.post
//都是将这些放在了一个数组当中,然后我们暴露的只是暴露一个对象
//要取出这些全部的路由信息,就需要通过router.routes()方法
app.use(router.routes());


//最后监听端口
app.listen("3001",(error)=>{
if(error){
console.log(error);
return;
}
console.log("服务器启动成功");
console.log("服务器地址:xxxxxx:3001");
});

get的参数获取

koa获取query参数

  • 通过ctx.query,会返回一个对象,包含着query参数,没有query参数的时候就会返回一个空对象
  • 如下图,访问http://localhost:3001/test?name=李白后输出ctx.query
1
2
3
4
5
6
7
8
9
const Router = require("koa-router");
const router = new Router();

router.get("/test",(ctx)=>{
console.log(ctx.query); //输出{name:'李白'}
ctx.body = "随便返回东东";
})

module.exports = router;

koa取params参数

  • 获取前和vue一样,需要在匹配路径的时候去占位!
  • 如下代码,浏览器输入网址http://localhost:3001/package/123/456后输出下面,即可获取params参数
1
2
3
4
5
6
7
8
9
const Router = require("koa-router");
const router = new Router();

router.get("/package/:aid/:cid",(ctx)=>{
console.log(ctx.params); //{ aid: '123', cid: '456' }
ctx.body = "随便返回东东";
})

module.exports = router;

post参数的获取

  • 和express,也需要一个中间件,koa获取post参数中间件为koa-bodyparser
  • 以下示例均访问网址http://localhost:3001/package/123/456并携带一个post参数

访问网址参数

  • 没有安装中间件
1
2
3
4
5
6
7
8
9
10
const Router = require("koa-router");
const router = new Router();

router.get('/package/:aid/:cid',(ctx)=>{
//获取post参数
console.log(ctx.request.body);//没有安装中间件,输出undefined
ctx.body="随便返回东东";
})

module.exports = router;
  • 安装中间件

    1
    npm install koa-bodyparser --save
  • 示例:安装中间件后

1
2
3
4
5
6
7
8
9
10
11
const Router = require("koa-router");
const router = new Router();
const bodyParser = require('koa-bodyparser');//引入
router.use(bodyParser());//使用中间件
router.get('/package/:aid/:cid',(ctx)=>{
//获取post参数
console.log(ctx.request.body);//安装中间件,输出{ age: '1000' }
ctx.body="随便返回东东";
})

module.exports = router;