【VUE常用特性】(P29~52)

本文标题中的数字表示B站视频内容对应的分P数。

1.常用特性预览

  1. 表单操作
  2. 自定义指令
  3. 计算属性
  4. 侦听器
  5. 过滤器
  6. 生命周期

2.表单操作

(1)基于Vue的表单操作

之前学的v-model指令用来实现表单输入域的双向数据绑定

  • Input 单行文本
  • textarea 多行文本
  • select 下拉多选
  • radio 单选框
  • checkbox 多选框
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style type="text/css">
form div {
height: 40px;
line-height: 40px;
}
form div:nth-child(4) {
height: auto; /* 第四个div,也就是职业那一行,设置高度 */
}
form div span:first-child {
display: inline-block;
width: 100px; /* 每个div下面的第一个span,也就是标题,设置宽度 */
}
</style>
</head>
<body>
<div id="app">
<form action="http://itcast.cn">

<div>
<span>姓名:</span>
<span>
<input type="text" v-model='uname'>
</span>
</div>

<div>
<span>性别:</span>
<span>
<input type="radio" id="male" value="1" v-model='gender'>
<label for="male"></label>
<input type="radio" id="female" value="2" v-model='gender'>
<label for="female"></label>
</span>
</div>

<div>
<span>爱好:</span>
<input type="checkbox" id="ball" value="1" v-model='hobby'>
<label for="ball">篮球</label>
<input type="checkbox" id="sing" value="2" v-model='hobby'>
<label for="sing">唱歌</label>
<input type="checkbox" id="code" value="3" v-model='hobby'>
<label for="code">写代码</label>
</div>

<div>
<span>职业:</span>
<!-- multiple属性控制多选,若为多选,下方Vue该职业的值要从数字改为数组,否则报错 -->
<select v-model='occupation'>
<option value="0">请选择职业...</option>
<option value="1">教师</option>
<option value="2">软件工程师</option>
<option value="3">律师</option>
</select>
</div>

<div>
<span>个人简介:</span>
<textarea v-model='desc'></textarea>
</div>

<!-- 禁用按钮默认提交 -->
<div>
<input type="submit" value="提交" @click.prevent='handle'>
</div>

</form>
</div>

<script type="text/javascript" src="js/vue.js"></script>
<script>
var vm = new Vue ({
el: '#app',
data: {
uname: 'Jack',
gender: 1,
hobby: ['2','3'],
occupation: 2,
// occupation: ['2','3'], //多选时,职业默认值应为数组
desc: 'nihao'
},
methods: {
handle: function(){
console.log(this.uname)
console.log(this.gender)
console.log(this.hobby.toString()) //.toString()加不加无所谓
console.log(this.occupation)
console.log(this.desc)

}
}
});
</script>

</body>
</html>

(2)表单域修饰符

  • number:转化为数值(方便进行计算操作)
  • trim:去掉开始和结尾的空格(中间的去不掉)
  • lazy : 将input事件切换为change事件

    input事件每次输入内容都会被触发
    change事件只有当失去焦点时候才会触发(比如验证注册昵称是否重复)

1
<input v-model.number="age" type="number">

3.自定义指令

(1)为何需要自定义指令

除了 Vue 一系列内置的指令 (比如 v-modelv-show) 之外,Vue 还允许你注册自定义的指令去满足你特定的需求。

(2)自定义指令的语法规则

以自定义获取元素焦点的指令为例:

1
2
3
4
5
6
7
// 第一个参数是指令名称,第二个参与用于实现指令操作的业务逻辑
Vue.directive('focus' {
inserted: function(el) {
//el表示指令所绑定的焦点
el.focus();
}
})

上面定义指令的过程中,inserted是一个钩子函数,规定自定义指令里会触发的一些动作。每个钩子函数有多个钩子函数参数
关于:指令钩子:指令的定义对象提供的几种钩子函数

(3)自定义指令用法

上方定义完指令,如何使用呢?

1
<input type="text" v-focus>

(4)带参数的自定义指令

以改变背景颜色为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 自定义指令
Vue.directive('color', {
bind: function(el, binding) {
el.style.backgroundColor = binding.value.color;
}
})

// 新建Vue实例
var vm = new Vue({
el: '#app',
data: {
msg: {
color: 'orange'
}
},
methods: {
hangle: function(){

}
}
})

其中,bind也是一个钩子函数,但和刚刚的inserted有点儿差别

(5)带参数自定义指令用法

1
2
3
4
<!-- 使用新建的指令 -->
<div id="app">
<input type="text" v-color='msg'>
</div>

(6)自定义局部指令

语法:在Vue实例(组件)的当中添加一个额外的对象directives,用于放置我们的局部指令。也就是在eldatamethods后面加上directives:

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
var vm = new Vue({
el: '#app',
data: {
msg: {
color: 'orange'
}
},
methods: {
hangle: function(){

}
},
// 注意有s
directives: {
color: {
bind: function(el, binding) {
el.style.backgroundColor = binding.value.color;
}
},
focus: {
inserted: function(el) {
el.focus();
}
}
}
})

实际上这种自定义的局部指令和上面普通的自定义指令,在功能上没有任何变化。区别有:

  1. 全局指令钩子函数写在Vue实例里面,局部指令写在Vue实例外面了。
  2. 局部指令局部生效(在本组件中使用),普通自定义指令全局生效。
1
2
3
4
<div id="app">
<input type="text" v-color='msg'>
<input type="text" v-focus>
</div>

4.计算属性

(1)为何需要计算属性?

比如,想要把{{msg}}运算符输出的字符串“Hello”前后反转,用原生JS方法:

1
2
3
4
5
6
7
8
9
// 原始标签
<div>{{msg}}</div>

// 字符串变成数组
<div>{{msg.split('')}}</div>
// 数组中元素顺序进行反转
<div>{{msg.split('').reverse()}}</div>
// 反转后的数组元素拼接成字符串
<div>{{msg.split('').reverse().join('')}}</div>

可以看出来,模板中的数据展示情况相对复杂,代码可读性低

表达式的计算逻辑比较复杂时,使用计算属性可以使模板内容更加简洁

(2)计算属性的用法

在原来的Vue实例中添加一个对象computed

1
2
3
4
5
6
7
8
9
10
11
12
13
var vm = new Vue({
el: '#app',
data: {
msg: 'Hello'
},
computed: {
// 方法名称:reversedMessage(翻转字符串)
reversedMessage: function () {
// 注意细节,要return返回结果
return this.msg.split('').reverse().join('')
}
}
})

页面中使用方法:

1
2
3
4
5
<!-- 使用时reversedMessage不需要加小括号,直接写函数名就可以 -->
<div id="app">
<div>{{msg}}</div>
<div>{{reversedMessage}}</div>
</div>

使用了计算属性,我们可以把模板中复杂的计算逻辑抽取出来,这样的话我们的模板就变得更加简单

(3)计算属性与方法的区别

  • computed计算属性:是基于它们的依赖进行缓存的(计算一次结果,利用缓存,下次无需计算)
  • methods方法:不存在缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var vm = new Vue({
el: '#app',
data: {
msg: 'Hello'
},
methods: {
// 方法:翻转字符串
reversedMessage: function () {
console.log('methods执行了一次')
return this.msg.split('').reverse().join('')
}
},
computed: {
// 方法:翻转字符串
reverseString: function () {
console.log('computed执行了一次')
return this.msg.split('computed').reverse().join('')
}
}
})

页面中调用时:

1
2
3
4
5
6
7
8
9
<!-- “计算属性”不需要加小括号,直接写函数名就可以 -->
<!-- “函数方法”需要加小括号进行调用 -->
<div id="app">
<div>{{msg}}</div>
<div>{{reversedMessage()}}</div>
<div>{{reversedMessage()}}</div>
<div>{{reverseString}}</div>
<div>{{reverseString}}</div>
</div>

控制台显示结果是:

  1. methods:方法中的reversedMessage()执行了两次;
  2. computed:计算属性reverseString利用缓存只执行了一次。

注意计算属性是有依赖的,如果所依赖的数据发生变化,缓存就失效了,会重新计算


5.侦听器

(1)侦听器的应用场景

用来侦听数据,如果数据变化,就通知侦听器所绑定的方法。

和刚刚的计算属性有相似的地方,就是数据的变化会触发对应的方法。

但是侦听器特有的应用场景是执行异步或开销较大的操作

  1. 异步:如Ajax和定时任务
  2. 开销大:指比较耗时的

(2)侦听器的用法

使用方法也和计算属性差不多,但它引入的属性是watch

1
2
3
4
5
6
7
8
9
watch: {
firstName: function(val){
// val表示变化之后的值
this.fullName = val + this.lastName;
},
lastName: function(val) {
this.fullName = this.firstName + val;
}
}

(3)案例1(拼接姓名)

  • 输入名字和姓氏,显示全名:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Document</title>
    </head>
    <body>
    <div id="app">

    <div>
    <span>名:</span>
    <span>
    <input type="text" v-model='firstName'>
    </span>
    </div>

    <div>
    <span>姓:</span>
    <span>
    <input type="text" v-model='lastName'>
    </span>
    </div>

    <div>{{fullName}}</div>
    </div>

    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
    var vm = new Vue({
    el: '#app',
    data: {
    firstName: 'Jim',
    lastName: 'Green',
    fullName: 'Jim Green'
    },

    /*侦听器*/
    watch: {
    firstName: function(val) {
    this.fullName = val + ' ' + this.lastName;
    },
    lastName: function(val) {
    this.fullName = this.firstName + ' ' + val;
    }
    }
    });
    </script>
    </body>
    </html>

其实此例用计算属性也能做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var vm = new Vue({
el: '#app',
data: {
firstName: 'Jim',
lastName: 'Green',
// fullName: 'Jim Green' //不需要了
},
computed: {
fullName: function(){
// 计算属性要有返回值
return this.firstName + ' ' + this.lastName;
}
}
})

对于这个场景,其实用计算属性更简单,用侦听器反而麻烦。

但有一些场景,用计算属性是做不到的(异步或开销比较大的操作)。

(4)案例2(验证昵称可用性)

本例展示监听器处理异步的操作,验证用户名的可用性。

一般在注册用户时使用,需求是:

  1. 输入用户名,失去焦点时才验证是否存在,(避免频繁调用接口)
  2. 如果已经存在,提示重新输入,
  3. 如果不存在,提示可以使用。

实现方法:

  1. 通过v-model实现数据绑定
  2. 需要提供提示信息
  3. 需要侦听器输入信息的变化
  4. 使用.lazy需要修改触发的事件(需是“失去焦点”时,而不是内容变化时)

    lazy是上面刚刚学的表单操作的“表单域修饰符”,将input事件切换为change事件:
    input事件每次输入内容都会被触发
    change事件只有当失去焦点时候才会触发(比如验证注册昵称是否重复)


6.过滤器

(1)过滤器的作用是什么?

格式化数据,比如将字符串格式化为首字母大写,将日期格式化为指定的格式等

hello => Vue过滤器 => Hello

(2)自定义过滤器

1
2
3
4
Vue.filter('过滤器名称', function(value){
// 过滤器业务逻辑,注意最后要返回最终结果
return 运算结果
})

案例:将输入字符串的首字母大写:

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<input type="text" v-model='msg'>
<div>{{msg | upper}}</div>
<div>{{msg | upper | lower}}</div>
<div :abc='msg | upper'>测试数据</div>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
过滤器
1、可以用与插值表达式和属性绑定
2、支持级联操作
*/

// 这里是全局定义
Vue.filter('upper', function(val) {
return val.charAt(0).toUpperCase() + val.slice(1);
});
// Vue.filter('lower', function(val) {
// return val.charAt(0).toLowerCase() + val.slice(1);
// });

var vm = new Vue({
el: '#app',
data: {
msg: ''
},

// 这里是局部定义
filters: {
upper: function(val) {
return val.charAt(0).toUpperCase() + val.slice(1);
}
}
});
</script>
</body>
</html>

其中,使用到的原生JS有:

  1. .charAt(0)可以拿到字符串第一个字符
  2. .toUpperCase()可以把字符串变成大写
  3. .toLowerCase()可以把字符串变成小写
  4. .slice(1)可以从第二个字符截取到最后

(3)过滤器的使用

1
2
3
4
5
6
7
8
9
<!-- 通过差值表达式使用,竖线后面是过滤器名称 -->
<div>{{msg | upper}}</div>

<!-- 通过级联的方式使用,支持同时使用多个,
把前边处理的结果作为下一个过滤器的输入值,然后再进行处理,最后产生一个新的结果 -->
<div>{{msg | upper | lower}}</div>

<!-- 通过属性绑定的值来使用 -->
<div v-bind:id=“id | formatId"></div>

(4)局部过滤器

过滤器也支持局部定义,刚刚的案例中已经展示过了

1
2
3
4
5
6
7
8
9
10
11
12
13
var vm = new Vue({
el: '#app',
data: {
msg: ''
},

// 这里是局部定义的方法
filters: {
upper: function(val) {
return val.charAt(0).toUpperCase() + val.slice(1);
}
}
});

(5)带参数的过滤器

1
2
3
Vue.filter('format', function(value, arg1){
// value就是过滤器传递过来的参数
})

第一个参数默认是要处理的data数据,所以要从第二个参数开始传起

(6)带参数过滤器的使用

1
<div>{{date | format(‘yyyy-MM-dd')}}</div>

(7)案例:过滤器格式化日期

2018-11-15T09:20:15.004Z
          ↓
      2018-09-27

日期格式化规则:

y:年, 
M:年中的月份(1-12), 
d:月份中的天(1-31), 
h:小时(0-23), 
m:分(0-59), 
s:秒(0-59), 
S:毫秒(0-999),
q:季度(1-4)

将时间格式化为 yyyy-MM-dd 格式

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<div>{{date | format('yyyy-MM-dd hh:mm:ss')}}</div>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
过滤器案例:格式化日期

*/
// Vue.filter('format', function(value, arg) {
// if(arg == 'yyyy-MM-dd') {
// var ret = '';
// ret += value.getFullYear() + '-' + (value.getMonth() + 1) + '-' + value.getDate();
// return ret;
// }
// return value;
// })
Vue.filter('format', function(value, arg) {
function dateFormat(date, format) {
if (typeof date === "string") {
var mts = date.match(/(\/Date\((\d+)\)\/)/);
if (mts && mts.length >= 3) {
date = parseInt(mts[2]);
}
}
date = new Date(date);
if (!date || date.toUTCString() == "Invalid Date") {
return "";
}
var map = {
"M": date.getMonth() + 1, //月份
"d": date.getDate(), //日
"h": date.getHours(), //小时
"m": date.getMinutes(), //分
"s": date.getSeconds(), //秒
"q": Math.floor((date.getMonth() + 3) / 3), //季度
"S": date.getMilliseconds() //毫秒
};

format = format.replace(/([yMdhmsqS])+/g, function(all, t) {
var v = map[t];
if (v !== undefined) {
if (all.length > 1) {
v = '0' + v;
v = v.substr(v.length - 2);
}
return v;
} else if (t === 'y') {
return (date.getFullYear() + '').substr(4 - all.length);
}
return all;
});
return format;
}
return dateFormat(value, arg);
})
var vm = new Vue({
el: '#app',
data: {
date: new Date()
}
});
</script>
</body>
</html>

上方过滤器的处理过程用到了正则,可以先不用理解,会用就行


7.生命周期

Vue官网-生命周期图示

(1)主要阶段

  • 挂载(初始化相关属性)
  1. beforeCreate
  2. created
  3. beforeMount
  4. mounted
  • 更新(元素或组件的变更操作)
  1. beforeUpdate
  2. updated
  • 销毁(销毁相关属性)
  1. beforeDestroy
  2. destroyed
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<div>{{msg}}</div>
<button @click='update'>更新</button>
<button @click='destroy'>销毁</button>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
Vue实例的生命周期

*/
var vm = new Vue({
el: '#app',
data: {
msg: '生命周期'
},
methods: {
// 更新按钮
update: function(){
this.msg = 'hello';
},
// 销毁按钮
destroy: function(){
this.$destroy();
}
},

// 挂载阶段
beforeCreate: function(){
console.log('beforeCreate');
},
created: function(){
console.log('created');
},
beforeMount: function(){
console.log('beforeMount');
},
mounted: function(){
console.log('mounted');
},
// 更新阶段
beforeUpdate: function(){
console.log('beforeUpdate');
},
updated: function(){
console.log('updated');
},
// 销毁操作
beforeDestroy: function(){
console.log('beforeDestroy');
},
destroyed: function(){
console.log('destroyed');
}
});
</script>
</body>
</html>

(2)Vue实例的产生过程

Vue实例的产生需要经过一个过程,这个过程包含了一系列钩子函数的调用。

选项-生命周期钩子

  1. 【挂载阶段】beforeCreate :在实例初始化之后,数据观测和事件配置之前被调用。
  2. 【挂载阶段】created :在实例创建完成后被立即调用。
  3. 【挂载阶段】beforeMount :在挂载开始之前被调用。
  4. 【挂载阶段】mounted :el被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子。
  5. 【更新阶段】beforeUpdate :数据更新时调用,发生在虚拟DOM打补丁之前。
  6. 【更新阶段】updated :由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。
  7. 【销毁阶段】beforeDestroy :实例销毁之前调用。
  8. 【销毁阶段】destroyed :实例销毁后调用。

在这些钩子函数中,我们最需要关心的是mounted
这个函数一旦被触发,就代表着初始化已完成,
代表页面中模板内容存在了,
代表着我们就可以往里面填充数据了。

因此,mounted最大的应用场景之一,是用于要调用后台接口获取数据,然后把数据填充到模板里边。因为要保证模板已经存在,才能往里面填充数据啊。


特别感谢: