26-Vue2.x串讲
# 一,Hello Vue
说明几点:
能否就业 ?= 基础扎实 + 项目经验 + 表达能力
学习习惯 = 重视基本功 + 亲力亲为准备面试题 + 动手犯错
2
Vue2特点:
- MVVM框架
- 响应式(声明式)
- 组件化(支持自定义组件)
- 丰富的指令(DOM功能的抽象)
- 基于选项的(template、data、computed、watch、methods)
- Vue文档集中(最好的教程)
- Vue生态丰富且简单
- 渐近式的(可大可小的项目都能做)
Vue开发思想:
当我们需要在交互事件中改变视图时,先在data选项中声明一个合适的变量,再在交互事件中改变这个声明式变量即可,视图自动更新,这是一种间接的操作。
参考代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h2>{{name}}</h2>
<!-- 指令的底层都是DOM操作 DOM操作抽象 -->
<h1 v-text="counter"></h1>
<button v-on:click="add">+1</button>
<hr>
<h2 v-bind:style="{color:aa}">我是一个h2</h2>
<button v-on:click="change">变色</button>
<hr>
<!-- 指令后面跟的基本上都是值,都是表达式 -->
<h2 v-if="show">我是一个h2</h2>
<button v-on:click="toggle">切换</button>
</div>
<script>
// Dom操作是直接操作DOM
// Vue、React:间接操作DOM
// Vue是一个构造器 new一个构造器就可以得到一个对象
// 在V3中就没法有这个构造器
// vm叫根组件 V3中App.vue组件叫根组件
// vm代理了data中的所有数据
let vm = new Vue({
// 把Vue组件(自带响应式系统)挂载到直接DOM上
el: "#app",
// 给模板提供响应式数据,数据变了,模板会自动刷新
data() {
return {
name: "码路",
counter: 1,
aa: "red",
show: true
}
},
methods: {
add() {
vm.counter++
},
change() {
this.aa = "blue"
},
toggle() {
this.show = !this.show
}
},
});
</script>
</body>
</html>
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
el
- 把vue组件(响应式系统)挂载到真实DOM
data
- 是专门用于定义声明式变量的,这些声明式变量有什么特点?当声明式变量变化时,它所对应的视图节点自动更新。这就是所谓的响应式。
methods
- 是专门用于定义函数方法的地方
数据响应式:
<script>
function upadteView() {
console.log("要更新视图了~");
}
function defineReactive(target, key, value) {
observer(value); // 深度递归遍历
// v2中响应式靠definedProperty
// defineProperty 定义属性
Object.defineProperty(target, key, {
// 当获取属性时,就会走get
get() {
console.log("get...");
// 收集依赖
return value
},
// 当设置属性时,会走set
set(newValue) {
// newValue也可能是一个对象,也需要深度递归遍历
observer(newValue)
console.log("set...");
value = newValue;
upadteView();
}
})
}
function observer(target) {
if (typeof target !== "object" || target === null) {
return target;
}
for (let key in target) {
defineReactive(target, key, target[key])
}
}
let data = {
name: "码路",
age: 18,
info: {
address: "bj"
}
}
data.info = {
a: {
b: {
c: 110
}
}
}
observer(data);
// console.log(data.name);
// console.log(data.age);
// data.name = "ok"
// console.log(data.name);
data.info.address = "sh"
</script>
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
# 二,指令
是否可以实现todoList案例是考察指令运用的是否灵活,地址:todolist.cn
指令分类:
- 文本相关的指令
- 属性绑定相关的指令
- 事件绑定相关的指令
- 表单相关的指令
- 列表渲染相关的指令
- 条件渲染相关的指令
- 其它指令
- 自定义指令
什么是指令?
就是vue内置的一套“模板(都是以v-),用于在视图节点上动态绑定变量(表达式)的。指令实际上是DOM功能的抽象,所以指令实际上也是DOM操作。
文本类指令
- {{}}文本插值、v-text、v-once、v-cloak、v-html
- {{}} 用于绑定节点的文本,它和v-text功能是一样的。区别是{{}}这种绑定值的方式会出现“{{}}一闪而过”的效果,建议使用v-cloak来解决。
- v-text 用于绑定节点的文件,在大多数时候,它和 {{}} 可以相互替换。
- v-once 用于指定节点的动态内容只绑定一次。当前节点中所对应的变量发生变化,视图不更新。一般情况下,v-once只能和{{}}一起用。
- v-once 和 v-cloak 都是不需要接收表达式来做为“值”
- v-html 用于绑定动态的html节点,相当于DOM中的 innerHTML。这个指令默认已经做了“防注入攻击XSS”的处理
2
3
4
5
6
动态属性指令 v-bind
- v-bind 用于动态绑定节点的属性(比如title、value、class、style等)
- v-bind 用得非常多,所以经常简写成 : <div :id='变量'></div>
- 动态class语法(一):<div :class='`${变量1} ${变量2} `'></div>
- 动态class语法(二):<div :class='[表达式1, 表达式2, ...]'></div>
- 动态class语法(三):<div :class='{ 类名1: 布尔值1, 类名2: 布尔值2, ... }'></div>
- 动态style语法(一):<div :style='`color:red;fontSize:20px`'></div>
- 动态style语法(二):<div :style='[{css键值对}, {css键值对}, ...]'></div>
- 动态style语法(三):<div :style='{css键值对}'></div>
2
3
4
5
6
7
8
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
[v-cloak] {
display: none;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<!-- ------------- 文本相关的指令 -->
<!-- <h2 v-cloak>{{name}}</h2>
<h2 v-text="name"></h2>
<h2 v-text="'年龄:'+age"></h2>
<h2 v-text="`年龄:${age}`"></h2>
<h2>{{'年龄:'+age}}</h2>
<div v-text="msg"></div>
<div v-html="msg"></div> -->
<!-- ------------- 动态绑定相关的指令 -->
<!-- <h2 v-bind:title="a1">我是一个h2</h2>
<h2 :title="a1">我是一个h2</h2>
<h2 v-bind="{title:a1,class:a2}"></h2>
<h2 v-bind:title="a1">我是一个h2</h2> -->
<!-- ------------- 动态绑定相关的指令 -->
<!-- <h1 :class="a3">动态绑定样式</h1>
<h1 :class="`${a3} ${a4}`">动态绑定样式</h1>
<h1 :class="[a3,a4]">动态绑定样式</h1>
<h1 :class="[a3,(2>1?'f1':'f2')]">动态绑定样式</h1>
<h1 :class="{box:true}">动态绑定样式</h1>
<h1 :class="{f1:Math.random()>0.5}">动态绑定样式</h1>
<h1 :class="{f1:110}">动态绑定样式</h1> -->
<!-- ------------- 动态绑定相关的指令 -->
<!-- <h1 style="color:red">动态绑定行内样式</h1>
<h1 :style="`color: yellowgreen; font-size: ${a6};`">22动态绑定行内样式</h1>
<h1 :style="{color:a5,fontSize:a6}">动态绑定行内样式</h1>
<h1 :style="[{color:'gold'},{fontSize:a7+'px'},{textDecoration:'line-through'}]">动态绑定行内样式</h1>
<h1 :style="{[a8]:a5,border:'1px solid blue'}">动态绑定行内样式</h1> -->
</div>
<script>
// setTimeout(() => {
let vm = new Vue({
el: "#app",
data() {
return {
name: "码路",
age: 18,
msg: `<a href="http://www.baidu.com">百度一下</a>`,
a1: 'ok',
a2: 'bad',
a3: "a33",
a4: "a44",
a5: "blue",
a6: "38px",
a7: 80,
a8: "color"
}
}
});
// }, 2000)
</script>
</body>
</html>
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
事件绑定 v-on
* v-on 用于给视图节点绑定各种JS事件,比如click、mouseenter、blur、keyup等
* v-on 用得非常多, 所以简写成 @ 基本语法: <div @事件名. 事件修饰符='事件处理器'></div>
* v-on 上可以使用事件修饰符: .stop阻止冒泡 .prevent阻止默认事件 .enter绑定键盘Enter键盘....
2
3
参考代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.outer {
width: 200px;
height: 200px;
background-color: gold;
display: flex;
justify-content: center;
align-items: center;
}
.inner {
width: 100px;
height: 100px;
background-color: skyblue;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<!-- ------------- 事件相关的指令 -->
<button v-on:click="handle">点我</button>
<button @click="handle">点我</button>
<!-- 事件相关的修饰符 stop -->
<!-- prevent once -->
<!-- 事件修饰符可以连用 -->
<!-- 事件对象,事件传参 -->
<div class="outer" @click="outerClick">
<div class="inner" @click.stop.once="innerClick"></div>
</div>
<button @click="fn">点我</button>
<button @click="gn(1,2)">点我</button>
<button @click="kn(1,2,$event)">点我</button>
</div>
<script>
let vm = new Vue({
el: "#app",
data() {
return {
// handle(){
// console.log("handle...");
// }
}
},
methods: {
handle() {
console.log("handle...");
},
outerClick() {
console.log("outerClick...");
},
innerClick() {
console.log("innerClick...");
},
fn(e) {
console.log("e:", e);
},
gn(a, b) {
console.log(a, b);
},
kn(a, b, e) {
// 和原生DOM中的事件对象是类似的
console.log(a, b, e);
}
},
});
</script>
</body>
</html>
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
表单绑定 v-model
* v-model 用于表单取值(表单双向绑定), 比如input/select/textarea等
* 基本语法: <input type="text" v-model. 表单修饰符='变量'/>
* 三个修饰符: .trim自动去除文本首尾空格 .number隐式类型转换变成Number类型 .lazy用于性能, 当表单失焦时再进行双向绑定
* v-model 还有更深的理解, 在组件化中进一步去理解
2
3
4
参考代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h2>{{name}}</h2>
<div>
<!-- 姓名:<input type="text" v-model="name" @keyup.enter="submit"> -->
<!-- 姓名:<input type="text" :value="name" @input="fn" @keyup.enter="submit"> -->
<!-- 姓名:<input type="text" :value="name" @input="name=$event.target.value" @keyup.enter="submit"> -->
<!-- v-model都有哪些修饰符?答:number trim lazy -->
<input type="text" v-model.trim="name" @keyup.enter="submit">
<input type="text" v-model.number="age" @keyup.enter="submit">
<h1>{{age}}</h1>
<input type="text" v-model.lazy="age" @keyup.enter="submit">
<input type="text" v-model.lazy.trim="age" @keyup.enter="submit">
<!-- v-model也可以写在组件上,在v2中和v3中是完全不一样的 -->
</div>
</div>
<script>
let vm = new Vue({
el: "#app",
data() {
return {
name: "码路",
age: 18
}
},
methods: {
submit(e) {
// console.log(this.name);
// console.log(e.target.value);
console.log(this.age);
},
fn(e) {
this.name = e.target.value;
}
},
});
</script>
</body>
</html>
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
列表渲染 v-for
* v-for 用于渲染列表、对象、Number变量等等
* 当 v-for 渲染列表时, 语法是这样的 <div v-for='(item, index) in array'></div>
* 当 v-for 渲染对象时, 语法是这样的 <div v-for='(value, key, index) in obj'></div>
* 当 v-for 渲染Number变量时, 语法是这样的 <div v-for='(num, index) in 5'></div>
* 注意: v-for在循环渲染时要求加key, 为什么呢? 在响应式原理时再解释
2
3
4
5
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h2>{{name}}</h2>
<!-- key:明天说 -->
<!-- v-for循环一个数组 -->
<div v-for="(item,index) in todolist" :style="{background:(item.done?'red':'transparent')}">
<input type="checkbox" @click="fn(item,index)" :checked="item.done">
<span>{{item.text}}</span>
<span>x</span>
</div>
<!-- v-for循环一个对象 -->
<div v-for="(value,key,index) in user">
<span>{{index}}</span>
<span>{{key}}</span>
<span>{{value}}</span>
</div>
<!-- v-for循环一个数字 -->
<div v-for="(num,index) in 5">
<span>{{index}}</span>
<span>{{num}}</span>
</div>
</div>
<script>
let vm = new Vue({
el: "#app",
data() {
return {
name: "码路",
todolist: [{
id: 1,
text: "学习vue1",
done: true
},
{
id: 2,
text: "学习vue2",
done: false
},
{
id: 3,
text: "学习vue3",
done: false
},
],
user: {
name: "ml",
age: 18,
scrore: 10
}
}
},
methods: {
fn(item, index) {
this.todolist[index].done = !this.todolist[index].done;
}
},
});
</script>
</body>
</html>
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
条件渲染 v-show / v-if / v-else-if / v-else
* v-show 用于显示或隐藏视图节点, 背后使用的 display: 原本的显示模式 / display:none 来实现的
* v-if / v-else-if / v-else 用于显示或隐藏视图节点, 背后是真正地移除或插入视图节点
* v-if 因为是节点插入或移除, 比较耗费性能; v-show只是通过样式来实现显示与隐藏, 性能开销更小.
* v-if, 不建议和 v-for 一起使用; 如果一定要在同一个节点上使用v-if和v-for, v-for优先级更好
2
3
4
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h2>{{name}}</h2>
<!-- v-show是通过display:none和display:block控制元素显示与隐藏? -->
<!-- 答:错 它是通过控制dislay:none和display:原本的显示模式-->
<!-- <h2 v-show="isShow">我是一个孤独的h2</h2> -->
<!-- <span v-show="isShow">我是一个孤独的span</span> -->
<!-- v-if 控制元素的创建或销毁-->
<!-- <h2 v-if="isShow">我是一个孤独的h2</h2> -->
<!-- 在vue2中, v-if不要和v-for连用,为什么? -->
<!-- v-for的优先级更高 先创建一片DOM元素 如果说v-if的条件是false,还会重新销毁 -->
<!-- 在v3中,v-if的优先级更高,可以连用 -->
<h1 v-if="row === 1">我是第一行</h1>
<h2 v-else-if="row === 2">我是第二行</h2>
<h3 v-else-if="row === 3">我是第三行</h3>
<h4 v-else>我是第四行</h4>
</div>
<script>
let vm = new Vue({
el: "#app",
data() {
return {
name: "码路",
isShow: false,
row: 1
}
}
});
</script>
</body>
</html>
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
其它指令
* v-pre 用于调试,可以阻止vue编译器对指令的编译,生产工作中很少使用。
* v-slot 在讲组件化时再讲,用于指定具名插槽。
2
自定义指令
* 在vue开发中,除了可以使用这些内置指令外,我们还可以使用Vue.directive() 或 directives选项来自定义指令。
面试题
* 常用的vue指令有哪些?你怎么理解指令?
* v-if 和 v-show 有什么区别?
* 文本插值有“{{}}一闪而过”问题,怎么处理?
* v-for 可以循环哪些数据类型?v-for列表渲染时为什么加key?
* v-model 有哪些修饰符?
* vue 中怎么阻止冒泡?怎么阻止默认事件?怎么监听键盘enter键?
* 工作中你封装过自定义指令吗?举一些例子
2
3
4
5
6
7
# 三,计算属性
计算属性(非常有用):computed选项
* 语法:在computed选项中,定义计算属性方法,在方法体使用声明式变量进行若干计算。
* 使用:在视图模板中把计算属性直接当作变量直接使用,在vue逻辑代码使用this访问计算属性,默认只有get功能。
* 作用1:当指令的表达式比较复杂时,我们建议使用计算属性来优化,提升视图模板中代码的可阅读性、可维护性。
* 作用2:用于缓存一个复杂的运算,避免组件更新时产生没有必要的性能损耗。计算属性本质上是一个函数,Vue会分析函数体中使用到了哪些声明式变量,有且仅有这些声明式变量发生变化时,计算属性才会重新执行。
2
3
4
如何让计算属性同时支持get/set功能呢?
* 计算属性默认是一个函数,表示get功能。为了支持set,要把计算属性写对象结构 {get, set}。
* 计算属性能不能绑定的v-model上?可以。(v-model具有set功能,计算属性可以拆成get/set写法)
* 在Vue中,计算属性能计算哪些性质的变量?除了可以计算data、vuex数据、$route等,还可以计算一切__ob__的数据。
2
3
参考代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h2>{{name}}</h2>
<h2>{{`总价$:${(num*price).toFixed(2)}`}}</h2>
<h2 v-text="`总价$:${(num*price).toFixed(2)}`"></h2>
<h2 v-text="total"></h2>
<h2 v-text="total2"></h2>
<button @click="btnClick">修改</button>
<hr>
<h2>{{total3}}</h2>
<hr>
firstName: <input type="text" v-model="firstname">
lastname: <input type="text" v-model="lastname">
<!-- 全名: <input type="text" disabled :value="firstname+lastname"> -->
<!-- 全名: <input type="text" :value="fullname" @input="fn"> -->
<!-- v-model后面可以跟一个计算属性 -->
<!-- 为什么v-model后面可以跟一个计算属性? -->
<!-- 答: 原因是计算属性有setter -->
全名: <input type="text" v-model.lazy="fullname">
<!-- 计算属性中不能写异步代码 -->
</div>
<script>
// 通过vm也可以得到计算属性
let vm = new Vue({
el: "#app",
data() {
return {
name: "码路",
num: 10,
price: 100,
msg: "123",
firstname: "",
lastname: ""
}
},
computed: {
// 一般情况下,计算属性都是和data相关的
// 表示getter
total() {
let t = (this.num * this.price).toFixed(2);
return '总价:$' + t
},
// 计算属性也可以和data没有任何关系
total2() {
return "666"
},
total3: {
get() {
console.log("get...");
return this.msg;
},
set(val) {
console.log(val);
this.msg = val;
}
},
// 只要firstname和lastname变了,计算属性会重新计算
fullname: {
get() {
return this.firstname + "." + this.lastname
},
set(val) {
// console.log(val);
let arr = val.split(".");
this.firstname = arr[0];
this.lastname = arr[1]
}
}
},
methods: {
btnClick() {
// this.total = 110
this.total3 = 110;
},
fn(e) {
// console.log(e.target.value);
let arr = e.target.value.split(".")
// console.log(arr);
this.firstname = arr[0]
this.lastname = arr[1]
}
},
});
</script>
</body>
</html>
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
98
99
100
101
102
面试题
* 计算属性有什么作用?(两大作用)
* 计算属性能不能绑定在v-model上?(可以)
* 怎么理解计算属性的缓存功能?(有且仅有被关联的声明式变量变化时,计算属性才会重新计算)
2
3
# 四,侦听器
侦听器(监听器) watch选项
* 作用:用于监听一个变量的变化,然后去做另一件事儿。
* 特点:当侦听器监听引用数据类型时,默认只能监听引用数据类型的第一层。为什么要这样?监听深度越深,Vue在背后要做的事儿越多,这是一种性能损耗,所以一般不要对一个引用类型的变量进行深度监听。
* 如何监听引用数据类型的每一层(深度监听)?侦听器语法这样写 info: { deep: true, handler() {} }为了避免deep:true深度监听导致性能损耗,我们推荐这种写 'info.child.age' () {}
* 在Vue中,侦听器能够监听哪些性质的变量变化呢?能够监听data、计算属性、vuex数据、$route等,凡是那些带有__ob__变量都能被监听到。
2
3
4
参考代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<!--
问:侦听器可以侦听什么?
答: data 计算属性 vuex数据 $route
只要这个数据有__ob__都可以被侦听器
作用: 监听到变化,然后去一些事件
特点: 默认情况下,只能侦听第一层,想深度侦听,deep:true
-->
<div id="app">
<h2>{{name}}</h2>
<h2>{{num}}</h2>
<button @click="num++">+1</button>
<hr>
<h2>{{info.child.age}}</h2>
<button @click="info.child.age++">+1</button>
</div>
<script>
let vm = new Vue({
el: "#app",
data() {
return {
name: "码路",
num: 100,
info: {
name: "ml",
age: 18,
child: {
name: "wc",
age: 20
}
}
}
},
// 在watch中可以写异步代码
watch: {
num(newNum, oldNum) {
console.log(newNum, oldNum);
},
// info(newInfo, oldInfo) {
// console.log(newInfo, oldInfo);
// }
// 一般情况下我们不会深度侦听,侦听的层级越深,vue背后做的事情越多,也是一种性能消耗
// info: {
// deep: true,
// handler(newInfo, oldInfo) {
// console.log(newInfo.child.age, oldInfo.child.age);
// }
// }
"info.child.age"(newAge, oldAge) {
console.log(newAge, oldAge);
}
}
});
</script>
</body>
</html>
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h2>{{obj.a}}</h2>
<button @click="obj.a++">+1</button>
</div>
<script>
let vm = new Vue({
el: "#app",
data() {
return {
obj: {
a: 1,
},
a: 111,
};
},
computed: {
obj1() {
return JSON.parse(JSON.stringify(this.obj));
},
},
// 深度侦听时,如何获取旧值
watch: {
obj1: {
deep: true,
handler: function (newVal, oldVal) {
console.log(newVal, oldVal);
},
},
}
});
</script>
</body>
</html>
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
面试题
* 什么是侦听器?有什么用?
* 侦听器能不能监听数组/对象的变化?(可以,但默认不监听深层)deep:true
* 侦听器为什么默认不支持深度监听?(性能优化)
* 如何监听一个对象中的某个属性变化?(deep:true, 推荐 'obj.key'这种监听写法)
* 侦听器能监听哪些变量变化?(data、computed、vuex、$route等)
2
3
4
5
# 五,响应式原理
几个重要概念
* 响应式reactive(当你get/set一个变量时,你可以“捕获”这个操作行为;就好比叫你一声,你会答应一样)
* 劫持(使用Object.defineProperty对data选项进行遍历并添加getter/setter钩子)
* touch(当指令第一次与声明式变量绑定时,第一次触发声明式变量的get钩子)
* 依赖收集(当第一次touch时,把当前声明式变量的更新方法watcher添加到dep依赖数组中)
* Watcher(与声明式变量对应的dom更新方法)
* re-render(当声明式变量被set时,Vue通知Watcher更新DOM,即重新渲染)
2
3
4
5
6
面试题:说一下Vue的响应式原理?
* 当vue组件被创建时,在生命周期的第一阶段,Vue使用Object.defineProperty()对data选项进行遍历劫持并添加get/set钩子;在生命周期第二阶段,指令第一次与声明式变量touch时,发生依赖收集,再调用当前组件的watcher第一次更新DOM,DOM视图就显示出来了。当声明式变量发生变化时,vue再次通知Watcher更新视图,这就是响应式(原理)
参考代码:
<!-- <script>
// obj不具备响应式
let obj = {
a: 1,
b: 2,
c: 3
};
console.log(obj.a);
</script> -->
<!-- ------------------------- -->
<!-- <script>
// obj具备响应式
let obj = {};
let a = 1;
let b = 2;
Object.defineProperty(obj, 'a', {
get() {
console.log("有人访问a了~");
return a;
},
set(val) {
console.log("有人修改a了~");
a = val;
}
})
Object.defineProperty(obj, 'b', {
get() {
console.log("有人访问b了~");
return b;
},
set(val) {
console.log("有人修改b了~");
b = val;
}
})
</script> -->
<!-- ------------------------- -->
<!-- <script>
let data = {
name: "ml",
age: 18
}
let app = {}; // vue实例
Object.keys(data).forEach(k => {
Object.defineProperty(app, k, {
get() {
console.log(`getter ${k}`);
return data[k]
},
set(val) {
console.log(`setter ${k}`);
data[k] = val;
}
})
})
</script> -->
<!-- ------------------------- -->
<!--
touch: 当指令第1次与声明式变量绑定时,第一次就会触发getter
依赖收集: 当第1次touch时, 会把当前声明式变量的的更新方法(watcher) 添加到dep依赖数组中
watcher: 与声明式变量相应的DOM更新方法
re-render: 当声明式变量变化时,走setter,通过Watcher更新DOM说白了,就是重新渲染
问:说一下,vue的响应式原理?
答:当Vue组件被创建时,在生命周期的第1个阶段,Vue使用Object.defineProperty()
对data中的数据进行深度递归遍历劫持,添加get和set钩子。在生命周期的第二个阶段,
指令第一次与声明式变量touch时,开始进行依赖收集,就是收集Watcher,Watcher就
是一个更新函数。然后进行第次的页面初始化(首次渲染),当数据发生变化时,Vue再次
通过Wathcer更新视图,这就是Vue的响应式原理。
-->
shuru: <input id="ipt" type="text">
shuchu: <h2 id="h2"></h2>
<hr>
<h1 id="h1"></h1>
<button id="btn">+1</button>
<script>
let data = {
name: "ml",
num: 1
}
let app = {}; // vue实例
// 给app上添加响应式数据
// vue实例化生命周期的第1阶段(劫持,添加get和set)
Object.keys(data).forEach(k => {
Object.defineProperty(app, k, {
get() {
console.log(`getter ${k}`);
return data[k]
},
set(val) {
console.log(`setter ${k}`);
data[k] = val;
Watcher(k); // 派发更新,通过界面更新
}
})
})
// 专门用来收集依赖
let dep = {
name: [], // 这数组中放watcher
num: []
}
// 第二阶段 挂载阶段
function init() {
// document.getElementById("ipt").value = app.name; // getter
// document.getElementById("ipt").addEventListener('input', e => {
// app.name = e.target.value; // setter
// })
// ()=>{} 更新界面的函数 watcher
dep['name'].push(() => {
document.getElementById("ipt").value = app.name;
})
document.getElementById("ipt").addEventListener('input', e => {
app.name = e.target.value; // setter
})
// v-text="name"
dep['name'].push(() => {
document.getElementById("h2").innerText = app.name;
})
// v-text="num"
dep['num'].push(() => {
document.getElementById("h1").innerText = app.num;
})
// v-on:click=
document.getElementById("btn").addEventListener("click",()=>{
app.num++; // 改变了num 走setter
})
// 第一次更新DOM
Object.keys(dep).forEach(k => Watcher(k))
}
function Watcher(k) {
dep[k].forEach(fn => fn())
}
init();
</script>
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# 六,组件和v-model
怎么理解组件化?
* 组件是HTML的扩展,使用粒度较小的HTML元素封装成粒度更大的标签(Vue组件)。
* 自定义组件技术,是MVVM框架中最重要的技术之一,可以实现快速开发、代码复用、提升可维护性。
2
如何自定义组件
* 一个组件,你可以理解成是由一组选项构成的。在封装自定义组件时,你可以使用大多数的Vue选项属性,比如data、template、methods等。
* 组件封装后,必须要注册(局部注册,或,全局注册)才能在Vue的地盘中使用。注册自定义组件时,组件名称名称必须由多个单词用中划线连接。
* 对一个组件来讲,最重要的选项是 template选项,用于指定组件的视图结构,在视图结构中你可以使用任意指令。
* 对一个组件来讲,你可以使用 props选项,用于接收父组件传递过来的自定义属性,在组件内部用this访问它们。
* 对一个组件来讲,你可以使用 this.$emit('自定义事件', '数据')触发父组件给的自定义事件,并回传数据给父组件。
* 注意:关于语法细节问题,老师讲过,或者你在官方文档见过,你可以用。不要随意“创造”语法。
* 当子组件定义声明式变量,也使用的是data选项,需要注意的是这里的 data(){return { num: 1 }},因为组件会被复用,为了保证data的独立性。只有 new Vue({})根组件中的data: {}。
2
3
4
5
6
7
如何进行组件注册?
* 全局注册:Vue.component('xx-yy', '原材料选项') 全局注册的组件,一次注册,在任何其它组件中都可以使用。
* 局部注册:components: { 'xx-yy': '原材料选项' } 局部注册的组件,只能在当前组件作用域中使用。
* 需要注意的是,注册组件时,组件名称名称必须由多个单词用中划线连接。
2
3
关于组件间关系与通信
* 约定:在MVVM框架,当我们谈论“组件”这个概念时,通常指是自定义组件。当在A组件的视图结构中使用到了B组件,这就形成了组件关系(父子组件),那么A就是B组件的父组件,B就是A的子组件。当组件足够多时,组件间关系就会形成“组件树”。
* 通信:在Vue中,所谓“通信”就是组件之间的数据交互。父组件向子组件通信,使用自定义属性,在子组件使用props接收;子组件向父组件通信,使用自定义事件,在子组件中使用 this.$emit('自定义事件', '数据') 回传数据。
2
组件化的三大技术
* 自定义属性 <ml-score :num='num' count='1'></ml-score>
* 自定义事件 <ml-score @change='' @click=''></ml-score>
* 自定义插槽 <ml-score><div #default></div> </ml-score>
* 组件封装:使用自定义属性、自定义事件、自定义插槽这三大技术实现组件化。
2
3
4
面试题
* 什么是组件化?你怎么理解组件化?
* 你工作中有没有封装比较好的组件?
* 父子组件之间如何通信?(父传子、子传父)
* 什么是插槽?什么是具名插槽?
2
3
4
参考代码:
<!--
组件:UI的复用,也可以有逻辑
hook:逻辑的复用。
组件的三个核心:
1)自定义属性 <ml-table :a="1" id="box"></ml-table>
2)自定义事件 <ml-table @click="fn" @ml="gn"></ml-table>
3)自定义插槽 <ml-table><div>我是一个div</div></ml-table>
组件通信:
1)组件与组件之间进行数据交互
2)父子通信 兄弟通信 祖先与后代
3)props
自定义事件
ref
事件总线
集中状态管理
v-model
provide inject
$root
$attrs
$linsers
$parent
$children
.sync
slot
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.rate {
display: flex;
line-height: 30px;
align-items: center;
font-size: 14px;
}
.rate>span {
display: inline-block;
width: 16px;
height: 16px;
background: url("./imgs/score.png") center center / 16px 16px;
}
.rate>span.on {
background: url("./imgs/score-on.png") center center / 16px 16px;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h2>{{name}}</h2>
<ml-rate :value="num" @input="num=$event">配送速度:</ml-rate>
<ml-rate :value="num2" @input="num2=$event">服务态度:</ml-rate>
</div>
<script type="x-template" id="rate">
<div class="rate">
<slot name="default"></slot>
<span v-for="i in 5" :class="{on:value>=i}" @click="$emit('input',i)"></span>
</div>
</script>
<script>
let rate = {
template: "#rate",
props: {
value: { type: Number, default: 0 }
}
}
let vm = new Vue({
el: "#app",
data() {
return {
name: "码路",
num: 1,
num2: 1
}
},
components: {
'ml-rate': rate
}
});
</script>
</body>
</html>
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
98
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.rate {
display: flex;
line-height: 30px;
align-items: center;
font-size: 14px;
}
.rate>span {
display: inline-block;
width: 16px;
height: 16px;
background: url("./imgs/score.png") center center / 16px 16px;
}
.rate>span.on {
background: url("./imgs/score-on.png") center center / 16px 16px;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h2>{{name}}</h2>
<!-- <ml-rate :value="num" @xxx="gn"></ml-rate> -->
<!-- <ml-rate :value="num" @xxx="num = $event"></ml-rate> -->
<ml-rate :value="num" @input="num = $event">配送速度:</ml-rate>
<ml-rate :value="num1" @input="num1 = $event">服务质量:</ml-rate>
<ml-rate :value="num2" @input="num2 = $event">用户体验:</ml-rate>
<!-- 组件可以复用:UI复用 -->
<!-- 为什么组件中的data必须是一个函数? -->
<!-- 答:组件是可以复用的,如果是一个对象的,多个组件实例的data是指向同一个堆空间,数据相互影响? -->
<!-- vm中的data可以是一个对象? -->
<!-- 答:它不需要复用 -->
</div>
<!-- <template id="rate"></template> -->
<script id="rate" type="x-template">
<div class='rate'>
<slot></slot>
<!-- <span v-for='i in 5' :class='{on:value>=i}' @click="fn(i)"></span> -->
<!-- <span v-for='i in 5' :class='{on:value>=i}' @click="$emit('xxx',i)"></span> -->
<span v-for='i in 5' :class='{on:value>=i}' @click="$emit('input',i)"></span>
</div>
</script>
<script>
let rate = {
template: "#rate",
props: {
value: { type: Number, default: 0 }
},
methods: {
fn(i) {
// console.log(i);
// this.$emit("xxx", i)
}
},
}
let vm = new Vue({
el: "#app",
components: {
"ml-rate": rate
},
data() {
return {
name: "码路",
num: 3,
num1: 3,
num2: 3,
}
},
methods: {
gn(i) {
// console.log(i);
this.num = i;
}
}
});
</script>
</body>
</html>
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.rate {
display: flex;
line-height: 30px;
align-items: center;
font-size: 14px;
}
.rate>span {
display: inline-block;
width: 16px;
height: 16px;
background: url("./imgs/score.png") center center / 16px 16px;
}
.rate>span.on {
background: url("./imgs/score-on.png") center center / 16px 16px;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h2>{{name}}</h2>
<!-- v2中v-model等价与: :value + @input -->
<!-- <ml-rate :value="num" @input="num = $event">配送速度:</ml-rate>
<ml-rate :value="num1" @input="num1 = $event">服务质量:</ml-rate>
<ml-rate :value="num2" @input="num2 = $event">用户体验:</ml-rate> -->
<!-- number叫v-model的修饰符 -->
<!-- <ml-rate v-model.number="num">配送速度:</ml-rate>
<ml-rate v-model.number="num1">服务质量:</ml-rate>
<ml-rate v-model.number="num2">用户体验:</ml-rate> -->
<!-- v-model写在h5标签上 -->
<!-- <input type="text" v-model="name">
<input type="text" :value="name" @input="name=$event.target.value"> -->
<!-- radio: 收集radio时,也是需要手动指定value,收集的也是value -->
<!-- <input type="radio" :value="0" :checked="gender==='0'" @change="gender=$event.target.value" /> 男
<input type="radio" :value="1" :checked="gender==='1'" @change="gender=$event.target.value" /> 女 -->
<!-- v-model: :checked + @change -->
<!-- <input type="radio" value="0" v-model="gender" /> 男
<input type="radio" value="1" v-model="gender" /> 女 -->
<!-- checkbox: -->
<!-- <input type="checkbox" value="001" v-model="hobbiey" /> 001
<input type="checkbox" value="002" v-model="hobbiey" /> 002
<input type="checkbox" value="003" v-model="hobbiey" /> 003 -->
<!-- select 下拉菜单 -->
<!-- v-model写在select上等价与xxx -->
<!-- <select v-model="level">
<option value="001">AAA</option>
<option value="002">BBB</option>
<option value="003">CCC</option>
</select> -->
<select :selected="level" @change="level=$event.target.value">
<option value="001">AAA</option>
<option value="002">BBB</option>
<option value="003">CCC</option>
</select>
</div>
<script id="rate" type="x-template">
<div class='rate'>
<slot></slot>
<span v-for='i in 5' :class='{on:value>=i}' @click="$emit('input',i)"></span>
</div>
</script>
<script>
let rate = {
template: "#rate",
props: {
value: { type: Number, default: 0 }
},
}
let vm = new Vue({
el: "#app",
components: {
"ml-rate": rate
},
data() {
return {
name: "码路",
num: 3,
num1: 3,
num2: 3,
gender: "0",
hobbiey: ['001'],
level: "001"
}
},
});
</script>
</body>
</html>
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
98
99
100
101
102
103
104
105
106
107
108
109
110
封装一个弹窗组件,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.layer {
background-color: rgba(0, 0, 0, .6);
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.modal {
position: absolute;
top: 100px;
left: 50%;
width: 520px;
margin-left: -260px;
border-radius: 3px;
background-color: #fff;
}
header {
line-height: 50px;
box-sizing: border-box;
padding: 0 20px;
font-size: 14px;
overflow: hidden;
border-bottom: 1px solid #eee;
}
header>.title {
float: left;
font-size: 18px;
}
header>.close {
float: right;
cursor: pointer;
}
main {
box-sizing: border-box;
padding: 20px;
font-size: 14px;
}
footer {
line-height: 50px;
border-top: 1px solid #eee;
height: 50px;
}
footer>span {
float: right;
height: 30px;
line-height: 28px;
margin-right: 20px;
cursor: pointer;
display: inline-block;
padding: 0 20px;
margin-top: 10px;
border: 1px solid #ccc;
border-radius: 2px;
}
footer>span.primary {
border-color: blue;
background-color: blue;
color: white;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<button @click="show=true">弹出model</button>
<ml-modal title="编辑商品" @submit="onSubmit" @close="onClose" :show="show">
<template>
<div>
<span>商品名称:</span>
<input type="text" v-model="name">
</div>
</template>
</ml-modal>
</div>
<script id="modal" type="x-template">
<div class="layer" v-if='show'>
<div class="modal">
<header>
<span class='title' v-text='title'></span>
<span class='close' @click="$emit('close')">X</span>
</header>
<main>
<slot>
<div>我是主要内容区域</div>
</slot>
</main>
<footer>
<span class="primary" @click="$emit('submit')">确定</span>
<span @click="$emit('close')">取消</span>
</footer>
</div>
</div>
</script>
<script>
Vue.component("ml-modal", {
template: "#modal",
props: {
title: {
type: String, default: "默认标题",
},
show: {
type: Boolean, default: false,
}
},
})
let vm = new Vue({
el: "#app",
data() {
return {
name: "码路",
show: false
}
},
methods: {
onClose() {
this.show = false;
},
onSubmit() {
// 获取新的数据,发送ajax请求
console.log(this.name);
this.name = "";
this.show = false;
}
},
});
</script>
</body>
</html>
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# 七,第1次作业
* 常用的vue指令有哪些?你怎么理解指令?
* v-if 和 v-show 有什么区别?
* 文本插值有“{{}}一闪而过”问题,怎么处理?
* v-for 可以循环哪些数据类型?v-for列表渲染时为什么加key?
* v-model 有哪些修饰符?
* vue 中怎么阻止冒泡?怎么阻止默认事件?怎么监听键盘enter键?
* 工作中你封装过自定义指令吗?举一些例子
* 计算属性有什么作用?(两大作用)
* 计算属性能不能绑定在v-model上?(可以)
* 怎么理解计算属性的缓存功能?(有且仅有被关联的声明式变量变化时,计算属性才会重新计算)
* 说一下Vue的响应式原理?
* 什么是组件化?你怎么理解组件化?
* 你工作中有没有封装比较好的组件?(选做)
* 父子组件之间如何通信?(父传子、子传父)
* 什么是插槽?什么是具名插槽?
2
3
4
5
6
7
8
9
10
11
12
13
14
15
作业答案
常用的vue指令有哪些?你怎么理解指令?
从分类的角度去解决:
1)文本绑定类
2)属性绑定类
3)事件绑定类
4)表单绑定类
5)列表渲染类
6)条件渲染类
7)其他指令和自定义指令
指令本质上是对dom的操作,vue封装这些指令就是为了我们更为合理符合规范的操作dom,避免DOM滥操作。我们也可以自定义指令,自定义指令也是对DOM操作的封装,我之前工作中封装过一些自定义指令,如:xxxx
2
3
4
5
6
7
8
9
10
11
v-if 和 v-show 有什么区别?
v-if是创建和销毁,比较耗费性能;v-show类似于display: 原本的显示模式 和display:none。
v-show不支持template。
如果看过源码,可以从源码角度去分析。v-if最终编译成了什么,v-show最终编译成了什么...
参考另一个答案:
手段:v-if是通过控制dom节点的存在与否来控制元素的显隐;v-show是通过设置DOM元素的display样式,block为显示,none为隐藏;
编译过程:v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show只是简单的基于css切换;
编译条件:v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译(编译被缓存?编译被缓存后,然后再切换的时候进行局部卸载); v-show是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且DOM元素保留;
性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗;
2
3
4
5
6
7
8
9
10
11
12
13
14
计算属性有什么作用?(两大作用)
可以研究一下,计算属性的实现原理。找资料,找视频,回答:我之前看过vue的源码,它的计算属性是xxxx实现的。
* 作用1:当指令的表达式比较复杂时,我们建议使用计算属性来优化,提升视图模板中代码的可阅读性、可维护性。
* 作用2:用于缓存一个复杂的运算,避免组件更新时产生没有必要的性能损耗。计算属性本质上是一个函数,Vue会分析函数体中使用到了哪些声明式变量,有且仅有这些声明式变量发生变化时,计算属性才会重新执行。
2
3
4
5
6
计算属性能不能绑定在v-model上?(可以)
计算属性可以有setter,所以可以使用在v-model上面。
2
3
说一下Vue的响应式原理?
当vue组件被创建时,在生命周期的第一阶段,Vue使用Object.defineProperty()对data选项进行遍历劫持并添加get/set钩子;在生命周期第二阶段,指令第一次与声明式变量touch时,发生依赖收集,再调用当前组件的watcher第一次更新DOM,DOM视图就显示出来了。当声明式变量发生变化时,vue再次通知Watcher更新视图,这就是响应式(原理)
2
3
什么是组件化?你怎么理解组件化?
现在主流的开发框,都是基于组件化的,组件的作用就是为了实现UI和逻辑的复用,在vue组件中,提供了很多的选项,如data,computed,watch,method,components,filter... 可以方便我们实现业务逻辑,组件化的好处就是可以复用UI和逻辑,在业务开发中,一般都会合理的划分组件,在vue中组件化的核心就是自定义属性,自定义事件,自定义插槽。
组件与组件通信常见的有8种方案,比如:... 展开讲 如果看过源码,可以从源码角度分析组件的实现过程。
2
3
4
你工作中有没有封装比较好的组件?
面试之前,需要封装几个组件,真实开发中,不会自己封装,都是用第三方封装好的,或公司自己的组件库。
2
3
# 八,生命周期
生命周期(组件从“生”到“死”的全过程)
* 创建阶段:beforeCreate、created
* 挂载阶段:beforeMount、mounted
* 更新阶段:beforeUpdate、updated
* 销毁阶段:beforeDestroy、destroyed
2
3
4
与动态组件有关的两个特殊的钩
* activated(激活)、deactivated(休眠)
* 与组件异常捕获有关的一个钩子:errorCaptured
2
什么MVVM框架?
* MVVM流程 : M数据层 -> VM虚拟DOM层 -> V视图层
* 网页本质 = M数据层 + V视图结构
* M+V是怎么组装的?( MVC MVP MVVM)
* M+V在哪儿组装?(前后端分离、前后端不分离、SSR服务端渲染)
* 进一步理解(阮一峰博客):https://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html
2
3
4
5
面试题
* Vue常用的生命周期有哪些?
* 在创建/挂载/更新/销毁阶段,Vue在背后分别做了些什么事儿?
* 响应式原理,发生在Vue哪些生命周期阶段?
* 虚拟DOM,在哪些阶段生成的?
* 哪些生命周期钩子可以执行多次?哪些执行一次?
* 什么虚拟DOM?(是一个很大的JSON数据,用于描述视图模板的,保存在内存中)
* 虚拟DOM存在的价值点在哪里?(更新,把DOM更新粒度降到最低,规避人为DOM滥操作)
* 什么diff运算?(在更新阶段,patch对新旧虚拟DOM进行对比,找出脏节点,提交更新)
* 谈一谈你对 MVVM、MVC、MVP的理解?
* 还有很多与生命周期有关的面试题
2
3
4
5
6
7
8
9
10
参考代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<!--
1)说一下,vue的生命周期?(8个 + 2个 + 1个)
2)在创建 挂载 更新 销毁阶段,vue背后做了什么?
3)响应式原理,发生在哪个阶段?
4)虚拟DOM在哪个阶段生成?
5)vue的生命周期哪些可以调用多次,哪些只能调用一次?
6)什么是虚拟ODM? js对象
7)虚拟DOM有什么价值? 真实DOM是一个庞大的对象,直接操作真实DOM,性能低,虚拟DOM比较小,
新旧虚拟DOM对比可以更快地找到差异,更新差异,并且可以避免人为操作DOM泛滥。配置指令,就
可以让我们更加规范的操作DOM。
8)什么是diff算法 ? 发生更新阶段,两个虚拟DOM树进行对象,找到差异,提交更新。
9)总之,针对生命周期,有很多面试...
-->
<div id="app">
<h2>{{name}}</h2>
<h1>{{num}}</h1>
<button @click="num++">+1</button>
</div>
<script>
let vm = new Vue({
el: "#app",
// 1)声明methods中的方法和声明生命周期钩子函数
beforeCreate() {
// 基本上没有
console.log("beforeCreate.....");
},
// 2)注入一些数据,初始化响应式系统
created() {
console.log("created.....");
// 调接口 获取路由参数
},
// 3)通过el $meount template找模块
// 会把模块变成render函数 调用render函数创建虚拟DOM
// 虚拟DOM转化成真实DOM,进行挂载
beforeMount() {
console.log("beforeMount.....");
},
// 4)开始挂载
// 调接口 开定时器 DOM操作 建立websocket连接 实例化echarts实例
mounted() {
console.log("mounted.....");
},
// 数据变化 beforeUpdate
beforeUpdate() {
console.log("beforeUpdate.....");
// 基本上用不到
},
// 要生成新的虚拟DOM,新的虚拟DOM和老的虚拟DOM进行对比
// 会执行patch运算,diff算法,找到两个虚拟DOM的最小差异,找到后,进行异步更新
// key: 可以让我们尽快找到差异
updated() {
console.log("updated.....");
// 不常用,不代表没有
// 有点类似于watch侦听器 还有点类似$nextTick();
// 不要是这里更新数据,会导致死循环
},
// 什么时候会调用beforeDestroy?
// 1)手动调用$destory()
// 2)路由切换 keep-alive 动态路由
beforeDestroy() {
console.log("beforeDestroy.....");
// 清空定时器 解除事件绑定 清除缓存...
},
// 移除当前组件的watcher,DOM就无法再更新
// 移除所有子组件
// 移除事件监听器
destroyed() {
console.log("destroyed.....");
},
data() {
return {
name: "码路",
num: 0
}
}
});
</script>
</body>
</html>
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
# 九,动态组件
keep-alive 动态组件
* 作用:被<keep-alive>所包裹的组件不会“销毁”,你可以理解成是对组件的一种缓存。
* 被动态组件包裹过的组件多了两个生命周期钩子:activated(激活)、deactivated(休眠)
* 注意这两组钩子的区别和使用场景:activated(执行多次) - mounted(执行一次) 、 deactivated(执行多次) - beforeDestroy(执行一次)
* <keep-alive :include='["ml-home"]' /> 包含在include数组中的组件不会“死”
* <keep-alive :exclude='["ml-home"]' /> 不包含在exclude数组中的组件不会“死”
*<component -is='组件名'>
* 作用:有种v-if的感觉,根据指定条件渲染目标组件,它的is属性等于哪个组件,就显示哪个组件。
* 场景:它经常配合keep-alive一起使用。
2
3
4
5
6
7
8
9
10
面试题
* 在Vue中实现条件渲染渲染有哪些办法?v-if / v-show / component is
* 什么是动态组件?keep-alive include exclude 还有两个新的生命周期钩子
* mounted和activated有什么区别?deactivated和beforeDestroy有什么区别?(执行次数、使用场景)
* Vue有哪些内置组件?(插槽、动态组件、过渡动画、component)
* 哪些场景下你会用到动态组件?
2
3
4
5
参考代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.tabbar {
height: 60px;
line-height: 60px;
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
}
.tabbar>span {
flex: 1;
border: 1px solid #ccc;
text-align: center;
cursor: pointer;
}
.tabbar>span.on {
color: red;
}
</style>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<!-- <ml-home></ml-home>
<ml-about></ml-about>
<ml-user></ml-user> -->
<!-- <component :is="comp"></component> -->
<!-- keep-alive可以缓存组件 -->
<!-- <keep-alive :include="['ml-home']"> -->
<!-- <keep-alive :include="['ml-home','ml-about']"> -->
<keep-alive :exclude="['ml-home']">
<component :is="comp"></component>
</keep-alive>
<!-- <ml-tabbar @input="comp = $event" :value="comp"></ml-tabbar> -->
<ml-tabbar v-model="comp"></ml-tabbar>
</div>
<script>
let Home = {
template: `
<div>
<h1>首页</h1>
<input type="text" v-model="a"></input>
</div>
`,
data() {
return {
a: 1
}
},
mounted() {
console.log("home挂载完毕了");
},
beforeDestroy() {
console.log("home销毁");
},
activated() {
console.log("home组件激活了");
},
deactivated() {
console.log("home组睡眠了");
},
}
let About = {
template: `
<div>
<h1>关于</h1>
<input type="text" v-model="a"></input>
</div>
`,
data() {
return {
a: 2
}
},
mounted() {
console.log("about挂载完毕了");
},
beforeDestroy() {
console.log("about销毁");
},
activated() {
console.log("about组件激活了");
},
deactivated() {
console.log("about组睡眠了");
},
}
let User = {
template: `
<div>
<h1>我的</h1>
<input type="text" v-model="a"></input>
</div>
`,
data() {
return {
a: 3
}
},
mounted() {
console.log("user挂载完毕了");
},
beforeDestroy() {
console.log("user销毁");
},
activated() {
console.log("user组件激活了");
},
deactivated() {
console.log("user组睡眠了");
},
}
Vue.component("ml-tabbar", {
template: `
<div class="tabbar">
<span v-for="item in list" :class='{on:item.tab == value}' @click='$emit("input",item.tab)'>{{item.label}}</span>
</div>
`,
props: {
value: { type: String, default: "" }
},
data() {
return {
list: [
{ id: 1, tab: 'ml-home', label: "首页" },
{ id: 2, tab: 'ml-about', label: "关于" },
{ id: 3, tab: 'ml-user', label: "我的" },
]
}
}
})
let vm = new Vue({
components: {
"ml-home": Home,
"ml-about": About,
"ml-user": User,
},
el: "#app",
data() {
return {
name: "码路",
comp: "ml-home"
}
}
});
</script>
</body>
</html>
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# 十,过渡动画
过渡动画 <transition>
* 作用:是Vue内置的一种动画方式,可以很方便地为元素显示隐藏或组件切换添加动画。
* 工作中使用动画,本质是一种用户体验的优化。
* 用法:把过渡动画抽象成两个过程(Enter进入动画、Leave离开动画)
* 如何自定义进入动画?.ml-enter / .ml-enter-active / .ml-enter-to (动画名就叫做'ml')
* 如何自定义离开动画?.ml-leave / .ml-leave-active / .ml-leave-to (动画名就叫做'ml')
* 注意:Vue过渡动画的终点是不会保留的,也就是说当动画结束后元素的样式决定于class/style样式。咱们定义动画时,特别注意.ml-enter-to和.ml-leave这两个类名,最好能够配合UI样式去定义。
2
3
4
5
6
使用第三方css动画(animate.css)
* 官网:https://animate.style/
* 用法:安装或引入第三方css动画库,使用enter-active-class指定进入动画,使用enter-active-class指定离开动画。
2
对多个元素执行动画时
* 要给每个元素都加上唯一的key(key值不能重复),否则动画不生效。
* <transition mode> 使用mode指定多个元素显示与隐藏的先后顺序,通常mode='out-in'离开动画先执行,进入动画后执行。
*<transition-group>
* 作用:一般用于给列表添加分组动画,参见Vue官网。工作中一般很少自己写列表动画。
2
3
4
5
面试题
* Vue中怎么做动画?(内置组件<transition>)
* 说一下Vue动画怎么实现?(使用那6个钩子编写自定义动画、使用animate.css第三方动画)
* transition对多个元素执行动画时有什么要注意的?(加key、使用mode)
2
3
参考代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.ml-enter {
opacity: 0;
color: red;
font-size: 12px;
}
.ml-enter-active {
transition: all ease 2s;
}
.ml-enter-to {
opacity: 1;
color: blue;
font-size: 22px;
}
.ml-leave {
opacity: 1;
color: blue;
font-size: 22px;
}
.ml-leave-active {
transition: all ease 2s;
}
.ml-leave-to {
opacity: 0;
color: red;
font-size: 12px;
}
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/animate.css@4.1.1/animate.min.css" />
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<button @click="isShow=!isShow">显示或隐藏</button>
<!-- <transition name="ml">
<h2 v-if="isShow">{{name}}</h2>
</transition> -->
<!-- 在v2中,多个节点,需要加Key, v3中不需要 -->
<!-- <transition name="ml">
<h2 v-if="isShow" key="a">{{name}}</h2>
<h2 v-else key="b">666</h2>
</transition> -->
<transition enter-active-class="animate__animated animate__bounce"
leave-active-class="animate__animated animate__backOutDown" mode="out-in">
<h2 v-if="isShow" key="a">{{name}}</h2>
<h2 v-else key="b">666</h2>
</transition>
</div>
<script>
let vm = new Vue({
el: "#app",
data() {
return {
name: "码路",
isShow: false
}
}
});
</script>
</body>
</html>
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
# 十一,混入
混入
* 作用:我们都知道vue组件是由若干选项组成的,混入用于向组件中混入可复用的选项。
* 全局混入:使用 Vue.mixin({选项})进行全局混入,所有组件都拥有了这些被混入的选项。
* 局部混入:使用mixins:[{}, {}]选项进行局部混入,只有当前组件才有这些被混入的选项。
* 当一个组件同时有全局混入、局部混入、自有选项时,它们的优先级满足:自有选项 > 局部混入 > 全局混入。
* 结论:无论是全局混入、还是局部混入,都解决的是选项的复用问题。
* 注意:工作中尽量不要使用全局混入,偶尔会用到局部混入。
* 混入缺点:当混入用得多了,选项“来历不明”,代码不易维护。
2
3
4
5
6
7
面试题
* 什么是混入?有什么用?(Vue.mixin()全局混入 mixins:[]局部混入)(对选项进行复用)
* 你用混入解决过什么问题?(注入埋点方法、Echarts图表封装)
* 全局混入(data、methods、生命周期钩子)
2
3
参考代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h2>{{name}}</h2>
<h2>{{a}}</h2>
<h2>{{b}}</h2>
<button @click="fn">调用fn</button>
<hr>
<ml-home></ml-home>
</div>
<script>
// 混入导致数据方法来源不明
// 全局混入
Vue.mixin({
// 表示给所有的组件都混入data选项
data() {
return {
a: 1,
b: 2
}
},
methods: {
fn() {
console.log("fn...");
}
},
created() {
console.log("混入的created..");
},
})
let kk = {
data(){
return{
x:110,
y:120
}
},
methods:{
gg(){
console.log("gg...");
}
}
}
let gg = {
data() {
return {
m: 110,
n: 120
}
},
methods: {
gg() {
console.log("gg...");
}
}
}
// 全局注册组件
Vue.component("ml-home", {
template: `
<div>首页--{{a}}--{{b}} --{{x}} ---{{y}}</div>
`,
// 局部混入
mixins:[kk,gg],
data(){
return{
a:11,
b:22
}
},
created() {
console.log("home的created..");
},
})
let vm = new Vue({
el: "#app",
data() {
return {
name: "码路",
}
},
created() {
console.log("vm的created..");
},
});
</script>
</body>
</html>
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
98
99
100
101
102
103
# 十二,过滤器
过滤器
作用:用于数据处理的。
全局过滤器:使用 Vue.filter('名称', val=>{return newVal}) 定义,在任何组件中都可以直接使用。
局部过滤器:使用 filters: {} 定义,只能在当前组件中使用。
注意1:过滤器只能用在 {{}} 和 v-bind 中,不支持其它指令。
注意2:过滤器还可以链式调用,像这样 {{ num | f1 | f2 }},过滤器是有顺序的。
2
3
4
5
6
面试题:
什么过滤器?你在工作中用它解决什么问题?(数据统一处理)
使用过滤器有什么要注意的?(只能在{{}}或者v-bind中使用)
2
参考代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<!-- 过滤器只能用在两个地方 -->
<!-- 1){{}} v-bind中 只能在这两个方用 -->
<div id="app">
<h2>{{name}}</h2>
<h1>{{price | rmb}}</h1>
<h1>{{date | dataFormat}}</h1>
</div>
<script>
// 全局过滤器
Vue.filter("rmb", val => {
return "¥" + Number(val).toFixed(2)
})
let vm = new Vue({
el: "#app",
data() {
return {
name: "码路",
price: 188.1,
date: "20220908"
}
},
filters: {
"dataFormat"(val) {
return "2022-09-08"
},
"timeFormat"(){
}
}
});
</script>
</body>
</html>
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
# 十三,第2次作业
* 你工作中有没有封装比较好的组件?
* 在创建/挂载/更新/销毁阶段,Vue在背后分别做了些什么事儿?
* 响应式原理,发生在Vue哪些生命周期阶段?
* 虚拟DOM,在哪些阶段生成的?
* 什么虚拟DOM?(是一个很大的JSON数据,用于描述视图模板的,保存在内存中)
* 虚拟DOM存在的价值点在哪里?(更新,把DOM更新粒度降到最低,规避人为DOM滥操作)
* 哪些场景下你会用到动态组件?
* 混入有什么优势,有什么缺点?
2
3
4
5
6
7
8
9
# 十四,自定义指令
自定义指令:
什么指令?指令本质上就是DOM功能的一种抽象封装。
什么是自定义指令?当你在项目中有一些常用的DOM功能要复用时,建议封装成指令,这就是自定义指令。
全局指令:使用 Vue.directive('指令名', fn/{})定义全局指令,在任何组件中都能使用。
局部指令:使用 directives: {} 定义局部指令,只能在当前组件中使用。
2
3
4
5
面试题:
常用指令有哪些?你怎么理解指令?你工作是否自已封装过指令?
举一个你封装自定义指令的例子(权限指令、拖拽指令)
推荐8个常用指令(项目经验):
https://zhuanlan.zhihu.com/p/337659611
https://juejin.cn/post/6906028995133833230
复用真的好吗?(对新手不太友好,复用太多,改bug,一个出问题,其它地方也会出问题,容易造成项目瘫痪。)
2
3
4
5
6
7
参考代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h2 v-color="'red'">{{name}}</h2>
<!-- <div v-color="'blue'">我是一个孤独的DIV</div> -->
</div>
<script>
// 可以自定义全局指令,也可以自定义局部指令
let vm = new Vue({
el: "#app",
data() {
return {
name: "码路",
}
},
// 一个指令有两种写法
// 指令的底层都是操作DOM
directives: {
// "color": function (el, binding, vnode) {
// console.log("el:", el);
// console.log("binding:", binding);
// // vnode中有一个上下文,在封装自定义指令时,会使用到上下文
// console.log("vnode:", vnode);
// el.style.color = binding.value;
// },
// 在对象中放生命周期函数
"color": {
bind(el, binding, vnode) {
// console.log("el:", el);
// console.log("binding:", binding);
// console.log("vnode:", vnode);
el.style.color = binding.value;
},
update(el, binding, vnode) {
console.log("upadte....");
console.log("el:", el);
console.log("binding:", binding);
console.log("vnode:", vnode);
},
}
}
});
</script>
</body>
</html>
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h2 v-color="vc">码路</h2>
</div>
<script>
// 可以自定义全局指令,也可以自定义局部指令
let vm = new Vue({
el: "#app",
data() {
return {
vc: "red"
}
},
// 一个指令有两种写法
// 指令的底层都是操作DOM
directives: {
// 等价与bind + update两个钩子函数
"color": function (el, binding, vnode) {
console.log("el:", el);
console.log("binding:", binding);
// vnode中有一个上下文,在封装自定义指令时,会使用到上下文
console.log("vnode:", vnode);
el.style.color = binding.value;
},
// 在对象中放生命周期函数
// "color": {
// bind(el, binding, vnode) {
// console.log("bind...");
// // console.log("el:", el);
// // console.log("binding:", binding);
// // console.log("vnode:", vnode);
// el.style.color = binding.value;
// },
// update(el, binding, vnode) {
// console.log("upadte....");
// // console.log("el:", el);
// // console.log("binding:", binding);
// // console.log("vnode:", vnode);
// el.style.color = binding.value;
// },
// }
}
});
</script>
</body>
</html>
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h2>码路</h2>
<h3>{{name}}</h3>
<!-- <input type="text" v-mlmodel.lazy="name"> -->
<!-- <input type="text" v-mlmodel.trim="name"> -->
<!-- <input type="text" v-mlmodel.upper="name"> -->
<input type="text" v-mlmodel.number="name">
</div>
<script>
Vue.directive("mlmodel", {
bind(el, binding, vnode) {
console.log("el:", el);
console.log("binding:", binding);
console.log("vnode:", vnode);
let { expression, modifiers } = binding;
let { context } = vnode;
let { lazy, trim, upper, number } = modifiers;
// console.log(lazy);
el.value = context[expression];
// el.addEventListener(lazy?'change':'input', function (e) {
el.addEventListener(lazy ? 'blur' : 'input', function (e) {
let val = e.target.value;
if (trim) val = val.trim();
if (upper) val = val.toUpperCase();
if (number) val = parseFloat(val)
// console.log(val);
context[expression] = val
})
},
update(el, binding, vnode) {
}
})
let vm = new Vue({
el: "#app",
data() {
return {
name: "ok"
}
},
});
</script>
</body>
</html>
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h2>码路</h2>
<h3>{{name}}</h3>
<!-- <input type="text" v-mlmodel.lazy="name"> -->
<!-- <input type="text" v-mlmodel.trim="name"> -->
<!-- <input type="text" v-mlmodel.upper="name"> -->
<!-- <input type="text" v-mlmodel.number="name"> -->
<input type="text" v-mlmodel="name">
</div>
<script>
Vue.directive("mlmodel", {
bind(el, binding, vnode) {
console.log("el:", el);
console.log("binding:", binding);
console.log("vnode:", vnode);
let { expression, modifiers,value } = binding;
let { context } = vnode;
let { lazy, trim, upper, number } = modifiers;
// console.log(lazy);
el.value = context[expression];
// el.addEventListener(lazy?'change':'input', function (e) {
el.addEventListener(lazy ? 'blur' : 'input', function (e) {
let val = e.target.value;
if (trim) val = val.trim();
if (upper) val = val.toUpperCase();
if (number) val = parseFloat(val)
// console.log(val);
context[expression] = val
})
},
update(el, binding, vnode) {
}
})
let vm = new Vue({
el: "#app",
data() {
return {
name: "ok"
}
},
});
</script>
</body>
</html>
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
# 十五,插件
插件封装:
在Vue生态中,除了Vue本身,其它所有与Vue相关的第三方包,都是插件,都得以插件的方式进行集成。
作用:是一种更加高级的代码复用技术,可以以插件的方式为我们提供可复用的组件、混入、指令、过滤器、原型链API。
经验:以公司真实项目、优秀开源项目,你只要发现里面有好用的组件或指令,插件,都收集到你的代码库中,以后可以复用。
浏览器和vue相关的包:https://awesomejs.dev/for/vue/pkg/286379879029539329/
2
3
4
5
如何封装Vue插件?(两种写法):
第一种写法: const Plugin = { install (Vue) { } }
第二种写法: const Plugin = function (Vue) {}
2
面试题:
你有封装过Vue插件?怎么封装的?封装过什么?(有,老项目中封装的好用东西,以插件的方式搬到新项目中去)
请问 Vue.use() 有什么用?讲一讲它背后做了什么?(调用插件函数或install方法,并传入Vue实参)
2
参考代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h2 v-color="'red'">{{name}} --- {{a}}</h2>
<h2>{{price | rmb}}</h2>
<button @click="fn">点我</button>
<ml-button></ml-button>
</div>
<script>
// 插件:代码的复用技术 更加高级
// 可以在插件中封装可复用的组件,指令,过滤器,原型链,混入...
// 之前学习过的复用技术,都可以在插件中体现
// vue中如何封装插件,两种写法
// 1)const MyPlugin = { install(Vue){ } }
// Vue.use(Myplugin)
// 2)const Myplugin = function(Vue){ }
let MlPlugin = {
install(Vue) {
Vue.mixin({
data() {
return {
a: 666
}
}
});
Vue.component("ml-button", {
template: `
<div>我是一个小button</div>
`
});
Vue.filter("rmb", val => ("¥" + val.toFixed(2)));
Vue.directive("color", function (el, binding) {
el.style.color = binding.value;
});
Vue.prototype.$ml = function () { console.log("ml..."); }
}
}
let MlPlugin2 = function(Vue){
Vue.prototype.eat = 123456789;
}
Vue.use(MlPlugin);
Vue.use(MlPlugin2);
let vm = new Vue({
el: "#app",
data() {
return {
name: "码路",
price: 100
}
},
methods: {
fn() {
this.$ml();
console.log(this.eat);
}
},
});
</script>
</body>
</html>
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
# 十六,两个的API
Vue.nextTick() / this.$nextTick()
set操作,代码确实是同步的,但是set行为是异步的;set操作修改声明变量,触发re-render生成新的虚拟DOM,进一步执行diff运算,找到脏节点集合,交给Vue背后的更新队列去执行循环更新。
什么是nextTick?在更新队列中每一个更新任务都是一个更新单元,nextTick表示下一个更新单元(更新周期)。
作用:这么描述一个场景,我们set操作data(更新DOM),你希望访问这个DOM的最新状态时,使用this.$nextTick(handler)。
2
3
4
5
this.$forceUpdate()
事实:Vue响应式是有缺陷的,什么缺陷?在复杂的Vue应用中,如果声明式变量是引用数据类型,当你set操作这些复杂的引用数据类型时,视图不更新。解决方案,set操作完成后,立即调用 this.$forceUpdate()强调更新(强制re-render)有时候,this.$forceUpdate()也无法解决上述问题,对set操作的变量进行一行深复制。
面试题
谈一谈你对 Vue.nextTick() 的理解?有什么用?(在nextTick访问最新的DOM)
nextTick() 和 updated() 的区别 (前者只是表示一个更新单元已完成,后者是生命周期钩子表示整个页面更新完成)
Vue响应式有没有缺陷呢?有什么缺陷?遇到这种问题你会怎么办?
什么是深拷贝?什么是浅拷贝?有哪些深拷贝的方法?让你手写一个深拷贝方法,你会怎么写?
2
3
4
参考代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h2>{{name}}</h2>
<h1 v-text='num' id='h1'></h1>
<button @click='add'>+1</button>
<hr>
<h1 v-html="msg"></h1>
<!-- <h1 v-text="info.user.child[0].age"></h1> -->
<h1 v-text="info.user.child[0]"></h1>
<button @click="change">改变</button>
<hr>
</div>
<script>
let vm = new Vue({
el: "#app",
data() {
return {
name: "码路",
num: 1,
msg: "",
// info: {
// user: {
// child: [
// {
// name: "ml", age: 18
// }
// ]
// }
// }
info: {
user: {
child: [
18
]
}
}
}
},
mounted() {
setTimeout(() => {
let res = `<div><a id="a" href="">我是一个孤独的DIV</a></div>`;
this.msg = res;
this.$nextTick(() => {
document.getElementById("a").style.color = "red";
})
}, 200)
},
methods: {
add() {
// 数据变了,要刷新模块,这个刷新过程是异步
// 操作数据是同步的,后面的刷新模板是异步的
// 数据变了,产生新的虚拟DOM,新旧的虚拟DOM需要进行DIFF算法,找到差异,差异也叫脏节点
// 找到脏节点,交给vue的更新队列,去异步更新节点
this.num++
// nextTick next是下一个的意思 Tick是记录
// 每一次更新,都很多的更新任务,每一个更新任务,就是一个更新单元,nextTick表示下一个更新单元
// const res = document.getElementById('h1').innerText
// console.log(res)
this.$nextTick(() => {
// 作用:当改变数据了,模板要更新,在nextTick可以获取DOM的最新状态
// updated类似,也是等模板更新完毕后,才会执行
// 问:updated和nextTick有什么区别?
// 答:updated表示整个模板渲染完毕后才会执行,是指整个页面。
// nextTick指在下一个更新单元后执行
const res = document.getElementById('h1').innerText
console.log(res)
})
},
change() {
// this.info.user.child[0].age++
// 如果数组中放的是基本数据类型
// 通过索引操作数据,并不是响应式的
// 通过length操作也不是响应式
this.info.user.child[0]++
// 你感觉vue的响应式系统,有什么缺陷?
// 1)如果组件嵌套的非常深,数据结构也非常深,可以会导致响应式失效。
// 失效了,如何解决?
// A)$forceUpdate() 有可能不起作用
// B)需要对数据实现深copy
// this.info.user.child[0].age++
// this.info = JSON.parse(JSON.stringfy(this.info))
this.$forceUpdate(); // 强制更新
}
}
});
</script>
</body>
</html>
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
98
99
100
101
102
103
104
105
106
# 十七,组件通信
组件通信:
1)父子组件通信:父传子使用自定义属性(props),子传父使用自定义事件($emit())。
2)状态提升:当兄弟组件之间需要共享数据时,我们通常的做法是把这个数据定义它们的共同的父组件中,再通过自定义属性实现数据共享。
3)provide/inject:这是在组件树中,自上而下的一种数据通信方案,也就是说只能父级组件中向后代组件传递。需要注意的是,当provide提供动态数据(声明式变量)时,动态数据发生变化,后代组件们不会自动更新。这是为什么呢?你自己从生命周期流程的角度去思考。
4)ref通信:ref是Vue内置的一个属性,每一个HTML元素或组件都有这个属性;ref作用在HTML元素上得到DOM实例,ref作用在组件上得到组件实例。使用ref访问组件实例,进一步可以访问组件中的数据和方法。(说明:ref是一种快速的DOM的访问方式,当然ref也可作用在组件上得到组件实例。这些ref得到的DOM实例或组件实例,使用this.$refs来访问它们。ref尽量少用,除非某些难搞的需求。)
5)插槽通信:借助<slot>组件实现从子组件向父组件传递数据,借助this.$slots访问父组件中的插槽实例。(在自定义组件中使用this.$slots访问父组件给的插槽实例;在父组件插槽中使用#default='scoped'访问子组件<slot>回传的数据。这种通信在组件库中、工作中,非常常见!)
6)$parent/$children:借助$parent/$children可以实现,在任一组件中访问组件树中的其它任意组件实例,可以做到在组件中随意穿梭。($parent表示的是当前组件的父组件实例,$children表示的是当前组件的子组件们。)
7)$attrs/$listeners:借助$attrs可访问父组件传递过来的自定义属性(除了class和style外),借助$listenrs可以访问父组件给的自定义事件。在某些场景下,$attrs/$listeners可以替代props/$emit()这种通用的通信方案。
8)事件总线:借助于Vue内置的事件系统($on/$emit/$off/$once)实现“订阅-发布”式的通信,这种通信方式是一种与组件层级无关的“一对多”的通信。(工作中很少用,一些特殊的Vue项目才用得到事件总线。)
9)Vuex通信:这是Vue架构中终极的通信方案,也是Vue架构中用的最多的一种通信方案。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
面试题:
Vue中有哪些常用的通信方案?(父子、ref、provide、slot、$parent、事件总线、Vuex)
父子组件之间怎么通信?(父传子、子传父)
provide/inject有什么特点?(只能自上而下、没有响应式)
$attrs能不能接收class和style?(不能)
谈一谈你对事件总线的理解(“订阅-发布”模式)的理解?
谈一谈以上九种通信方案之间的区别?或者任意两种通信方案之间的区别?(语法区别、使用场景的区别)
有没有读过vue源码?经常加班,没时间读,今年打算读一下
2
3
4
5
6
7
参考代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<ml-a :couter="couter"></ml-a>
<ml-b :couter="couter"></ml-b>
</div>
<script>
Vue.component("ml-a", {
template: `
<div>
<div>我是ml-a组件 -- {{couter}}</div>
</div>
`,
props: {
couter: { type: Number, default: 0 }
}
})
Vue.component("ml-b", {
template: `
<div>
<div>我是ml-b组件 -- {{couter}}</div>
</div>
`,
props: {
couter: { type: Number, default: 0 }
}
})
let vm = new Vue({
el: "#app",
data() {
return {
couter: 0
}
}
});
</script>
</body>
</html>
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<ml-a :couter="couter" @add="couter+=$event"></ml-a>
<ml-b :couter="couter" @add="couter+=$event"></ml-b>
</div>
<script>
Vue.component("ml-a", {
template: `
<div>
<div>我是ml-a组件 -- {{couter}}</div>
<button @click="$emit('add',1)">+1</button>
</div>
`,
props: {
couter: { type: Number, default: 0 }
}
})
Vue.component("ml-b", {
template: `
<div>
<div>我是ml-b组件 -- {{couter}}</div>
<button @click="$emit('add',1)">+1</button>
</div>
`,
props: {
couter: { type: Number, default: 0 }
}
})
let vm = new Vue({
el: "#app",
data() {
return {
couter: 0
}
}
});
</script>
</body>
</html>
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<ml-a></ml-a>
<hr>
<h1>{{num}}</h1>
<button @click="num++">+1</button>
</div>
<script>
Vue.component("ml-a", {
template: `
<div>
<div>我是ml-a组件 -- {{msg}}</div>
<span v-for="i in list">{{i}}</span>
<div>{{num}}</div>
</div>
`,
inject: ["msg", "list", "num"]
})
// provide + inject是自上而下的通信方案
// 提供数据变了,后代不会自动刷新,提供的数据并不是响应式的,为什么?
// 答:从生命周期角度分析,注入数据发生在第个阶段
let vm = new Vue({
el: "#app",
provide() {
return {
msg: "hello",
list: ["a,b,c,d"],
num: this.num
}
},
data() {
return {
num: 110
}
}
});
</script>
</body>
</html>
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<ml-a ref="mla"></ml-a>
<button @click="fn">访问a组件</button>
</div>
<script>
Vue.component("ml-a", {
template: `
<div>
<div>我是ml-a组件</div>
</div>
`,
data() {
return {
a: 1, b: 2
}
},
methods: {
fn() {
console.log("fn...");
}
},
})
let vm = new Vue({
el: "#app",
data() {
return {
}
},
methods:{
fn(){
console.log(this.$refs.mla.a);
console.log(this.$refs.mla.b);
this.$refs.mla.fn()
}
}
});
</script>
</body>
</html>
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<ml-a>
<template #default>
<div>我是插入的内容 -- {{a}}</div>
</template>
<template #aa>
<div>我是插入的内容2 -- {{b}}</div>
</template>
<template #bb="scoped">
<div>我是插入的内容3 -- {{scoped.car}}</div>
</template>
<template #cc="{car}">
<div>我是插入的内容3 -- {{car}}</div>
</template>
</ml-a>
</div>
<script>
Vue.component("ml-a", {
template: `
<div>
<div>我是ml-a组件</div>
<slot name="default">
<div>我是默认值</div>
</slot>
<slot name='aa'>
<div>我是默认值</div>
</slot>
<slot name='bb' :car='car'>
<div>我是默认值</div>
</slot>
</slot>
<slot name='cc' :car='car'>
<div>我是默认值</div>
</slot>
</div>
`,
data() {
return {
car: "benchi"
}
}
})
let vm = new Vue({
el: "#app",
data() {
return {
a: 1,
b: 2
}
},
methods: {
}
});
</script>
</body>
</html>
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<ml-a>
</ml-a>
<hr>
<button @click="fn">获取子组件中的数据</button>
</div>
<script>
Vue.component("ml-a", {
template: `
<div>
<div>我是ml-a组件</div>
</div>
`,
mounted() {
console.log(this.$parent.a);
},
data() {
return {
b: 2
}
}
})
let vm = new Vue({
el: "#app",
data() {
return {
a: 1
}
},
methods: {
fn() {
console.log(this.$children[0].b);
}
}
});
</script>
</body>
</html>
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.page span {
display: inline-block;
padding: 0 10px;
cursor: pointer;
}
.page span.on {
color: red;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h2>{{name}}</h2>
<ml-a :page="page" @change="chagePage"></ml-a>
</div>
<script>
// 在有些情况下,$attrs和$listeners比较props和$emit更好用
// $attrs可以接收父传递过来的自定义属性
// $listeners可以接收父传递过来的自定义事件
Vue.component("ml-a", {
template: `
<div class="page">
<div>我是ml-a组件</div>
<span @click='$listeners.change(i)' :class="{on:$attrs.page === i}" v-for="i in pageArr" v-text="i"></span>
</div>
`,
mounted() {
console.log("$attrs:", this.$attrs);
console.log("$listeners:", this.$listeners);
},
computed: {
pageArr() {
let p = this.$attrs.page;
if (p <= 3) {
return [1, 2, 3, 4, 5]
}else{
// return ["a","b","c","d","e"]
return [p-2,p-1,p,p+1,p+2]
}
}
},
data() {
return {
}
}
})
let vm = new Vue({
el: "#app",
data() {
return {
name: "码路",
page: 1
}
},
methods: {
chagePage(val) {
this.page = val || 1;
}
},
});
</script>
</body>
</html>
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
</head>
<body>
<!--
事件总线:
1) let bus = new Vue(); 事件总线
bus.$on("eat",callback) 订阅 监听
bus.$emit("eat",'hello') 发布 可以传递数据
bus.$off("eat") 取消订阅 取消监听
bus.$once("eat") 只监听一次,或只订阅一次
-->
<div id="app">
<h2>{{name}}</h2>
<ml-wc></ml-wc>
<hr>
<ml-xq></ml-xq>
</div>
<script>
let bus = new Vue();; // 事件总线
Vue.component("ml-wc", {
template: `
<div>
<h2>wc在线</h2>
<input type="text" v-model="msg" @keyup.enter="send" />
<button @click="send">发送</button>
<div v-html="content"></div>
</div>
`,
data() {
return {
msg: "",
content: ""
}
},
mounted() {
bus.$on("wc", msg => {
console.log('wc收集到数据:', msg);
this.content += `<div class='row'>xq说:${msg}</div>`
})
},
methods: {
send() {
bus.$emit("xq", this.msg)
this.msg = ""
}
},
})
Vue.component("ml-xq", {
template: `
<div>
<h2>xq在线</h2>
<input type="text" v-model="msg" @keyup.enter="send" />
<button @click="send">发送</button>
<div v-html="content"></div>
</div>
`,
data() {
return {
msg: "",
content: ""
}
},
mounted() {
bus.$on("xq", msg => {
console.log('xq收集到数据:', msg);
this.content += `<div class='row'>wc说:${msg}</div>`
})
},
methods: {
send() {
bus.$emit("wc", this.msg)
this.msg = "";
}
},
})
let vm = new Vue({
el: "#app",
data() {
return {
name: "码路",
}
}
});
</script>
</body>
</html>
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
98
99
# 十八,第3次作业
常用指令有哪些?你怎么理解指令?你工作是否自已封装过指令?
举一个你封装自定义指令的例子(权限指令、拖拽指令)
复用真的好吗?(对新手不太友好,复用太多,改bug,一个出问题,其它地方也会出问题,容易造成项目瘫痪。)
你有封装过Vue插件?怎么封装的?封装过什么?(有,老项目中封装的好用东西,以插件的方式搬到新项目中去)
请问 Vue.use() 有什么用?讲一讲它背后做了什么?(调用插件函数或install方法,并传入Vue实参)
谈一谈你对 Vue.nextTick() 的理解?有什么用?(在nextTick访问最新的DOM)
nextTick() 和 updated() 的区别 (前者只是表示一个更新单元已完成,后者是生命周期钩子表示整个页面更新完成)
Vue响应式有没有缺陷呢?有什么缺陷?遇到这种问题你会怎么办?
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vue总结
1)十几个指令(内置指令)
2)底层原理(响应式,生命周期,两个API)
3)组件化(props,computed,watch,template,data,methods,组件注册...)
4)五个内置组件(slot,transition,component,keep-alive)
5)逻辑复用技术(自定义组件,混入,过滤器,自定义指令,原型链,插件)
6)组件通信方案(9种)
2
3
4
5
6
# 十九,脚手架
创建项目:
npm install cnpm -g --registry=https://registry.npmmirror.com
【使用npm安装@vue/cli】
cnpm install @vue/cli -g 全局安装(一次安装,以后都能用)
vue -V (验证@vue/cli是否安装成功)
创建项目:
vue create myapp
cd myapp
npm run serve
2
3
4
5
6
7
8
9
10
目录分析:
- README.md 记录整个项目的命令行、启动方式、业务需求与细节、特殊bug说明。。
- vue.config.js 是基于webpack(5)二次封装的配置文件,比如改端口、做代码、上线打包优化等。
- package.json 是npm包管理器的配套文件,用于记录整个项目的基本信息(版本号、项目名称、当前项目所需要的第三方依赖包...)注意:这个文件相当重要,现在的你不要随便动它。
- jsconfig.json 这是TypeScript的配置文件(最新的TS配置文件推荐使用 tsconfig.json)
- babel.config.js 这是Babel编译器的配置文件(在脚手架中可以用比较新的语法,Babel用于把比较新的语法编译成浏览器能够普遍兼容的ES5语法)
- .gitigore 当你 git add / git commit / git push时,要忽略哪些文件或目录。
- node_modules目录,当前项目所依赖的所有第三方包(如果你的项目没有这个目录,或者出现了丢包情况,都建议把node_modules目录删掉,执行`cnpm install`重新安装node_modules包。)
- public目录,是当前项目的本地的静态资源目录(就是本地服务器)
- src目录,是你的业务代码目录(源码目录)
- .vue文件(单文件组件,由视图模块、Vue组件选项、样式这三个部分组成)为了支持单文件组件的高亮,大家安装vetur插件。
2
3
4
5
6
7
8
9
10
面试题:
你做vue项目用的是哪个版本的脚手架?(v4/v5)怎么创建项目?(vue ui / vue create)
使用脚手架有什么好处?(有ESlint代码检测、有热更新、有单文件组织、背后还有webpack功能丰富)
什么是单文件组织?浏览器是怎么解析.vue代码?(vue-loader)
在脚手架中,如何做代理?你有打包过vue项目?有没有做过vue项目部署?(vue.config.js配置)
2
3
4
# 二十,路由
单页和多页:
单页面应用程序(SPA):通过路由系统把组件串联起来的并且只有一个根index.html页面的程序,叫做单页面应用程序。在SPA中,页面的切换,本质上就是组件的显示与隐藏,背后是路由系统在起作用。
多页面应用程序(MPA):整个应用程序中,有多个.html页面。
2
如何在脚手架环境中集成Vue路由系统?(vue(2) + vue-router(3))
第一步:安装路由v3版本,注册路由
cnpm i vue-router@3.5.4 -S
@用于指定版本、-S表示安装成功后把这个包记录在package.json的“dependencies”中。
新建src/router.js文件,注册路由Vue.use(VueRouter)
第二步:创建路由实例、定义路由规则,并在main.js挂载路由系统
export default new VueRouter({mode, routes:[]})
在main.js挂载路由 new Vue({ router })
第三步:在合适的位置放置一个视图容器和菜单
在App.vue的视图中放置一个 <router-view>显示url匹配成功的组件。
在App.vue的视图中使用 <router-link>制作菜单,点击跳转url。
2
3
4
5
6
7
8
9
10
11
12
盘点路由知识点:
两种路由模式:hash路由、history路由。
- hash路由:有#,背后是监听onhashchange事件实现的,hash路由部署上线不会出现404;
- history路由:没有#,背后是基于history api实现的,history路由部署上线会出现404问题。
两个全局组件:<router-view name>视图容器、<router-link to tag active-class>用于设计菜单导航的。
- <router-link>:to属性用于指定跳转的目标;tag用于指定最终渲染成什么标签,默认渲染成a标签;
- active-class/exact-active-class用于指定菜单的高亮样式。
- <router-view>:name属性用于指定命令视图(给router-view加个名字,默认叫default)。
两个内置API:$route表示路由信息,$router用于路由跳转的。
- $route路由信息: this.$route.fullPath/query/params/meta。(watch可以监听$route的变化)
- $router路由实例:this.$router.push()向前跳转/replace()向前跳转/back()返回上一次。
两种路由跳转:声明式跳转、编程式跳转。
- 所谓的声明式路由跳转,就是使用 <router-link>做跳转,一般用于菜单设计。
- 所谓的编程式路由跳转,就是使用 $router 做跳转,一般用在事件中。
两种命名:命名视图、命名路由。
- 所谓的命名视图,意思是给<router-view>加一个name属性。
- 所谓的命名路由,意思是给{path,component}路由规则取一个名字。
两种路由传参:query传参、动态路由传参。
- query传参:在跳转路由的url后面用?a=1&b=2&c=3这种方式传参,在另一个组件中使用this.$route.query接收。
- 动态路由传参:像这样 `{path: '/good/:id', component }`定义路由规则,在这条动态路由规则对应的组件中使用this.$route.params接收,或者开启props:true后使用 props选项来接收。
两个优化:路由懒加载、重定向与别名
- 路由懒加载:当一个SPA应用程序中的页面足够多,我们需要根据路由系统进行按需加载组件(而不是一次性加载所有组件),该怎么实现呢?使用路由懒加载(背后原理是Webpack代码分割技术、Vue异步组件)。路由懒加载,是一种性能优化方案。
- 重定向与别名:当用户访问一个未定义的路由时,给一个重定向(跳转到另一个已定义的路由上),这是一种用户体验的优化。重定向规则,一般要放在路由规则的最后面。什么是别名?别名是path的简写,可以用于路由访问;什么时候需要用到别名?当path比较复杂时,需要给它设计一个别名。
两个难点:嵌套视图(嵌套路由)、导航守卫(路由元信息)。
- 嵌套视图(嵌套路由):当我们设计类似知乎官网那样的一级菜单、二级菜单时,就要用到嵌套视图。所谓“嵌套视图”,从组件树的角度来讲,<router-view>所显示的组件的内部还有<router-view>;从路由规则的角度来讲,{path,component,children}带有children这个属性;从产品设计的角度来讲,一级菜单对应的页面中还有二级菜单。
- 导般守卫:在router实例对象上有三个重要的全局钩子(beforeEach、beforeResolve、afterEach),每次url发生变化时,都会触发这三个钩子按顺序执行。那么以后我可以在这些钩子编写验证逻辑,如果验证通过就放你过去,你就可以正常访问你想访问的页面;如果验证失败,就阻止你访问目标页面,这就实现“守卫”的效用了。在路由中,使用导航守卫和路由元信息,可以做鉴权、还可以做权限设计。
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
面试题:
- 说一下history和hash路由有什么区别?
- 什么是命名视图、命名路由、别名?(<router-view name>、{path,component,name,alias})
- 说一下路由怎么传参?什么是动态路由?(两种传参。所谓动态路由,就是定义路由规则时path字段串中有冒号)
- 什么是路由懒加载?它背后的原理是什么?(Webpack代码分割、使用动态导入语法的Vue异步组件)
- 什么是路由守卫(导航守卫)?你项目的鉴权怎么做的?你这个管理系统的权限怎么设计的?
- 在Vue中做组件的显示与隐藏,有哪些方案?(v-show/v-if、<component>、路由或嵌套路由)
- 在Vue中,怎么监听路由的变化?(watch监听$route)
- 什么是单页面应用程序(SPA)?你怎么理解单页面应用程序?
2
3
4
5
6
7
8
# 二十一,Vuex
版本问题:
在Vue2技术栈中:Vue(2) + VueRouter(3) + Vuex(3)
在Vue3技术栈中:Vue(3) + VueRouter(3) + Vuex(4) / Pinia(2)
2
关于vuex:
状态管理:状态在应用程序中表示数据,状态管理就是数据管理。
两个作用:组件之间的数据共享;做数据缓存。
特别注意:在应用程序中,要正确地选择通信方案,数据流管理要合理。
怎么学习Vuex?一个流程图(要求会画会说会写)、五个概念、四个map方法。
2
3
4
集成Vuex:
第一步:安装vuex指定版本,并注册Vue.use(Vuex) / cnpm i vuex@3.6.2 -S
第二步:创建store实例{五个概念}并抛出,在main.js挂载store
第三步:在组件中使用this.$store/四个map方法来使用store或走数据流程
2
3
五个概念:
创建store时要用的五个概念(state/getters/mutations/actions/modules)
- state: {} 用于定义可被组件共享数据,是具有响应式的;在组件中使用this.$store.state来访问它们。
- getters: {fn} 用于计算state,相当于Vue的计算属性,当state发生变化时getters方法自动自动重新计算;在组件中使用this.$store.getters来访问它们。
- mutations: {fn} 专门用于修改state的,所以mutations方法是这样fn(state,payload)定义的;mutations方法在actions中或组件中使用,使用$store.commit('mutations方法',payload)
- actions: {fn} 专门用于调接口的,所以actions方法是这样fn(store,payload)定义的;在组件中使用this.$store.dispatch('actions方法', payload)。
- modules: {子store} 是一个Vuex架构层面的概念,用于拆分子store。大家在拆分子store务必在子store中使用namespaced:true开启命名空间。
2
3
4
5
6
四个map:
mapState/mapGetters,必须写在computed计算属性中,用于访问state/getters数据。映射进来后,就可以用this来访问这些数据了。
mapActions/mapMutations 必须写在methods选项中,用于访问mutations/actions方法。映射进来后,可以用this调用这些方法。
它们的语法是相同的:map*('命名空间', ['k1', 'k2'])
2
3
几个原则:
原则1:只要使用Vuex一定要拆分store,拆分store后在根store上不要再使用state/mutations/actions。
原则2:在子store务必开启命名空间namespaced:true。
原则3:在组件中尽可能不要使用$store,建议使用四个map*方法。
2
3
面试题:
简述Vuex的作用和意义?(基于Flux思想的状态管理工具,用于组件间数据通信、用于数据缓存)
简述Vuex的工作流程?(actions -> mutations -> state -> 组件)
什么是单向数据流?(state -> view -> action)
说一下状态管理的五个概念分别代表什么?说一下mutations和actions的区别?
2
3
4
仓库中的代码:
// src/store/index.js
import Vuex from "vuex"
import Vue from "vue"
// 在真实开发中,需要对axios进行二次封装
import axios from "axios"
Vue.use(Vuex);
let store = new Vuex.Store({
state: {
num: 1,
list: [],
},
mutations: {
addNum(state, payload) {
state.num += payload
},
changeList(state, payload) {
state.list = payload;
}
},
actions: {
asyncAddNum({ commit }, params) {
setTimeout(() => {
commit("addNum", params)
}, 2000)
},
getList({ commit }, params) {
axios.get("https://cnodejs.org/api/v1/topics", {
params: params
}).then(res => {
// console.log("res:", res);
commit("changeList", res.data.data)
})
}
},
getters: {
doubleNum(state) {
return state.num * 2
}
}
})
export default store;
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
使用仓库:
<template>
<div>
<div>HomePage</div>
<!-- home中的二级路由出口 -->
<!-- <router-link to="/home/a" exact-active-class="on">A</router-link>
<router-link to="/home/b" exact-active-class="on">B</router-link>
<router-view></router-view> -->
<!-- --------------- 学习使用vuex -->
<hr>
<div>
<p>使用仓库中的数据:{{ $store.state.num }}</p>
<p>使用仓库中的计算属性:{{ $store.getters.doubleNum }}</p>
<button @click="add">同步+1</button>
<button @click="asyncAdd">异步+1</button>
</div>
<hr>
<ul>
<li v-for="item in $store.state.list" :key="item.id">{{ item.title }}</li>
</ul>
<button @click="nextpage">下一页</button>
</div>
</template>
<script>
export default {
name: "HomePage",
props: [],
data() {
return {
page: 1,
tab: "job",
limit: 10,
};
},
mounted() {
this.$store.dispatch("getList", {
page: this.page,
tab: this.tab,
limit: this.limit,
})
},
methods: {
add() {
this.$store.commit("addNum", 1)
},
asyncAdd() {
this.$store.dispatch("asyncAddNum", 2)
},
nextpage() {
this.page++;
this.$store.dispatch("getList", {
page: this.page,
tab: this.tab,
limit: this.limit,
})
}
},
};
</script>
<style lang="less" scoped>
</style>
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
使用四个map方法:
<template>
<div>
<div>HomePage</div>
<!-- home中的二级路由出口 -->
<!-- <router-link to="/home/a" exact-active-class="on">A</router-link>
<router-link to="/home/b" exact-active-class="on">B</router-link>
<router-view></router-view> -->
<!-- --------------- 学习使用vuex -->
<hr>
<div>
<p>使用仓库中的数据:{{ num }}</p>
<p>使用仓库中的计算属性:{{ doubleNum }}</p>
<button @click="addNum(1)">同步+1</button>
<button @click="asyncAddNum(1)">异步+1</button>
</div>
<hr>
<ul>
<li v-for="item in list" :key="item.id">{{ item.title }}</li>
</ul>
<button @click="nextpage">下一页</button>
</div>
</template>
<script>
import { mapState, mapMutations, mapActions, mapGetters } from "vuex"
export default {
name: "HomePage",
props: [],
data() {
return {
page: 1,
tab: "job",
limit: 10,
};
},
computed: {
...mapState(["num", "list"]),
...mapGetters(["doubleNum"]),
},
mounted() {
this.$store.dispatch("getList", {
page: this.page,
tab: this.tab,
limit: this.limit,
})
},
methods: {
...mapMutations(['addNum', 'changeList']),
...mapActions(['asyncAddNum', 'getList']),
nextpage() {
this.page++;
this.getList({
page: this.page,
tab: this.tab,
limit: this.limit,
})
}
},
};
</script>
<style lang="less" scoped>
</style>
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
modules的使用:
// src/store/index.js
import Vuex from "vuex"
import Vue from "vue"
// 在真实开发中,需要对axios进行二次封装
import axios from "axios"
Vue.use(Vuex);
import a from "./modules/a.js"
import b from "./modules/b.js"
// 如果创建了n个模块,通常在入口,不会写刚才讲的四个概念
let store = new Vuex.Store({
modules: {
a,
b,
}
})
export default store;
// src/store/modules/a.js
import axios from "axios"
export default {
state: {
num: 1,
list: [],
},
mutations: {
addNum(state, payload) {
state.num += payload
},
changeList(state, payload) {
state.list = payload;
}
},
actions: {
asyncAddNum({ commit }, params) {
setTimeout(() => {
commit("addNum", params)
}, 2000)
},
getList({ commit }, params) {
axios.get("https://cnodejs.org/api/v1/topics", {
params: params
}).then(res => {
// console.log("res:", res);
commit("changeList", res.data.data)
})
}
},
getters: {
doubleNum(state) {
return state.num * 2
}
}
}
// // src/store/modules/b.js
import axios from "axios"
export default {
state: {
msg: "malu"
},
}
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
使用仓库:
<template>
<div>
<div>HomePage</div>
<!-- home中的二级路由出口 -->
<!-- <router-link to="/home/a" exact-active-class="on">A</router-link>
<router-link to="/home/b" exact-active-class="on">B</router-link>
<router-view></router-view> -->
<!-- --------------- 学习使用vuex -->
<hr>
<div>
<p>使用仓库中的数据:{{ $store.state.a.num }}</p>
<p>使用仓库中的计算属性:{{ $store.getters.doubleNum }}</p>
<button @click="add">同步+1</button>
<button @click="asyncAdd">异步+1</button>
</div>
<hr>
<ul>
<li v-for="item in $store.state.a.list" :key="item.id">{{ item.title }}</li>
</ul>
<button @click="nextpage">下一页</button>
</div>
</template>
<script>
export default {
name: "HomePage",
props: [],
data() {
return {
page: 1,
tab: "job",
limit: 10,
};
},
mounted() {
this.$store.dispatch("getList", {
page: this.page,
tab: this.tab,
limit: this.limit,
})
},
methods: {
add() {
this.$store.commit("addNum", 1)
},
asyncAdd() {
this.$store.dispatch("asyncAddNum", 2)
},
nextpage() {
this.page++;
this.$store.dispatch("getList", {
page: this.page,
tab: this.tab,
limit: this.limit,
})
}
},
};
</script>
<style lang="less" scoped>
</style>
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
项目中,都会给不同的模块开启命名空间,如下:
import axios from "axios"
export default {
namespaced: true, // 开启命名空间
state: {
msg: "malu"
},
}
2
3
4
5
6
7
8
貌似开启命名空间没有解决实际问题,要使用命名空间,需要配置mapxx。
使用mapxx方法:
<template>
<div>
<div>HomePage</div>
<!-- home中的二级路由出口 -->
<!-- <router-link to="/home/a" exact-active-class="on">A</router-link>
<router-link to="/home/b" exact-active-class="on">B</router-link>
<router-view></router-view> -->
<!-- --------------- 学习使用vuex -->
<hr>
<p>使用b 子store中的msg: {{ msg }}</p>
<hr>
<div>
<p>使用仓库中的数据:{{ num }}</p>
<p>使用仓库中的计算属性:{{ doubleNum }}</p>
<button @click="addNum(1)">同步+1</button>
<button @click="asyncAddNum(1)">异步+1</button>
</div>
<hr>
<ul>
<li v-for="item in list" :key="item.id">{{ item.title }}</li>
</ul>
<button @click="nextpage">下一页</button>
</div>
</template>
<script>
import { mapState, mapMutations, mapActions, mapGetters } from "vuex"
export default {
name: "HomePage",
props: [],
data() {
return {
page: 1,
tab: "job",
limit: 10,
};
},
computed: {
...mapState("a", ["num", "list"]),
...mapState("b", ["msg"]),
...mapGetters("a", ["doubleNum"]),
},
mounted() {
this.$store.dispatch("a/getList", {
page: this.page,
tab: this.tab,
limit: this.limit,
})
// this.getList({
// page: this.page,
// tab: this.tab,
// limit: this.limit,
// })
},
methods: {
...mapMutations("a", ['addNum']),
...mapActions("a", ['asyncAddNum', 'getList']),
nextpage() {
this.page++;
this.getList({
page: this.page,
tab: this.tab,
limit: this.limit,
})
}
},
};
</script>
<style lang="less" scoped>
</style>
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
# 二十二,第4次作业
- Vue中有哪些常用的通信方案?(父子、ref、provide、slot、$parent、事件总线、Vuex)
- 谈一谈你对事件总线的理解(“订阅-发布”模式)的理解?
- 谈一谈以上九种通信方案之间的区别?或者任意两种通信方案之间的区别?(语法区别、使用场景的区别)
- 你做vue项目用的是哪个版本的脚手架?(v4/v5)怎么创建项目?(vue ui / vue create)
- 使用脚手架有什么好处?(有ESlint代码检测、有热更新、有单文件组织、背后还有webpack功能丰富)
- 什么是单文件组织?浏览器是怎么解析.vue代码?(vue-loader)
- 说一下history和hash路由有什么区别?
- 什么是命名视图、命名路由、别名?(<router-view name>、{path,component,name,alias})
- 说一下路由怎么传参?什么是动态路由?(两种传参。所谓动态路由,就是定义路由规则时path字段串中有冒号)
- 什么是路由守卫(导航守卫)?你项目的鉴权怎么做的?你这个管理系统的权限怎么设计的?
- 在Vue中做组件的显示与隐藏,有哪些方案?(v-show/v-if、<component>、路由或嵌套路由)
- 在Vue中,怎么监听路由的变化?(watch监听$route)
- 什么是单页面应用程序(SPA)?你怎么理解单页面应用程序?
- 简述Vuex的作用和意义?(基于Flux思想的状态管理工具,用于组件间数据通信、用于数据缓存)
- 简述Vuex的工作流程?(actions -> mutations -> state -> 组件)
- 什么是单向数据流?(state -> view -> action)
- 说一下状态管理的五个概念分别代表什么?说一下mutations和actions的区别?
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20