Vue(三)
链接地址:
Vue 全套教程(一)
Vue 全套教程(二)
文章目录
一、监视数据原理
Vue 会监视 data 中所有层次的数据,自动的给每个元素添加 getter、setter。也就是说,自定义的 data 数据在变成 Vue 实例的 _data
时会给每个元素自动添加 getter、setter(数据劫持)。
基本原理:Vue 会汇总 data 中的所有属性形成一个数组,然后遍历数组中的所有元素,给每一个元素添加 getter 和 setter,setter 方法监视到 _data
中的数据发生变化时,会自动的重新解析模板,更新页面中的数据。
注意:
-
这里所说的 getter、setter 指的并不是数据代理,而是
_data
中的每个属性也都会有 getter、setter,如下所示:代码演示:
<body> <div id="root"> <h2>名称:{{name}}</h2> </div> </body> <script type="text/javascript"> const vm = new Vue({ el:'#root', data:{ name:'Jay' } }) console.log(vm) </script>
控制台输出vm后的结果如下图:
-
数据代理指的是 Vue 实例的数据如何影响
_data
中的数据。而数据劫持指的是如何将_data
中的数据改动体现到页面中。 -
对于上图,当执行
vm.name = '新名字'
时,首先会通过红框中的 setter 方法修改_data
中的数据(数据代理),然后自动的通过蓝框中的 setter 修改页面上的值(数据劫持)。
1.1 监视对象中的数据
首先需要明确,Vue 只会对 new Vue
时就存在的 data 数据做数据劫持,后添加的数据默认不会做数据劫持(修改数据的值不会同步到页面上),如下所示:
代码演示:
<body>
<div id="root">
<h2>名称:{{student.name}}</h2>
<!-- 引用的是初始化时不存在的属性 -->
<h2>性别:{{student.sex}}</h2>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
student:{
name: 'Jay'
}
}
})
</script>
运行结果:

控制台输出 vm._data
后发现,新添加的 sex 属性并没有做数据劫持(没有添加 getter、setter 方法),所以 _data
数据的变化并没有通过 setter 重新解析到页面上,如下图:

1.1.1 Vue.set()方法
该方法用来给后添加的属性做响应式处理(可以理解为做数据劫持,添加 getter、setter 方法)。
使用方式1:
Vue.set(要添加属性的对象,要添加的属性名/数组索引值,属性对应的值)
使用方式2:
vm.$set(要添加属性的对象,要添加的属性名/数组索引值,属性对应的值)
对上述运行结果做代码演示:

控制台输出 vm._data
后发现新增的 sex 属性被成功的添加 getter 和 setter 方法,后续通过 vm._data.student.sex = "女"
也可以修改页面的值(通过 setter 方法重新解析模板),如下图所示:

注意:Vue.set()
方法不可以直接用来给 根data 或 根vm实例 添加属性,即第一个参数不能写成 vm._data
或 vm
。
1.2 监视数组中的数据
如果 data 中的属性是一个数组,那么监视数组的变化首先需要数组本身发生变化,比如调用 sort、reverse
等方法,Vue 会对这些方法进行包装,自动的先将数组本身改变,然后重新解析模板,渲染新数组的数据。
可以使得数组本身发生变化的方法:
- push:在数组的最后添加一个元素
- pop:删除数组的最后一个元素
- shift:删除数组的第一个元素
- unshift:在数组的最前面添加一个元素
- splice:在数组的指定位置进行插入、删除、替换等操作
- sort:对数组进行排序
- reverse:反转数组
注意:
- 无法使数组本身发生变化的方法,比如
filter
方法,更改的数据想要在页面中展示出来,需要使用新数组替换旧数组。 Vue.set()
可以通过索引值给数组元素做响应式处理。
1.3 总结
代码演示:
要求各个按钮完成按钮文字的功能:
<body>
<div id="root">
<h3>个人信息</h3>
<button @click="student.age++">年龄+1岁</button> <br/>
<button @click="addSex">添加性别属性,默认值:男</button> <br/>
<button @click="addFriend">在列表首位添加一个朋友</button> <br/>
<button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button> <br/>
<button @click="addHobby">末尾添加一个爱好:学习</button> <br/>
<button @click="updateHobby">修改第二个爱好为:开车</button> <br/>
<button @click="removeSmoke">过滤掉爱好中的烫头</button> <br/>
<h3>姓名:{{student.name}}</h3>
<h3>年龄:{{student.age}}</h3>
<h3 v-if="student.sex">性别:{{student.sex}}</h3>
<h3>爱好:</h3>
<ul>
<li v-for="(h,index) in student.hobby" :key="index">
{{h}}
</li>
</ul>
<h3>朋友们:</h3>
<ul>
<li v-for="(f,index) in student.friends" :key="index">
{{f.name}}--{{f.age}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
student:{
name:'tom',
age:18,
hobby:['抽烟','喝酒','烫头'],
friends:[
{name:'jerry',age:35},
{name:'tony',age:36}
]
}
},
methods: {
addSex(){
this.$set(this.student,'sex','男')
},
addFriend(){
this.student.friends.unshift({name:'jack',age:70})
},
updateFirstFriendName(){
// 数组的元素是对象时,会给对象中的所有属性做数据劫持
this.student.friends[0].name = '张三'
},
addHobby(){
this.student.hobby.push('学习')
},
updateHobby(){
// this.student.hobby.splice(0,1,'开车')
// 通过Vue.set方法也可以修改数组的值
// Vue.set(this.student.hobby,1,'开车')
this.$set(this.student.hobby,1,'开车')
},
removeSmoke(){
this.student.hobby = this.student.hobby.filter((h)=>{
return h !== '烫头'
})
}
}
})
</script>
运行结果(未点击按钮):

运行结果(点击所有按钮):

运行结果(数据代理):

可以发现,对于自定义的数据,Vue 实例只会对最外层的 student
添加 getter 和 setter 方法(student 任意层次数据发生改变,都会导致 _data 中的数据发生改变),而不会对所有的深层属性都添加 getter 和 setter。
运行结果(数据劫持—hobby):

hobby
是一个数组,所以没有 setter 和 getter,如果想要页面的数据发生变化,则需要修改数组本身。
运行结果(数据劫持—friends):

friends
虽然是一个数组,但是其中的值是对象,Vue 会给对象中的属性做数据劫持,所以直接通过上述的代码 this.student.friends[0].name = '张三'
,也可修改页面的数据。
二、再谈v-model指令
2.1 收集表单数据
收集用户输入的数据可以通过 v-model
双向绑定,会分为以下几种情况:
- 若为
<input type="text"/>
等文本输入框类型,由于用户的输入默认赋值的就是value
属性,故v-model
收集的是用户直接输入的数据。 - 若为
<input type="radio"/>
单选框,则需要手动给该标签添加value
属性,v-model
收集的是该标签的value
属性值。 - 若为
<input type="checkbox"/>
多选框:- 没有配置 value 属性,那么
v-model
收集的是布尔类型的checked
属性(是否被选中)。 - 已经配置 value 属性:
v-model
绑定的data
属性的初始类型非数组,那么v-model
收集的是布尔类型的checked
属性(是否被选中)。v-model
绑定的data
属性的初始类型为数组,那么v-model
收集的是所有多选框的value
属性值所组成的数组。
- 没有配置 value 属性,那么
代码演示:
<body>
<div id="root">
<!-- 给表单绑定一个提交事件,并且阻止默认的跳转页面行为去执行demo函数 -->
<form @submit.prevent="demo">
账号:<input type="text" v-model="userInfo.account"> <br/><br/>
年龄:<input type="number" v-model="userInfo.age"> <br/><br/>
性别:
男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br/><br/>
爱好---data初始非数组:
<!-- data初始非数组 -->
学习1<input type="checkbox" v-model="userInfo.hobby2" value="study">
学习2<input type="checkbox" v-model="userInfo.hobby2" value="study">
<br/><br/>
爱好---data初始为数组:
<!-- data初始为数组 -->
学习<input type="checkbox" v-model="userInfo.hobby1" value="study">
打游戏<input type="checkbox" v-model="userInfo.hobby1" value="game">
吃饭<input type="checkbox" v-model="userInfo.hobby1" value="eat">
<br/><br/>
所属校区
<!-- select下拉框默认v-model获取的是value属性值 -->
<select v-model="userInfo.city">
<option value="">请选择校区</option>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="shenzhen">深圳</option>
<option value="wuhan">武汉</option>
</select>
<br/><br/>
其他信息:
<textarea v-model="userInfo.other"></textarea> <br/><br/>
<!-- 不需要获取value值,只需要判断布尔值即可 -->
<input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.baidu.com">《用户协议》</a>
<!-- 表单中的按钮会自动触发提交事件 -->
<button>提交</button>
</form>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
userInfo:{
account:'',
age:18, //默认填入18
sex:'female', //默认取值为女
hobby1:[], //初始化为数组
hobby2:'',
city:'beijing', //默认选中北京
other:'',
agree:''
}
},
methods: {
demo(){
alert(JSON.stringify(this.userInfo))
}
}
})
</script>
运行结果:

注意:
<input type="number"/>
标签, 可以控制输入框只能输入数字,并且自动添加增减数字选项,如图所示:- 上图中数字的取值为字符串,并非数字格式
2.2 三个修饰符
- lazy:输入框失去焦点后再收集数据,并非实时收集数据
- number:输入的字符串转为有效的数字
- trim:输入的数据首尾空格过滤
代码演示:
<body>
<div id="root">
<form>
账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>
其他信息:<textarea v-model.lazy="userInfo.other"></textarea> <br/><br/>
</form>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
userInfo:{
account:'',
age:'',
other:''
}
}
})
</script>
运行结果:

- 输入字符之前、之后的空格被自动删除,但是字符中的空格不会被删除
- 输入的数字转为数字格式而不是字符串
- 其他信息会全部输入并且失焦后才会获取到值
三、过滤器
定义:
过滤器(函数)用来对数据进行处理后再显示。
用法:
- 注册过滤器:
- 全局:
Vue.filter(过滤器名称,过滤器处理数据的函数)
- 局部:
new Vue{filters:{过滤器1名称(参数){处理过程},过滤器2...}}
- 全局:
- 使用过滤器:
- 插值语句:
{{要处理的数据 | 过滤器名称}}
- 单向绑定:
v-bind:属性 = "要处理的数据 | 过滤器名称"
- 插值语句:
处理过程:
- 要处理的数据自动的作为参数传递给过滤器函数,过滤器函数的返回值替换整个插值表达式或属性值。
- 过滤器是不需要传递实参的,自动的将要处理的数据作为参数传递给过滤器函数。
- 过滤器函数也可以接收额外的参数,但是第一个形参永远是要处理的数据。
代码演示:
<body>
<div id="root">
<!-- 过滤器函数不使用参数 -->
<h3>现在是:{{msg | Formater1}}</h3>
<!-- 过滤器函数传参 -->
<h3>现在是:{{msg | Formater2('一路向北')}}</h3>
</div>
<div id="root2">
<h2>{{msg | mySlice}}</h2>
</div>
</body>
<script type="text/javascript">
//全局过滤器,多个容器都可以使用
Vue.filter('mySlice',function(value){
return value.slice(0,4)
})
new Vue({
el:'#root',
data:{
msg:'Jay111'
},
//局部过滤器,仅供div标签为root的容器使用
filters:{
// 不传递实参默认value获取的是要处理的数据
Formater1(value){
return value.slice(0,3)
},
// 第一个参数永远是要处理的数据,第二个参数为传递的参数
Formater2(value, str){
return value + str
}
}
})
new Vue({
el:'#root2',
data:{
msg:'1111Hello'
}
})
</script>
运行结果:

注意:
-
过滤器并没有改变原本的数据,而是产生新的数据替换原位置的值。
-
多个过滤器可以串联使用,如下列代码所示:
{{msg | Formater1 | mySlice}}
,要被修改的数据msg
作为参数传递给Formater1
过滤器,Formater1
过滤器的返回值作为参数传递给mySlice
过滤器。
四、指令
4.1 内置指令
4.1.1 v-text
向其所在的标签渲染指定的文本内容。
注意:
- 与插值表达式的区别:
v-text
会替换标签中的所有内容,而插值表达式不会替换。 - 该指令仅将所有数据渲染为文本,不会解析标签。
代码演示:
<body>
<!-- 准备好一个容器-->
<div id="root">
<div>没有被替换,{{name}}</div>
<div v-text="name">会被覆盖掉哦</div>
<div v-text="str"></div>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
name:'Jay',
str:'<h3>你好啊!</h3>'
}
})
</script>
运行结果:

4.1.2 v-html
向其所在的标签渲染指定的文本内容,与 v-text
的不同是,会解析标签。
代码演示:
<body>
<div id="root">
<div v-html="str">被替换掉了哦</div>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
str:'<h3>你好啊!</h3>',
}
})
</script>
运行结果:

注意:由于 v-html
会解析标签,所以存在一定的安全隐患,容易导致 XSS 攻击。
4.1.3 v-cloak
v-cloak
没有属性值,Vue 实例在创建完毕并接管容器之后,会自动的删除所有 v-cloak
属性。
作用:
有时 JS 文件引入慢时,会出现 JS 阻塞现象,即 Vue 实例并不会立即创建,某些情况下页面上的标签会先展示出来,导致如插值表达式没有被解析就展示在了页面上。
使用 v-cloak
指令配合 css 样式的 display:none
属性,可以避免页面出现未经解析的模板。
代码演示:
思路:给包含 v-cloak
属性的所有标签使用 css 样式的 display:none
属性,Vue 实例没有被加载出来之前,样式被隐藏,不会展示在页面上。当 Vue 实例加载之后,v-cloak
属性消失,css 样式的 display:none
属性失效,成功将解析后的结果展示在页面上。
<head>
<meta charset="UTF-8" />
<title>v-cloak指令</title>
<!-- 隐藏属性的样式 -->
<style>
/* 中括号表示所有使用v-cloak属性的标签 */
[v-cloak]{
display:none;
}
</style>
</head>
<body>
<div id="root">
<h2 v-cloak>{{name}}</h2>
</div>
<!-- 先展示结果,后引入 JS 标签,假设由于网速该 JS 标签在5秒之后才会被引入,会导致页面加载未解析的插值表达式 -->
<script type="text/javascript" src="https://xxx.vue.js"></script>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
name:'Jay'
}
})
</script>
运行结果:
页面空白5秒之后,显示 Jay,不会出现未解析的模板。
4.1.4 v-once
v-once
没有属性值,v-once
所在的标签在初次动态渲染之后,就视为静态内容,之后的数据不会再改变。
代码演示:
<body>
<div id="root">
<h2 v-once>初始化的n值是:{{n}}</h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
n:1
}
})
</script>
运行结果:
点击了5次按钮:

4.1.5 v-pre
v-pre
没有属性值,v-pre
可以让其所在的标签跳过 Vue 的解析,如果使用在没有指令语法或插值表达式的标签中,可以加快编译过程。
代码演示:
<body>
<div id="root">
<h2 v-pre>当前的n值是:{{n}}</h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
n:1
}
})
</script>
运行结果:
点击了5次按钮:

4.2 自定义指令
Vue 除了内置指令之外支持使用者自定义指令,当调用自定义指令时完成某一操作,自定义指令需要使用 directives
关键字。
4.2.1 函数式
定义:
函数需要定义两个参数,参数1表示使用自定义指令所在标签的所有信息,参数2表示指令与绑定数据的一些信息。
<body>
<div id="root">
<h2>
<!-- 在需要使用的位置使用 v-函数名 即可完成big函数内的功能 -->
<span v-big="n"></span>
</h2>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
n:1
},
// 自定义指令
directives:{
big(element,binding){
console.log(element,binding)
element.innerText = binding.value * 10
}
}
})
</script>
运行结果:
页面会显示10,控制台输出:

注意:
- 自定义函数被调用的时间:
- 指令与标签第一次成功绑定时(绑定不代表会显示在页面上,绑定是内存操作)
- 指令所在的模板(div标签中的内容)被重新解析时
- 指令定义时不加
v-
,使用时要加v-
4.2.2 对象式
函数式的缺点是无法在指定的时间进行某一操作。
使用对象式的方式,需要在值中定义三个函数:
bind(element,binding){}
:指令与标签成功绑定时会自动调用inserted(element,binding){}
:指令所在的标签被插入页面时会自动调用update(element,binding){}
:指令所在的模板被重新解析时会自动调用
代码演示:
需求:输入框在页面初始展示时自动获取焦点,点击按钮之后,输入框内的值加一并获取焦点。
注意:不能使用函数式,因为函数式在第一次绑定成功(仅在内存中)就会调用函数获取焦点,这个时候页面上并没有输入框,所以无法获取焦点,必须要等到页面中有元素才可以获取焦点。
<body>
<div id="root">
<!-- 使用自定义指令v-fbind -->
<input type="text" v-fbind:value="n">
<button @click="n++">点我n+1</button>
</div>
</body>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
n:1
},
directives:{
fbind:{
//指令与标签成功绑定时会自动调用
bind(element,binding){
element.value = binding.value
},
//指令所在的标签被插入页面时会自动调用
inserted(element,binding){
//获取焦点
element.focus()
},
//指令所在的模板被重新解析时会自动调用
update(element,binding){
element.value = binding.value
element.focus()
}
}
}
})
</script>
4.2.3 全局指令
局部方式仅指定的 div 标签(容器)可以使用,全局指令可以让所有容器都可以使用。
代码演示:
// 对象式
Vue.directive('fbind',{
bind(element,binding){
element.value = binding.value
},
inserted(element,binding){
element.focus()
},
update(element,binding){
element.value = binding.value
}
})
//函数式
Vue.directive('fbind',function(element,binding) {
console.log(element,binding)
})
注意:
-
自定义指令的命名方式非驼峰式,而是不同的单词使用 ‘-’ 分隔开,比如:
//使用指令: v-big-number //定义指令: 'big-number'(element,binding){ console.log(element, binding) },
由于定义函数时,JS 简写形式无法识别 ‘-’,所以函数名必须使用加单引号的非简写形式。
-
不论函数式还是对象式,函数中的 this 均表示 window 非 vm(指令不需要Vue管理)