链接地址:
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 修改页面上的值(数据劫持)。
首先需要明确,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 重新解析到页面上,如下图:
该方法用来给后添加的属性做响应式处理(可以理解为做数据劫持,添加 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。
如果 data 中的属性是一个数组,那么监视数组的变化首先需要数组本身发生变化,比如调用 sort、reverse 等方法,Vue 会对这些方法进行包装,自动的先将数组本身改变,然后重新解析模板,渲染新数组的数据。
可以使得数组本身发生变化的方法:
注意:
filter 方法,更改的数据想要在页面中展示出来,需要使用新数组替换旧数组。Vue.set() 可以通过索引值给数组元素做响应式处理。代码演示:
要求各个按钮完成按钮文字的功能:
<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 双向绑定,会分为以下几种情况:
<input type="text"/> 等文本输入框类型,由于用户的输入默认赋值的就是 value 属性,故 v-model 收集的是用户直接输入的数据。<input type="radio"/> 单选框,则需要手动给该标签添加 value 属性,v-model 收集的是该标签的 value 属性值。<input type="checkbox"/> 多选框:
v-model 收集的是布尔类型的 checked 属性(是否被选中)。v-model 绑定的 data 属性的初始类型非数组,那么 v-model 收集的是布尔类型的 checked 属性(是否被选中)。v-model 绑定的 data 属性的初始类型为数组,那么 v-model 收集的是所有多选框的 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"/> 标签, 可以控制输入框只能输入数字,并且自动添加增减数字选项,如图所示:
代码演示:
<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 过滤器。
向其所在的标签渲染指定的文本内容。
注意:
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>
运行结果:
向其所在的标签渲染指定的文本内容,与 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 攻击。
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,不会出现未解析的模板。
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次按钮:
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次按钮:
Vue 除了内置指令之外支持使用者自定义指令,当调用自定义指令时完成某一操作,自定义指令需要使用 directives 关键字。
定义:
函数需要定义两个参数,参数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,控制台输出:
注意:
v-,使用时要加 v-函数式的缺点是无法在指定的时间进行某一操作。
使用对象式的方式,需要在值中定义三个函数:
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>
局部方式仅指定的 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管理)