前言
首先说明:要直接上手简单得很,看官网熟悉大概有哪些东西、怎么用的,然后简单练一下就可以做出程序来了,最多两天,无论Vue2还是Vue3,就都完全可以了,Vue3就是比Vue2多了一些东西而已,所以:要快速上手的以及说些三观不正的废话的,麻烦您滤过本篇博客,自行查看Vue官网即可。
基础篇
初识Vue
下载Vue.js,链接:https://cn.vuejs.org/v2/guide/installation.html 。 开发版和生产版就字面意思。
开发工具:VSCode。
-
VSCode中的插件和设置自行百度进行配置。搜索VSCode初始配置即可,然后后续的需要时百度进行配置即可。
-
给浏览器安装vuejs devtool工具,可以“魔法”上网的话直接google应用商店搜索vuejs即可(注意区分Vue2和Vue3),不能的话去阿里云盘链接: https://www.aliyundrive.com/s/JtHVq5SX3po 。 下载后浏览器扩展程序 -> 拖进去即可。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>初识vue.html</title>
<!-- 0、导入vue.js -->
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 1、定义一个容器 -->
<div id="app">
<h3>第一个vue程序,并使用插值表达式取得值为:{{name}} </h3>
</div>
</body>
<script>
// 去除浏览器控制台中的错误信息 可以尝试去掉这个,然后进入浏览器控制台,观察上面报的东西
Vue.config.productionTip = false;
// 2、初始化Vue容器
new Vue({
el: "#app",
data: {
name: "紫邪情",
age: "18"
}
})
</script>
</html>
小结
- Vue容器中的代码一样符合html规范,只不过是混入了一些Vue的特殊用法而已,这个容器中的代码被称为:Vue模板。
- Vue实例(new Vue处)和 Vue容器是一 一对应的,即:一个Vue实例只能对应一个Vue容器。
- 当然:真实开发中只有一个Vue实例,后续会见到。
{{xxx}}
中的xxx要写js表达式,{{}}
即为插值表达式,且xxx可以自动读取到data中的所有属性,一旦data中的数据发生改变,那么页面中使用该数据的地方一样发生改变。
js表达式 和 js代码的区分:
- js表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方。
a
a + b
test(2)
x === y ? 'a' : 'b'
- js代码:
if(){}
for(){}
.........
认识v-bind:数据单向绑定
<!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>vue之v-bind</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 常规写法:v-bind 绑定的是属性 -->
<a v-bind:href="url"></a>
<!--
多层嵌套取值:插值表达式 取的东西就是下面Vue实例new Vue({}) data中的东西,可以理解为在找寻集合中的元素
-->
<h3>{{person.name}}</h3>
<!-- 简写 -->
<h3 :mySex="person.sex">{{person.name}}</h3>
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el: "#app", // el指定的是为哪一个容器服务 值就是一个css中的选择器
data: { // data存储的就是数据,这些数据是供el指定的容器去用的
url: "https://www.cnblogs.com/zixq/",
person: {
name: "紫邪情",
sex: "女"
}
}
})
</script>
</html>
小结:Vue模板有两大类
- 插值语法,前面已经见到了,就是:
{{取data中保存的数据}}
。
- 功能:解析标签体的内容。
- 注意:
{{}}
中的东西必须是js表达式。
- 指令语法:
- 功能:解析标签( 包括属性、标签体内容、绑定事件...... )。
- 例子:
v-bind:herf = "xxxx"
或 简写为:herf = "xxxx"
。注意:xxx同样要写成js表达式。 - 另外:Vue中有很多指令语法,形式都是: v-xxxx。如:v-bind、v-model、v-if、v-for、v-on......,而且一些指令也都可以简写。
认识Vue的数据绑定
这里需要了解mvvm模式,要理解,可以先参照Java中的MVC模式,然后再了解MVVM,具体内容自行先百度了解一下。@紫邪情
<!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>v-model</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 单向绑定:绑的是属性 -->
单向绑定:<input type="text" :value="name">
<!-- 双向绑定:绑的是值 -->
双向绑定:<input type="text" :myName="name" v-model:value="username">
<!-- 双向绑定的简写 因为:model绑的就是值,因此:value可以省掉 -->
双向绑定:<input type="text" :myName="name" v-model="username">
</div>
</body>
<script>
Vue.config.productionTip = false;
new Vue({
el: "#app",
data: {
name: "紫邪情",
username: "邪公子"
}
})
</script>
</html>
测试单向绑定
测试双向绑定
数据绑定小结:Vue中数据绑定有两种方式
- 单向绑定(v-bind),数据只能从data流向页面。
- 双向绑定(v-model),数据不仅能从data流向页面,还可以从页面流向data,但需注意如下两点
- 双向绑定一般都应用在表单类元素上,如:input、select等。
v-model:value
可以简写为 v-model,因为:v-model默认收集的就是值。
el和data的两种写法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>vue的el和data的两种写法</title>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app"></div>
<script>
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
// el:'#app', // el 常规写法
// data:{}, // data 常规写法 这种也叫对象式写法
/*
data 函数式写法,这种写法后续经常见
这里使用 data:function(){} 也可以,但是:绝对不能写成 data:()=>{}
因为这种写法变质了,成为Window对象了,这样后续用this指向时,这个this所指代的就不是Vue实例了
*/
data () {
return {
}
}
});
// el 另外的写法 这种写法需要记住,后续组件化开发需要用到
vm.this.$mount('#app')
</script>
</body>
</html>
小结:el和data的两种写法
- el
- new Vue时配置el选项。
- 先创建Vue实例,然后再通过
vm.this.$mount( '#app' )
来指定el的值。
- data
- 对象式。
- 函数式。
- 如何选择哪种用法:目前都可以,但是后续会玩组件,则:必须用函数式,而且不可以用兰姆达表达式,即:上面的
data:()=>{}
,否则:会出错。
- 一个原则:由Vue管理的函数,一定不要写箭头函数,就上面的兰姆达表达式,否则就会导致this不再是Vue实例了。
- 这个this有大用处,在"new Vue"中用了this就是指的当前的Vue实例,后续可以通过这个this玩很多东西 。
理解MVVM模型
M 指:model。
V 指:view。
VM 指:ViewModel。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>理解MVVM</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<h2>姓名:{{name}}</h2> <br>
<h2>性别:{{sex}}</h2>
<hr>
<h2>智商:{{1+1}}</h2>
</div>
<script>
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
name: "紫邪情",
sex: "女"
},
});
console.log(vm);
</script>
</body>
</html>
查看代码中的MVVM
小结:MVVM模型
- M:Model(模型) ————> data中的数据。
- V:View(视图)—————> 模板代码。
- VM:ViewModel(视图模型) ————> Vue实例。
观察效果发现
- data中所有的属性最后都在vm身上,即:ViewModel。
- vm身上所有的属性 及 Vue原型上的所有属性,在Vue模板中都可以直接使用。原型就是下图中的这个。
回顾 Object.defineProperty() 函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>了解object.defineProperty()</title>
<script src="../js/vue.js"></script>
</head>
<body>
<script>
Vue.config.productionTip=false;
let sex = '女'
let person = {
name: '紫邪情'
}
/*
参数说明:
person 为要修改的对象
sex 为具体修改的对象中的哪个属性
{} 对修改属性的配置
*/
Object.defineProperty(person , 'sex' , {
// 以下就是{}中的相关配置
// value: '男',
// enumerable: true, // 这个sex是否可以被遍历
// writable: true, // 这个sex是否可以被修改
// configurable: true, // 这个sex是否可以被删除
// 当有人获取sex这个属性时会触发这个get()方法
get(){
console.log('有人读取sex属性了');
return sex;
},
// 当有人修改sex属性的值时会触发这个set方法
set(value){
console.log('有人修改了sex的值');
return sex = value;
}
});
</script>
</body>
</html>
注意:去找temp获取值是调用了
get()
方法进行获取的,相应地set()
方法也知道是怎么回事了。
简单理解数据代理原理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>简单理解数据代理</title>
</head>
<body>
<script>
let obj1 = {x:100};
let obj2 = {y:200};
// 数据代理: 通过一个对象代理 对 另一个对象中属性的操作( 读 / 写 )
Object.defineProperty(obj2 , 'x' , {
get(){
console.log("有人获取了x的值");
return obj1.x;
},
set(value){
console.log("有人修改了x的值");
obj1.x = value;
}
})
</script>
</body>
</html>
原理图:下图是简单了解,深入了解在后面玩Vue监视数据原理时会再次见到
defineProperty()
和这个数据代理。
小结:Vue中的数据代理
- 通过vm对象来代理data对象中的属性的操作( 读 / 写 )
- 好处:更加方便操作data中的数据
- 原理:
- 通过
object.defineProperty()
把data对象中所有属性添加到vm上 - 为每一个添加到vm上的属性,都指定一个 getter / setter
- 在getter / setter内部去操作( 读 / 写 )data中的属性
- 通过
事件绑定及其修饰符
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>事件绑定及其修饰符</title>
<script src="../js/vue.js"></script>
</head>
<!--
常用的事件修饰: 事件、事件,放在对应事件后就可以了,如:@click.prevent = "xxxx"
prevent 表示:阻止默认事件的触发
stop 表示:阻止事件冒泡
once 表示:只执行一次事件
-->
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<!-- vue中事件绑定的简单写法 -->
<button v-on:click="showInfo()">点我显示提示信息</button>
<!-- vue中事件绑定的简写 还可以传递参数 -->
<button @click="showInfo2($event, 66)">点我获取带参的事件信息</button>
</div>
<script>
Vue.config.productionTip=false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{},
methods: {
showInfo(){
alert("这是vue中的事件绑定");
},
showInfo2(event , number){
console.log(event + "=====>" + number);
}
}
});
</script>
</body>
</html>
小结:事件的使用
- 使用
v-on:xxxx
,或@xxx
来绑定事件,xxxx就是事件名。 - 事件的回调需要配置在methods对象中,最终会在vm上。
- methods中配置的函数,记得不要用兰姆达函数,否则this无效了。
- methods中配置的函数,都是被Vue所管理的函数,this指向的就是vm 或 组件实例对象。
@click = “demo”
和@click = “demo($event)
效果一致,但是:后者可以传参(后续有用)。
计算属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>计算属性conputed</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
姓名: <input type="text" v-model="name">
性别: <input type="text" v-model="sex">
<hr>
信息: <span>{{info}}</span>
</div>
<script>
Vue.config.productionTip=false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
name:'紫邪情',
sex:'女'
},
// 所谓的计算属性:就是通过已有属性( 一般为data中的 )计算得来
computed: {
info:{
get(){
console.log('开始调用get()');
return this.name + '-' + this.sex
},
set(value){
console.log('开始调用set()' + value);
// 计算属性要被修改,那必须写set()去响应修改,且set()中要引起计算时依赖的数据发生改变
const arr = value.split('-');
this.name = arr[0];
this.set = arr[1];
}
}
}
});
</script>
</body>
</html>
计算属性小结
- 定义:要用的属性不存在,要通过已有属性计算得来。
- 原理:借助了
object.defineproperty()
的getter和setter。 get()
什么时候执行?
- 初次读取时会执行一次。
- 当依赖的数据发生改变时会再次被调用。
- 优势:与methods相比,内部有缓存机制(复用),效率更高、调试方便。
- 注意:
- 1、计算属性最终都会在vm(Vue实例)上,直接读取即可。
- 2、若计算属性要被修改,那必须写
set()
去响应修改,且set()
中要引起计算时依赖的数据发生改变(例子中没有做这一步)。
- 另外,计算属性是可以简写的,就是把
set()
去掉,因为:更多时候是读取,并不修改,同时修改还要去控制台(刷新就又没了),因此:把set()
去掉就是计算属性的简写。
结合computed、methods、v-on做一个小Demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>使用计算属性做一个Demo</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<h2>你是:{{result}}</h2>
<button @click="changeResult">切换名字</button>
</div>
<script>
// 去除浏览器控制台中的错误信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
name: true
},
computed:{
result(){
return this.name?'紫邪情':'紫女'
}
},
methods: {
changeResult(){
return this.name = !this.name
}
},
});
</script>
</body>
</html>
但是:上面的代码其实是可以简化的
我们主要做的是图中的这一步,因此:简化 ———— 衍生出v-on的另外一个玩法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>使用计算属性做一个Demo 简化</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<h2>你是:{{result}}</h2>
<!--
@xxx = 'yyyy' 如果只是做一件简单的事情,那么就可以使用下面的方式
因为:v-on是可以支持js表达式的 注意:不是js代码
但是:如果这个v-on需要做多件事,那么最好就别这么玩
如:切换了名字,还要弄一个弹窗这种就别玩
-->
<button @click="name = !name">切换名字</button>
</div>
<script>
// 去除浏览器控制台中的错误信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
name: true
},
computed:{
result(){
return this.name?'紫邪情':'紫女'
}
},
// methods: {
// changeResult(){
// return this.name = !this.name
// }
// },
});
</script>
</body>
</html>
监视属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>监视属性 watch</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<h2>你是:{{result}}</h2>
<button @click= "name = !name">切换名字</button>
</div>
<script>
// 去除浏览器控制台中的错误信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
name: true
},
computed: {
result() {
return this.name?'紫邪情':'紫女'
}
},
/*
监视属性 watch 监视的是属性,也就是data中 和 computed中的都可以监视
现在这种我是用的简写形式 ———— 前提是:只需要使用 handler() 中的东西时,handler后续会用完整形式
*/
watch: {
name( newValue, oldVaule ){ // 表示的是:监视哪个属性
console.log('属性发生变化了' , newValue , oldVaule);
}
},
});
// 当然:监视属性还有一种写法,就是利用Vue的内置函数
/* vm.$watch( 'name', { // 这里的name就是指监听哪个属性 而且必须是' '引起来的
handler( newValue, oldVaule ){
console.log('属性发生变化了' , newValue , oldVaule);
}
}) */
</script>
</body>
</html>
小结:监视属性 watch
- 当被监视的属性变化时,回调函数自动调用,进行相关操作。
回调函数:指的是:handler()
,第一种写法也是可以用handler,只是把watch换成了$watch
而已,后面步骤其实是一样的,只是我把第一种给简写了。
- 监视的属性必须存在,才能进行监视。
- 监视有两种写法
- (1)、new Vue时传入watch配置。
- (2)、通过
vm.$watch
内置函数进行监视。
深度监视 - 需掌握
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>深度监视</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<h2>你是:{{result}}</h2>
<!-- 现在这个name就是person里面的了,这就是需要监视多层属性变化 -->
<button @click= "person.name = !person.name">切换名字</button>
</div>
<script>
// 去除浏览器控制台中的错误信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
person:{
name: true
}
},
computed: {
result(){
return this.person.name?'紫邪情':'紫女'
}
},
// 下面这种是监视属性的完整写法,前面玩的一个是简写形式
watch: {
person:{
// 监视多层结构中所有属性的变化
deep: true, // 深度监视 就做了这一步操作而已
/*
Vue中watch默认是不可以监视属性更里层的,
如:上面person只可以监视person本身这个属性,理解的话,
可以采用对象空间值来比对,假如:person空间地址是是01x23,
那么Vue只会监视这个空间变量有没有发生改变,而内层就不可以监视,
因为:内层中的属性改变了,但是person这个对象本身并没有改变
*/
handler(){
console.log('属性改变了');
}
}
}
});
</script>
</body>
</html>
小结:深度监视
-
Vue中的watch默认不监视对象内部值的改变(监视的是一层)。
-
配置
deep:true
可以监视对象内部值的改变(监视多层)。
注意:
- Vue自身可以监视对象内部值的改变,但Vue提供的watch默认不可以。
这个可以验证的,使用多层属性,最后是绑定在vm上的,那么在控制台使用vm去改变值,再去观察页面就可以发现效果了,这里是为了解释Vue不是不可以监视多层的改变,只是:watch默认不支持。
- 使用watch时根据数据的具体结构(属性是一层还是多层),决定是否采用深度监视。
computed 和 watch的坑
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>computed和watch的坑</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
姓: <input type="text" v-model="firstname">
名: <input type="text" v-model="lastname">
<hr/>
信息: {{fullname}}
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
firstname: '紫',
lastname: '邪情',
fullname: '紫邪情'
},
watch: {
firstname(val){
this.fullname = val + this.lastname;
},
lastname(val){
this.fullname = this.firstname + val;
}
}
});
</script>
</body>
</html>
用computed实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>computed实现</title>
<script src="../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
姓: <input type="text" v-model = "firstname">
名: <input type="text" v-model = "lastname">
<hr/>
信息: {{fullname}}
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
firstname: '紫',
lastname: '邪情'
},
computed: {
fullname(){
return this.firstname + this.lastname;
}
}
});
</script>
</body>
</html>
最后运行的效果都是一样的,而官网中对这二者有着这样的实例演示
但是:虽然看起来没区别,可是computed和watch还是有区别的。
假如:现在我需要让姓改了之后,隔1s之后再显示到信息那里。那么:computed无法做到,但是watch就可以。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>用watch实现异步操作</title>
<script src="../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
姓: <input type="text" v-model = "firstname">
名: <input type="text" v-model = "lastname">
<hr/>
信息: {{fullname}}
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
firstname: '紫',
lastname: '邪情',
fullname: '紫邪情'
},
watch: {
firstname(val){
// 一定需要注意:这里必须使用兰姆达表达式() =>{}
/*
前面说过,Vue所管理的函数,切记别用兰姆达表达式,只能用函数式
这是为了能够通过this拿到vm实例的东西而已
但是注意:这里这个setTimeout()定时函数是Vue所管理的吗?
不是,而后面需要的val是哪里的?是Vue实例中的,修改的值,是在Vue实例身上
所以:想要拿到Vue身上的val怎么弄?页面的展示都是js引擎帮忙去进行操作 / 找寻的
因此:利用js引擎做文章,让它找的时候自动去层级查找,它执行到里面的this.fullname = val + this.lastname时
会去找this是谁,()=>{}这里是用的兰姆达表达式,这就会指向Window对象,而Window上没有fullname,所以就会
往外找,找到firstname(val)这是函数式,指向的就是Vue实例,也就找到了fullname、val.....
*/
setTimeout( () => {
this.fullname = val + this.lastname;
}, 1000);
},
lastname(val){
this.fullname = this.firstname + val;
}
}
});
</script>
</body>
</html>
而利用computed就不可以做到上面的这种异步操作
小结:computed和watch的区别
- computed能完成的功能,watch绝对都可以做到。
- watch能完成的功能,computed不一定可以做到,如:上面举的例子进行异步操作。
原则:
- 凡是被Vue所管理的函数,最好都写成函数式( 即:普通函数),这是为了能够让this指向Vue实例 或 组件实例对象(后续会见到),这样就可以通过this拿到它们对应的东西。
- 凡是不被Vue所管理的函数(如:定时器里面的回调函数、ajax中的回调函数.... ),最好都写成兰姆达表达式(即:箭头函数),这样做也是为了能够让this指向Vue实例 或 组件实例对象。
样式绑定
class样式绑定
假如有如下的一个页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>class样式绑定</title>
<script src="../../js/vue.js"></script>
<style>
body {
background-image: url(img/bg3.jpg);
text-align: center;
background-size: 100% 100%;
height: 100%;
overflow: hidden;
background-repeat: no-repeat;
background-position: center;
background-attachment: fixed;
}
.top {
background: #ffffff2e;
width: 100%;
position: absolute;
bottom: 0;
line-height: 60px;
left: 0px;
right: 0px;
color: #fff;
text-align: center;
font-size: 16px;
font-weight: 600;
}
.basicLogin {
position: absolute;
top: 16%;
left: 28.5%;
width: 40%;
padding: 70px 2%;
text-align: center;
}
.title {
font-weight: 600;
font-size: 22px;
color: #0000FF;
margin-bottom: 40px;
}
.line {
border-bottom: 1px solid #ffff;
margin: 22px 1%;
width: 96%;
}
.line input {
border: none;
padding: 0px 1%;
margin: 1%;
background: #ffffff14;
width: 84%;
font-size: 16px;
line-height: 30px;
outline: none;
}
.line .smallImg {
width: 26px;
float: left;
vertical-align: middle;
margin-top: 1px;
}
.logBut {
background: #7bb5ee;
padding: 10px 80px;
border: none;
color: #fff;
margin-top: 40px;
font-size: 16px;
cursor:pointer;
}
</style>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<div class="top">©紫邪情 · 用Java改变未来</div>
<div class="basicLogin">
<div class="title">©紫邪情 · 登录</div>
<div class="line">
<img class="smallImg" src="img/icon-4.png" />
<input placeholder="请输入账号" type="text" />
</div>
<div class="line">
<img class="smallImg" src="img/icon-5.png" />
<input placeholder="请输入密码" type="password" />
</div>
<button type="button" class="logBut">登 录</button>
</div>
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
},
});
</script>
</body>
</html>
现在需求1:中间的登录页样式,不要这种,想要换一种:假如是如下这种
.login1 {
background: #ffffffd6;
border-radius: 2px;
}
可是,领导那个灾舅子滴看了效果觉得还是不行,觉得前面一种还行(不确定的样子),此时:增加样式
.login2 {
transform: translate(-50%,-50%);
background: rgba(0,0,0,.8);
box-sizing : border-box;
box-shadow: 0 15px 25px rgba(0,0,0,.5);
border-radius: 10px;
}
后面又说其实都可以,但是先把两种都留着,因此:此时这个class的名字不确定到底是哪一个,需要动态去绑定,所以通过Vue来动态绑定一下, 即:把class类名交给Vue托管。
class类名不确定,交由Vue托管很简单,而且知识点前面已经学过了,就是v-bind
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<div class="top">©紫邪情 · 用Java改变未来</div>
<div class="basicLogin" :class = "unknown" @click = "changeLogin">
<div class="title">©紫邪情 · 登录</div>
<div class="line">
<img class="smallImg" src="img/icon-4.png" />
<input placeholder="请输入账号" type="text" />
</div>
<div class="line">
<img class="smallImg" src="img/icon-5.png" />
<input placeholder="请输入密码" type="password" />
</div>
<button type="button" class="logBut">登 录</button>
</div>
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
unknown: 'login1'
},
methods: {
changeLogin(){
if( this.unknown === 'login1'){
this.unknown = 'login2'
}else{
this.unknown = 'login1'
}
}
}
});
</script>
</body>
需求2:现在class类名的个数、名字都不确定
那么就继续改造
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>数组写法</title>
<script src="../../../js/vue.js"></script>
<style>
body {
background-image: url(../img/bg3.jpg);
text-align: center;
background-size: 100% 100%;
height: 100%;
overflow: hidden;
background-repeat: no-repeat;
background-position: center;
background-attachment: fixed;
}
.top {
background: #ffffff2e;
width: 100%;
position: absolute;
bottom: 0;
line-height: 60px;
left: 0px;
right: 0px;
color: #fff;
text-align: center;
font-size: 16px;
font-weight: 600;
}
.basicLogin {
position: absolute;
top: 16%;
left: 28.5%;
width: 40%;
padding: 70px 2%;
text-align: center;
}
.login1 {
background: #ffffffd6;
border-radius: 2px;
}
.login2 {
background: rgba(0,0,0,.8);
box-sizing : border-box;
box-shadow: 0 15px 25px rgba(0,0,0,.5);
border-radius: 10px;
}
.title {
font-weight: 600;
font-size: 22px;
color: #0000FF;
margin-bottom: 40px;
}
.line {
border-bottom: 1px solid #ffff;
margin: 22px 1%;
width: 96%;
}
.line input {
border: none;
padding: 0px 1%;
margin: 1%;
background: #ffffff14;
width: 84%;
font-size: 16px;
line-height: 30px;
outline: none;
}
.line .smallImg {
width: 26px;
float: left;
vertical-align: middle;
margin-top: 1px;
}
.logBut {
background: #7bb5ee;
padding: 10px 80px;
border: none;
color: #fff;
margin-top: 40px;
font-size: 16px;
cursor:pointer;
}
</style>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<div class="top">©紫邪情 · 用Java改变未来</div>
<div class="basicLogin" :class = "unknown" @click = "changeLogin">
<div class="title">©紫邪情 · 登录</div>
<div class="line">
<img class="smallImg" src="../img/icon-4.png" />
<input placeholder="请输入账号" type="text" />
</div>
<div class="line">
<img class="smallImg" src="../img/icon-5.png" />
<input placeholder="请输入密码" type="password" />
</div>
<button type="button" class="logBut">登 录</button>
</div>
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
unknown: [ 'login1' , 'login2' ]
},
methods: {
changeLogin(){
const arr = [ 'login1' , 'login2' ];
this.unknown = arr[ Math.floor( Math.random() *2 ) ];
}
}
});
</script>
</body>
</html>
需求3:要绑定的class名字确定,但是:个数不确定,也需要动态绑定
小结:class样式绑定
- 字符串写法,适用于:class名字不确定时使用。
- 数组写法,适用于:class名字、个数都不确定时使用。
- 对象写法,适用于:class名字确定、但个数不确定,如:例子中的login1可能用,可能不用,而login2也是一样,所以这就是各种组合。
style行内样式绑定 - 了解
小结:class和style样式绑定
- class样式绑定
- 写法
:class = "xxxx"
,其中xxxx可以使字符串、数组、对象。 - 字符串写法适用于:类名不确定、要动态获取( 交由Vue管理,随时调试 )。
- 数组写法:要绑定多个样式、class名字和个数都不确定。
- 对象写法:要绑定多个样式、class名字确定而个数不确定( 个数不确定是因为不知道某个样式用不用 )。
- style样式绑定
- 写法
:style = “xxx"
,其中xxx可以是对象、数组。 - 对象写法是推荐用的一种。
- 原则:在Vue中静态不变的东西,就放在Vue模板中即可( 即:那个div容器中 ),而动态改变的东西就放在Vue实例身上。
条件渲染 v-if
v-if
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-if</title>
<script src="../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<!--
v-if和java中的if一样,只要结果为true就执行,为false就不执行
所以:这里面不用true,用 1 === 1 也行,当然:使用v-bind绑定,写到data中,只要最后结果为boolean值即可
相应地:有了v-if,那当然也有v-else-if、v-else
不过 v-else 这个有点特殊
这是什么条件都不满足的时候,最终执行的 类似于try....catch....finally中的finally
-->
<div v-if = "true">
<img :src="result" alt="不见了影响你开法拉利不?" :style="obj">
</div>
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
result: 'img/19.jpg',
obj: {
width: '500px',
hight: '500px'
}
},
});
</script>
</body>
</html>
注意:v-if、v-else-if、v-else组合用时,需要留意代码是紧挨着的即可。这就好比:Java的pageHelper分页插件,用startPage和pageInfo联合使用一样,数据查询完了之后放到pageInfo<>()中,这二者中间就不可以夹杂另外的语句,否则报错。
v-if配套工具template
注意事项:template“只能”与v-if搭配使用。
在弄这个之前,回到前面的v-if代码
所以:使用v-if和template改造
v-show
v-if会了,那么v-show就会了,用法和单纯的v-if一模一样。
不过它和v-if的原理不一样。
小结:v-if和v-show
- v-if
写法:
v-if = “表达式”
v-else-if = “表达式”
v-else = “表达式”
适用于:切换频率较低的场景,因为:v-if是直接把不展示的DOM元素给移除掉。
特点:不展示的DOM直接移除。
注意:v-if可以和v-else-if、v-else一起使用,但是:要求结构不可以被“打断”(代码紧挨着)。
- v-show
- 写法:
v-show = “表达式”
。 - 适用于:切换频率较高的场景。
- 特点:不展示的DOM元素未被移除,仅仅是使用
display:none
样式给隐藏掉了而已。
- 注意:使用v-if时,元素可能无法获取到,而使用v-show,则:一定可以获取到。
因为:v-show是做了样式修改,但是DOM节点还在,所以是可以操作这个DOM节点的,但是:v-if是直接把DOM元素给移除掉了,万一是误操作而导致把v-if的值弄为false了,那后续万一其他地方用到了v-if移除的节点呢?不就裂开了吗。
列表渲染 v-for
认识v-for
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>认识v-for</title>
<script src="../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<!-- 1、遍历数组 -->
<h2>人员信息</h2>
<!-- 简写形式 -->
<ul>
<!--
v-for就和js中的for in差不多
:key="p.id" 此处:是给每一个li节点绑定一个唯一的id
和身份证号一样 就相当于是<li id = ‘xxx’>,此时不写没事,但是最好都带上,后续有用
p 代表的就是:从persons中遍历出来的每一条数据
-->
<li v-for = "p in persons" :key="p.id">
{{p.name}} ----- {{p.age}}
</li>
</ul>
<!-- 完整写法 -->
<ul>
<li v-for = "(val,index) in persons" :key="val.id">
{{val.name}} ----- {{val.age}} ------ {{index}}
</li>
</ul>
<!-- 2、遍历对象 -->
<h2>神奇之地</h2>
<ul>
<li v-for = "(val,index) in like" :key="index">
{{val}} ------ {{index}}
</li>
</ul>
<!-- 另外:v-for还可以遍历字符串、某个数字(循环这个数字这么多次),但是这两种基本上都不用,所以不说明了 -->
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
persons: [
// 数组中又放得有对象
{'id':'10001','name':'紫邪情','age':'18'},
{'id':'10002','name':'紫女','age':'18'},
{'id':'10003','name':'邪公子','age':'19'}
],
like: {
name: '香牌坊',
ko:'范冰冰'
}
},
});
</script>
</body>
</html>
小结:v-for
- 语法:
v-for = “ ( intem,index) in xxx ”:key = “yyy”
。 - 可遍历:数组、对象、字符串(用得少)、指定次数(用得更少)。
- 可用于:展示列表数据。
key值的坑(作用与原理 )
在前面v-for中key值使用index时有一些坑的。
正确的做法是:最好使用数据的id作为key值,但不是说index作为key值就是错的,前面演示过了,是正常效果,可是:在特定的场景下使用
:key = "index"
就会出问题。@紫邪情
实例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>key值的坑</title>
<script src="../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<h2>人员信息</h2>
<ul>
<!-- 这里使用index作为key值 -->
<li v-for = "(p , index) in persons" :key="index">
{{p.name}} ----- {{p.age}} <input type="text">
</li>
</ul>
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
persons:[
{'id':'001' , 'name':'张三','age':'18'},
{'id':'002' , 'name':'李四','age':'19'},
{'id':'003' , 'name':'王五','age':'20'}
]
},
});
</script>
</body>
</html>
现在需要在做一件事情
为什么会发生上面的问题?那就需要看看原理图,从而了解用index做key值的坑了。
了解了上面的index作为key值,那么用数据id作为key值也懂了
不写key的话,Vue做了一件默认的操作,就是把遍历的index作为了key值,也就是和前面使用index作为key值是一样的效果了。
把以上的内容,换成文字小结一波:Vue中v-for的key有什么作用(原理是什么?)
- 虚拟DOM中key的作用
- key是虚拟DOM对象的标识,当状态中的数据发生改变时,Vue会根据【 新数据 】生成【 新的虚拟DOM 】。
- 随后Vue进行【 新虚拟DOM 】 与【 旧虚拟DOM 】的差异比较(对比算法 / diff算法)。
- 对比算法的规则
- 1)、旧虚拟DOM中找到了与新虚拟DOM相同的key。
- 若虚拟DOM中内容没变,则:直接使用之前的真实DOM;
- 若虚拟DOM中内容变了,则:生成新的真实DOM,随后替换掉页面中之前的真实DOM。
- 2)、旧虚拟DOM中未找到与新虚拟DOM相同的key。
- 直接创建新的真实DOM,虽然渲染到页面。
- 用index作为key值可能会引发的问题
- 1)、若对数据进行:逆序添加、逆序删除.....等破坏数据顺序的操作,则:会产生不必要的真实DOM更新,即:界面显示没问题,但:效率低(效率低是因为:最后生成真实DOM时,文本节点是重新生成的)。
所谓的逆序操作就是数据添加在前面,如:例子中是添加在“张三”前面的,但是:在最后添加( 即:在例子中“王五”后面添加数据,这种在最后面添加,则:在新虚拟DOM中生成真实DOM时,顺序是对的,不会出现上述问题的 。
- 2)、若结构中还包含输入类(如:例子中的input)的DOM,则:会产生错误DOM更新 即:页面中数据错位。
- 开发中如何选择key?
- 1)、最好使用数据自己的唯一标识作为key值,比如:手机号、身份证号.....(直接利用数据库中的主键id也行)。
- 2)、如果不存在对数据逆序添加、逆序删除等破坏数据顺序的操作,只用来渲染列表页面,则:使用index作为key是莫问题的。
列表过滤(查询功能 )
- 使用watch实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>列表过滤( 查询功能 )</title>
<script src="../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<H2>人员信息列表</H2>
搜索:<input type="text" placeholder="请输入搜索词" v-model="keyWord">
<ul>
<li v-for = " p in filterPersons" :key="p.id">
{{p.name}} -------- {{p.age}}
</li>
</ul>
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
// 1、搜集搜索框中的内容 搜索框内容变,这里的内容也要变,所以:双向绑定v-model
keyWord: '',
// 2、准备初始数据
persons: [
{'id':'10001','name':'紫邪情','age':'18'},
{'id':'10002','name':'紫邪晴','age':'19'},
{'id':'10003','name':'邪公子','age':'20'},
{'id':'10004','name':'公孙策','age':'21'},
{'id':'10005','name':'孙悟空','age':'22'}
],
// 4、需要准备另一个容器来装过滤之后的数据
filterPersons: []
},
// 3、使用watch来实现搜索效果
/*
需要做两件事
(1)、拿到data的keyWord中的内容
(2)、使用keyWod中的内容 去 persons中进行过滤筛选,从而把结果渲染到页面
*/
watch: {
keyWord: {
// 这个的作用:页面初始化时就执行一次handler()
immediate: true,
handler(newVal){
// 1)、过滤数据 把结果放到filterPersons中去
this.filterPersons = this.persons.filter( (p)=>{
// 没在数组中indexOf()返回的就是-1
return p.name.indexOf(newVal) !== -1
})
}
}
}
});
</script>
</body>
</html>
- 使用computed实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>列表过滤( 查询功能 )</title>
<script src="../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<H2>人员信息列表</H2>
搜索:<input type="text" placeholder="请输入搜索词" v-model="keyWord">
<ul>
<li v-for = " p in filterPersons" :key="p.id">
{{p.name}} -------- {{p.age}}
</li>
</ul>
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
// 1、搜集搜索框中的内容 搜索框内容变,这里的内容也要变,所以:双向绑定v-model
keyWord: '',
// 2、准备初始数据
persons: [
{'id':'10001','name':'紫邪情','age':'18'},
{'id':'10002','name':'紫邪晴','age':'19'},
{'id':'10003','name':'邪公子','age':'20'},
{'id':'10004','name':'公孙策','age':'21'},
{'id':'10005','name':'孙悟空','age':'22'}
],
},
// 3、使用computed来实现搜索效果
/*
需要做两件事
(1)、拿到data的keyWord中的内容
(2)、使用keyWod中的内容 去 persons中进行过滤筛选,从而把结果渲染到页面
*/
computed: {
filterPersons() {
return this.persons.filter( (p)=>{
return p.name.indexOf( this.keyWord ) !== -1
})
}
}
});
</script>
</body>
</html>
列表排序
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>列表排序</title>
<script src="../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<h2>人员信息</h2>
搜索: <input type="text" v-model = "keyWoed" placeholder="请输入关键字">
<button @click = "sortType = 1">升序</button>
<button @click = "sortType = 2">降序</button>
<button @click = "sortType = 0">原顺序</button>
<ul>
<li v-for = " p in filterPersons" :key="p.id">
{{p.name}} ----- {{p.age}}
</li>
</ul>
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
keyWoed: '',
sortType: 0,
persons: [
{'id':'10001','name':'紫邪情','age':'18'},
{'id':'10002','name':'紫邪晴','age':'14'},
{'id':'10003','name':'邪公子','age':'121'},
{'id':'10004','name':'公孙策','age':'21'},
{'id':'10005','name':'孙悟空','age':'42'}
]
},
computed: {
filterPersons(){
/*
排序:就是将列表过滤之后的数据 根据特定字段排序之后,再渲染到页面中即可
因此:return this.persons.filter这里不能直接用return,不然就直接把过滤的数据渲染到页面了
所以:再对这一步做一下排序操作
*/
// 先用个数组装起来
const arr = this.persons.filter( (p)=>{
return p.name.indexOf( this.keyWoed ) !== -1;
})
// 然后对数组进行排序操作,但是得先知道用户是点击的哪个按钮( sortType )
if (this.sortType) {
arr.sort( (previous,last)=>{
return this.sortType == 1 ? previous.age - last.age : last.age - previous.age;
})
}
return arr;
}
}
});
</script>
</body>
</html>
有个注意的地方:
Vue监视数据的原理
监视数据失效实例
先来看看Vue检测属性失效的问题,从而引申出Vue监视数据的原理。
先来玩正常版
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>监视数据失效实例</title>
<script src="../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<h2>Vue监视数据失效问题</h2>
<ul>
<li v-for = "p in persons" :key="p.id">
{{p.name}} ---- {{p.age}}
</li>
</ul>
<button @click = "update">修改数据</button>
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
persons: [
{'id':'10001','name':'紫邪情','age':'18'}
]
},
methods: {
update(){
this.persons[0].name = '紫女';
}
}
});
</script>
</body>
</html>
把代码稍微变一下,整出bug
上面的问题是怎么回事?要知道这个的原因就需要了解一下Vue监视数据的原理了,可是装数据的类型有什么?对象 { }
和 数组 [ ]
,所以:这里又得去了解这两种类型的原理,当然:像什么data中还可以放字符串这就不用说明了,了解了对象和数组,字符串也就可以反应过来了。
Vue监视对象的原理
玩这个就需要回到前面玩过的“数据代理”了。
前面就说过,这个数据代理,只是简单了解而已,上面的流程其实不算对,因为少了一个步骤,整个流程应该是:
- 自己写的data。
- 对data进行加工。
- 把data中的数据给
_data
。 - 再把
_data
的内容给页面需要的data中。即:这一步相当于执行了vm._data = data
, 这个data就是页面上的data嘛,所以就成功地让页面中的数据跟着自己的修改而改变了,但是底层不是用vm._data = data
,方法名不一样,而且还要更复杂一点。
来回顾一下例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>回顾数据代理</title>
<script src="../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<h2> 姓名: {{person.name}} </h2>
<h2> 性别: {{person.sex}} </h2>
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
person: {
name: '紫邪情',
sex: '女'
}
},
});
</script>
</body>
</html>
根据上面的原理,简单模拟一个vue的数据监视。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>模拟Vue数据监视</title>
</head>
<body>
<script>
// 准备一个对象
let data = {
name: '紫邪情',
sex: '女'
}
// vue做了精妙之一就是:设计了一个构造方法Observer(传对象名),这个东西刚刚在_data中见过它,打开就是用Observer打开的
function Observer(obj) {
// 拿到data中的所有key值( 即:上面的name、sex ),放到一个数组里面去
const keys = Object.keys( obj );
// 遍历这些key值,得到每一个key
keys.forEach( (key) => {
// 使用defineProperty()做数据代理 这里的this就是指:传的对象
Object.defineProperty( this, key , {
get(){
return obj[key];
},
set(val){
console.log( "值被修改了,要开始解析数据、生成虚拟DOM、进行对比算法、响应数据了");
obj[key] = val;
}
})
});
}
// 调用设计的构造方法,从而开始做 data 转 _data
const objs = new Observer(data);
let vm = {};
vm._data = data = objs
console.log( objs );
</script>
</body>
</html>
当然:上面只是简单流程,甚至多层对象的data加工,一个对象中又有另一个对象 / 数组,这样递归下去,所以Vue做得完善一点就是再对对象进行了递归,直到不是对象了为止,同时:这个例子也没有弄“数据修改再次解析模板、生成虚拟DOM、响应数据的事 ,研究得明明白白,全整得出来的话,我还坐起,早飘了!!!。
了解 Vue.set( target ,key , val )
实例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>了解Vue.set( target, key, val )</title>
<script src="../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<h2> 姓名: {{person.name}} </h2>
<h2> 性别: {{person.sex}} </h2>
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
person:{
name: '紫邪情',
sex: '女'
}
},
});
</script>
</body>
</html>
现在需要添加一个属性 age: 18
,不可以直接添加在data中,这样就没意义了。
当然:还有一种方式也可以实现
上面这是正常情况,都说了target这个参数有坑,官网有介绍:
就来玩一下这个坑吧
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>target参数的坑</title>
<script src="../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<h2> 姓名: {{name}}</h2>
<h2> 性别: {{sex}}</h2>
<h2 v-if = "age"> 年龄: {{age}}</h2>
<button @click = "addAge">添加年龄属性</button>
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
// 把这里的属性换一下位置,不用person{}对象包起来,直接放到data中
name: '紫邪情',
sex: '女'
},
methods: {
addAge(){
this.$set( this.data, 'age', 18);
}
}
});
</script>
</body>
</html>
这就是target这个参数的坑:target不可以直接指向vm这个实例对象,更不可以把要操作的对象指向data这个根数据对象上。
Vue监视数组数据的原理
写个正常例子,去控制台看数组的结构
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>正常实例 - 控制台查看结构</title>
<script src="../../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<h2>爱好</h2>
<ul>
<li v-for = "(h , index) in hobby" :key="index">
<h2> {{h}} </h2>
</li>
</ul>
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
hobby: [ '吃', '喝', '嫖', '赌', '要不得']
},
});
</script>
</body>
</html>
那么我们在控制台把数据改了呢?会不会响应到页面上?
上面这个问题就和前面一开始的问题一样了:监视数据失效的问题
这二者都是使用的"索引"来找的数组的具体某个值,而这:恰恰就是一个坑,想一下在Vue中我们操作数组是怎么做的?是通过数组调了对应的API,如:arr.push()
、arr.shift()
........,那Vue怎么知道我调的是哪个API,换句话说:它怎么知道这个API是做什么的 / 这个API是数组中的,要搞这个问题,就要看看在js中是怎么去调数组的API的?
试验一下:这7个API是否可以做到监视数据的效果
答案肯定不是
而上面探索的结果,在Vue官网说的有
所以:Vue中要监视到数组的变化,调用下面的7个API中的一个就可以
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
一定是上面的7个API中的一个才可以吗?
- 答案也肯定不是,前面玩了
Vue.set( target, key, val )
和vm.$set( target, key, val )
也可以做到啊,只需要把target转成对应的对象即可(只要找他数组中要监视到的对象就行嘛)。
Vue监视数据原理总结
- vue会监视data中所有层级的数据。
- Vue如何监视对象中的数据?
- 通过setter实现监视,且要在new Vue时就传入要监视的数据。
- 1)、对象中后追加的属性,Vue默认不做响应式处理。
- 2)、如果需要对后添加的属性做响应式,则:通过如下API就可做到
vue.set( target, key, val ) 说明一下:其中的key可以是属性名,也可以是数组的下标值
vm.$set( target, key, val ) key和上面一样
- Vue如何监视数组中的数据?
- 通过封装 / 包裹数组更新元素的方法来实现,这个封装的方法做了两件事
- 1)、调用数组原生的同名API,对数组进行更新;
- 2)、重新解析数据、生成新的虚拟DOM、进行对比算法、响应页面。
- 在Vue中要修改数组中的某个元素时,只能使用如下的方法
1)、push()、pop()、shift()、unshift()、splice()、sort()、reverse()
2)、vue.set( target, key, val ) 或 vm.$set( target, key, val )
注意:
vue.set( target, key, val )
和vm.$set( target, key, val )
不能给vm(即:vue实例) 或 根数据对象data 进行添加属性操作。
- 前面分析对象的原理时说过一个流程:数据发生改变,是通过setter来获取改变的数据,从而重新解析模板、生成新的虚拟DOM、进行对比算法、响应页面。在这里面有一个重要的流程:
- 1)、数据改变、setter获取到改变后的值;
- 2)、setter重新解析模板,这里1 ——> 2这一步有个专业的词叫做:数据劫持,劫持劫持嘛,半路把东西拿走了呗(就是setter把数据劫走了,它去做后续的操作)。
input输入框中v-model的使用技巧
- text类型
- radio单选框类型
原因:
- 1、v-model收集的是value值。
- 2、而radio是选择,是使用checked = true / false来做到是否选中的。
- 3、因此:给此种radio类型的input添加一个value属性即可。
- checkBox多选框类型
- select与option下拉框类型
- textarea类型
- checkBox的另一种类型:做协议勾选的
- 做了上面这些就可以把数据收集起来了
- v-model的修饰符
- 前面说过事件的修饰符。
prevent 表示:阻止默认事件的触发
stop 表示:阻止事件冒泡
once 表示:只执行一次事件
- 而v-model也有修饰符,就3个而已:number、lazy、trim,看名字大概就知道是干嘛的。
1)、number修饰符,这个修饰符一般都是和input的number类型一起使用的,如:增加一个年龄。
去控制台查看,会发现一个问题。
因此:需要把number弄为number类型。
2)、lazy修饰符,这个修饰符就光标离开之后再收集数据,拿textarea来举例。
所以这种情况应该用lazy来修饰,让光标离开当前输入框之后再收集数据。
3)、trim修饰符,这个前后端都知道,就是去除前后的空格,不演示了。
小结:使用v-model收集表单数据
- 若:
< input type = "text" />
,则:v-model收集的是value值,而用户输入的就是value值。 - 若:
< input type = "radio" />
,则:v-model收集的是value值,且要给标签配置value属性。 - 若:
< input type = "checkBox" />
- 1)、没配置input的value属性,那么收集的就是checked(true / false,是否勾选)。
- 2)、配置input的value属性:
- (1)、v-model的初始值是非数组,那么收集的就是checked。
- (2)、v-model的初始值是数组,那么收集的就是value组成的数组。
- v-model的3个修饰符
number 将输入的字符串转为 有效的数字
lazy 失去光标焦点才收集数据
trim 去掉首尾的空格
过滤器filters
这里有一个过滤器的知识,使用computed、methods也可以实现过滤器的东西,比如:把时间戳弄成规定的格式,所以不了解也罢,需要时再看都可以,就是另类的函数、而且还支持多个过滤器一起用、以及配置局部和全局的过滤器罢了。
Vue内置指令
回顾一下:已经学了Vue的哪些内置指令。
v-on
v-bind
v-model
v-if 、v-else-if、v-else
v-show
另外还有哪些内置指令?
v-text
v-html
v-cloak
v-once
v-pre
以及可以自定义指令
v-text指令
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-text指令</title>
<script src="../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<!-- 插值表达式 -->
<h2> {{name}}</h2>
<!-- v-text指令
这和java中的springboot推荐的thymeleaf模板中的th:text很像
-->
<h2 v-text="name"></h2>
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
name: '紫邪情'
},
});
</script>
</body>
</html>
但是:v-text和插值表达式有区别
插值表达式和v-text指令的最大区别,v-text是将v-text的值去找寻之后,把内容回填到对应的标签里面,是内容全覆盖,所以:例子中就算写了“信息*两个字,但是:也会被v-text值的返回内容给全覆盖了。
当然:还有一个注意点
v-text和插值表达式都不可以解析html标签。
小结:v-text内置指令
- 作用:向其所在的节点中渲染“文本”内容
- 与插值表达式的区别:v-text会替换掉节点中的所有内容,而{{xxx}}插值表达式则不会
v-html指令 - 慎用
先说用法,其实和v-text差不多,但是内在区别很大
用法简单,但是:谨慎用就体现在可以解析html标签上,不安全:
- 这里涉及到cookie的机制。
- 去访问一个需要输入信息的网站时(如:要输入用户名、密码之类的重要信息),信息在你要登录的网站的服务器上核对成功之后,服务器返回的除了会把用户名、密码这些后续需要的东西返回回来之外,还会返回一些服务器响应的特殊json字符串,从而凭借服务器返回的这些信息,你才可以进入到相应的页面,而这些信息就保存在浏览器的cookie中(实现机制不一样保存不一样),如下图:
- 而服务器返回的json信息有大用处,你的重要信息就保存在cookie中,比如:成功登录之后,操作其他的功能就不会要求再次输入用户名、密码之类的(个别除外,那是另外设计机制),这里就会想到另一个东西:跨浏览器不会出现无账号密码就可以访问,比如:我在google浏览器登录之后,再用Edge浏览器打开,那Edge是不会登录的,但是:利用v-html就可以做到窃取你在google浏览器中登录之后服务器给你返回的json信息,然后把这些json信息放到Edge浏览器中,那么Edge浏览器就不需要做登录操作,一样可以登录进去。
- 而使用v-html能够获取到cookie是因为:可以在用v-html获取的内容中夹杂一些获取你当前服务器的cookie,然后拼接到窃取人的服务器地址的后面(比如:网页中的恶意广告,那广告词中的词条是通过v-html渲染上去的,那么一点击就会把你电脑上的cookie获取到然后转发到他自己的服务器上去)。
- 当然:现在好一点的浏览器都为了防止xss攻击(模拟用户之手),所以:对浏览器做了一些安全限定,最典型的就是google浏览器,默认是不可以携带cookie转发的,但是有些做的不好的浏览器(网站)就可能没弄这些,一点然后跳过去就遭殃了。
小结:v-html
- 作用:向指定节点中渲染包含html结构的内容。
- 与插值表达式的区别
- 1)、v-html会替换掉节点中所有的内容,而插值表达式{{xx}}则不会。
- 2)、v-html可以识别html结构。
- 特别注意:v-html有安全性问题
- 1)、在网站上动态渲染任意html是非常危险的,容易导致XSS攻击。
- 2)、一定要在可信的内容上使用v-html,此指令永远不要用在用户提交的内容上!!!!!!。
v-cloak指令
这是专门用来处理页面渲染时因网络或其他原因导致页面无法及时刷新出来,把页面加载过程给显露出来了(等到后续懂了Vue的生命周期时,此命令会更容易懂 ,当然:现在也很容易懂)。@紫邪情
实例:把加载js的代码移动一个位置。
然后把浏览器的网速调一下。
然后重新刷新页面,会发现页面的name会有一个稍微慢一点的页面加载过程,是从 {{name}} ——————> 紫邪情,这里不方便演示,就不弄了。
而为了解决这个问题,可以使用v-cloak指令,本质是操作CSS罢了,
小结:v-cloak指令
- 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
- 使用CSS配合v-cloak指令可以解决网速慢时页面展示出
{{xxx}}
插值表达式 / axios交互延迟的问题。
axios就是ajax,Vue本身并不支持交互,所以才需要借助axios插件来进行交互,这也是玩了Vue之后必须掌握的一个知识点,对于学Java的人来说太简单了,上手的话,看一眼就懂了,那就是一个链式调用罢了,和StringBuilder一样链式调,只是需要传一些东西进去而已,而传的东西在ajax中都见过。
当然:只学了Vue,然后使用ajax一样可以进行数据发送,只是axios更轻小而已。
v-once指令
因此:使用v-once实现效果。
小结:v-once指令
- v-once所在节点在初次动态渲染后,就视为静态内容了。
- 以后数据的改变不会引起v-once所在结构的更新,可以用来做性能优化。
- 注意:和事件修饰符中的once区别开
- 事件中的once修饰符是说的事件( 函数 / js表达式)只执行一次。
- 而v-once指令是说的对div容器中的模板只动态渲染一次。
v-pre指令
小结:v-pre
- v-pre是跳过其所在节点的编译过程,也就是:原本是什么样子,那么Vue就拿到的是什么样子,不会再去解析它。
- 可利用它跳过:没有使用指令语法、没有使用插值表达式的节点,从而加快编译,优化性能。
自定义指令
函数式定义
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>函数式定义</title>
<script src="../../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<h2>原来的数值为: {{number}}</h2>
<hr/>
<!-- 使用自定义指令 -->
<h2> 扩大10倍之后的数值: <span v-big = "number"/> </h2>
<hr/>
<button @click = "number ++"> <h4>number+1</h4></button>
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
number: 10
},
// 设置自定义指令 - 函数式
// directives里面是可以写N多个自定义指令的,加了s嘛
directives: {
/*
element 是自定义指令所在结构的HTML标签元素( 节点 )
binding 是模板中自定义指令和HTML元素进行的绑定的信息,在这个绑定中有一些需要的东西
注:此绑定和v-bind中的绑定不是一个概念
v-bind是指的HTML元素内的"属性"和值的绑定
*/
big(element , binding){
element.innerText = binding.value * 10
}
}
});
</script>
</body>
</html>
注意点:上面自定义的指令什么时候会被调用?
- 毫无疑问,HTML元素和自定义指令成功绑定时,自定义指令就会被调用,注意:这句话有坑,待会儿下一种自定义方式就会弄到这句话。
看到的效果就是初始化页面一上来就调用了一次,但是里面的门道不是这样的,成功绑定是什么意思,下一种自定义指令再说明。
- 自定义指令所在的模板( Div容器 )被重新解析时会被调用。
别混淆了此种说法:自定义指令所依赖的数据发生改变时就会调用自定义指令,此种说法是错的。来个实例
对象式定义
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>对象式定义</title>
<script src="../../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<!-- 现在做一件事情:模拟lable for标签效果 就是:光标位置锁定 别在意设计好不好-->
<h2>计数: {{count}}</h2>
<button @click = "count++">地址: </button>
<input type="text" v-findfocus:value= "address">
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
count: 1,
address: '中国大陆'
},
directives: {
// 先用函数式来玩一下 注意:自定义指令 多个单词别整驼峰命名啊,识别不了的
findfocus( node, binding){
console.log(node , binding);
node.value = binding.value;
// 获取焦点
node.focus();
}
}
});
</script>
</body>
</html>
现在再加一点要求:就是页面一初始化时,焦点就在input框中,此时发现貌似做不到(用js可以做到啊,只是现在是用Vue)。
为什么Vue不行?因为这要考虑到Vue的加载顺序问题(重点咯,开始衍生出Vue的生命周期和钩子函数咯,也就可以很容器理解前面使用v-cloak解决页面闪烁的问题了)。
所以:应该把 focus()
获取焦点,放到页面渲染时再做,不然:无法聚焦。
相应的,用函数式并不能做到,因为:函数式直接一条龙在内存执行完了,所以:需要使用对象式自定义指令来解决这种小细节问题。
update()
中不写逻辑目前是不报错的,但是:最好还是写上。
自定义指令的两大坑 和 补充
- 命名的坑: 这个坑在前面玩对象式时已经说过了,多个单词的名字别采用驼峰命名,但是:只说了这一点,并没有说正确形式应该怎么命名。
现在采用驼峰命名看一下这个坑:
多个单词组成的指令名正确形式,采用 - 分隔开,指令定义中回归js原始配置
- this指向的坑
为什么自定义指令中的this是window对象?
这是因为:自定义指令已经不是Vue自己本身的东西了,而是根据需要自行设计,因此:此时你要操作的是HTML的DOM节点,因此:为了方便,Vue就把this还回来了,重新给了window。
this变了,那想要拿到Vue中的数据咋办?
- 补充:自定义全局指令
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>全局指令配置</title>
<script src="../../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<h2>计数: {{count}}</h2>
<button @click = "count++">地址: </button>
<!-- 正确形式命名 采用 - 分隔开 -->
<input type="text" v-find-focus:value= "address">
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 定义全局指令 - 对象式
/*
第一个参数 指令名
第二个参数 配置项( 就是把前面的对象配置{}这个花括号整体搬过来了而已 ),函数式也是一样的
只不过函数式需要使用 function给包裹起来而已
*/
Vue.directive('find-focus',{
// 1、HTML元素和指令进行绑定时执行的函数
bind(node, binding){
node.value = binding.value;
},
// 2、指令所在元素被插入被解析完毕,插入到页面后执行的函数
inserted(node, binding){
node.focus();
},
// 3、指令所在模板被重新解析时执行的函数
update(node, binding){
node.value = binding.value;
}
})
/* // 函数式全局指令
Vue.directive('find-focus', function(){
// 1、HTML元素和指令进行绑定时执行的函数
bind(node, binding){
node.value = binding.value;
},
// 2、指令所在元素被插入被解析完毕,插入到页面后执行的函数
inserted(node, binding){
node.focus();
},
// 3、指令所在模板被重新解析时执行的函数
update(node, binding){
node.value = binding.value;
}
})
*/
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
count: 1,
address: '中国大陆'
},
});
</script>
</body>
</html>
创建了全局指令之后,就可以摆脱只能在一个div容器中使用前面那种局部指令的缺陷了,也就是:现在可以再开一个div容器,然后在这个新开的div容器中一样可以使用全局配置中的东西。
全局配置东西基本上都是这么玩的,后续玩组件会用到。
自定义指令总结
- 局部指令
对象式
new Vue({
directives: {
指令名: {
配置
}
}
})
函数式
new Vue({
directives: {
指令名(){
配置
}
}
})
- 全局配置
对象式
Vue.directive( '指令名',{
配置
})
函数式
Vue.directive('指令名', function(){
配置
})
- 配置中常用的3个回调
bind( element, binding ) 指令与元素成功绑定时调用
inserted( element, binding ) 指令所在元素被解析完、插入到页面时调用
update( element, binding ) 指令所在模板被重新解析时调用
- 注意项:
- 1)、指令定义时不加
v-
,但使用时必须加v-
。 - 2)、指令名如果是多个单词,要使用
-
短横线分隔开,切忌使用驼峰命名(包括大驼峰和小驼峰)。
Vue生命周期
了解Vue生命周期的概念
做这么一个效果 —— 文本动态透明。
先用不对的方式来做一下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>认识Vue生命周期概念</title>
<script src="../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<h2 :style="{opacity: num}">玩动态改变文本透明度</h2>
{{ change() }}
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
num: 1,
},
// 改变透明度值大小
methods: {
change(){
setInterval(() => {
if( this.num <= 0 ) this.num = 1;
this.num -= 0.01;
}, 50);
}
}
});
</script>
</body>
</html>
上面这样做之后,看起来好像成功了,但是运行一会就会发现:电脑的风扇会使劲儿转^ _ ^ 。原因如下:
改造:使用Vue的钩子函数
注意:钩子函数执行的次数问题
这就是前面说 mounted()
为什么是:Vue解析完了模板之后,准备把“初始的真实DOM”渲染到页面时会调用的一个函数。初始的真实DOM,是初次转成真实DOM时执行的,后面数值改变那是更新,对于模板来说,它不叫改变,别忘了前面玩diff算法时说过的流程 ———— 新的虚拟DOM会和初始虚拟DOM进行对比。
小结:Vue生命周期
- 定义:Vue在关键时刻帮我们调用的一些特殊名称的函数。
- 别名:生命周期回调函数 / 生命周期函数 / 生命周期钩子。
- 注意:
- 生命周期函数的名字不可更改,但是:函数中的执行体是可以自己根据需求编写的。
- 生命周期函数中的this指向是Vue实例 或 组件实例对象。
生命周期过程
- 首先这个知识点的原理图在官网中有。
- 对上图中的内容做补充。下图这个图是为了先了解大概,接下来会简单说明一下图中的内容。
Vue的挂载流程
所谓的挂载在前面引入生命周期概念时说过了,就是Vue在调
mounted()
时,换言之:就是前面图中直到mounted()
时的步骤就是挂载流程,只是需要解读一下。@紫邪情
先来看一下这部分:
说在beforeCreate时还不可以使用vm访问data中的数据、methods中的方法,在这之前只开始初始化生命周期、事件,那就来测试一下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue的挂载</title>
<script src="../../js/vue.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<h2>{{name}}</h2>
<button @click = "print">切换名字</button>
</div>
<script>
// 去除浏览器控制台中的警告提示信息
Vue.config.productionTip = false;
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el:'#app',
data:{
name: '紫邪情'
},
methods: {
print(){
console.log("正在执行print()函数");
}
},
// 之前只开始初始化生命周期、事件,此时:还不能使用vm访问data和methods
beforeCreate () {
// 操作data中的数据 methods也是差不多的
console.log( this.name );
// 查看Vue内容,看看是哪些东西
console.log(this);
// 打断点 和java中的debug是一回事
debugger;
}
});
</script>
</body>
</html>
运行程序,调出控制台,然后刷新页面进入debug断点:
接着来看这一部分:
说的是:已经开始了数据监测、数据代理,可以使用vm访问data和methods了,那久测试一下。
接着看这一部分:
先看下面部分,说:页面呈现的是未经编译的DOM结果且对DOM的操作,“最终”都不奏效,测试一下。
同时还说:所有对DOM结构的操作都不奏效,那就试一下。
现在接着回去看那个逻辑判断里面的内容:
外层和内层就是下面这个:
而所谓的template模板更简单:
截图中有个地方容易产生歧义:template中应该还需要使用一个div给套起来,template中只能有一个根节点,而例子中有h2 和 button两个节点,会报错的,所以需要使用div( 或其他标签)把要写的代码给套起来,可以对照Vue实例和容器的对应关系( 一 一对应)。
最后来看这一部分:
但是:这里有一个点需要注意
这里是将真实DOM使用”vm.$el“把真实DOM中的内容给复制了一份保存起来了,这一步很关键,为的就是后续 新的虚拟DOM 和 初始的虚拟DOM进行对比算法时,有相同的数据内容,那么新的虚拟DOM就会直接用初始虚拟DOM的数据(前面玩v-for中key的原理时,key值为index和数据id的区别),而要用初始DOM就需要找到初始DOM呗,它保存起来之后就方便找到了嘛。
Vue的更新流程
内容就一丢丢,下图中红色框起来的内容:
来看beforeUpdate()
接着来看Updated
Vue的销毁流程
内容就是图中的最后一点东西,但是:这里面坑也是有点多的
图中提到了 vm.$destroy()
和 销毁哪些东西,所以先看官网对销毁的介绍:
现在就用代码去验证一下图中的内容,需要改造一下代码:
执行效果如下:
开始踩另一个坑
vm确实是销毁了,但是这个坑是前面说的销毁事件监听,它销毁的是:自定义事件(后续接触),所以Vue自身的事件还在,也执行了
那又有问题:页面为什么没 +1 ?
- 是因为
beforeDestroy ()
是一个贼尴尬的阶段,这里面确实可以拿到data、methods、指令这些; - 但是:当一点击销毁(调用了
vm.$destroy()
),程序就会执行beforeDestroy ()
,这里面的调用methods也会去执行,可是:关键点就是它不会再去调用beforeUpdate ()
和updated ()
———— 正常流程是数据发生改变就会调这两个,但是:deforeDestroy()
就不会调用,所以:才在前面说 此阶段一般干的事情是:关闭定时器、解除自定义事件绑定等操作。
Vue生命周期总结
Vue生命周期共有8个(4对 ),另外其实还有3个周期( 但是:这三个需要到router路由中玩切换时才能整)。
beforeCreate() created()
beforeMount() mounted()
beforeUpdate() Updated()
beforeDestroy() Destroyed()
其中:常用的钩子函数就两个
mounted() 用来发送ajax请求、启动定时器、绑定自定义事件、订阅消息(后续说明)等 即:完成的是"初始化操作"
beforeDestroy() 用来清除定时器、解绑自定义事件、取消订阅消息等,即:做的是“收尾操作”
关于销毁vm的问题
- 销毁后借助Vue开发者工具是看不到任何信息的。
- 销毁后自定义事件会失效,但是:Vue原生DOM事件依然有效。
- 一般不会在
beforeDestroy()
中操作数据,因为就算操作了,那也不会触发beforeUpdate()
和updated()
。