我的Vue学习笔记
MVVM思想
MVVM是Model-View-ViewModel的简写,即:模型-视图-视图模型.
模型是指后端传递的数据
视图是指我们看到的页面
视图模型则是MVVM思想的核心部分,它链接了View和Model并作为它们之间通信的桥梁,它有两个方向:一是将Model转化为View,即后端传递的数据转化成看到的页面,二则是将View转化为Model,也就是将用户操作在页面上的内容转化为后端的数据,这两个功能是通过数据绑定以及DOM事件监听来实现的,我们称之为数据的双向绑定。如图所示:
总的来说:视图层与模型层通过ViewModel进行通信,当数据发生变化时,ViewModel能够监听到数据的这种变化,然后通过通知对应的视图层更新页面;当用户操作视图层,ViewModel也能够监听到视图的变化,然后通知数据做改动,这就是MVVM思想,而Vue.js 则使用了MVVM的思想搭建出了既轻量级又开发高效快速且功能强大的框架。
创建实例,数据绑定,指令和事件
创建实例
vue 创建实例的方法如下:
<div id="app">
{{myData}}
</div>
<script>
var vueApp = new Vue({
el:'#app',
data:{
myData:'Hello World'
myData2:'Fuck World'
}
});
</script>
代码中 el 和 data 都是vue实例的属性,el 用于指定页面当中已存在的用来挂载Vue实例的DOM元素,data则是Vue需要双向绑定的数据。
数据绑定
当挂载后,我们可以使用 vue实例.$vue实例的属性
这样的语法来获取vue实例的属性,例如:
vueApp.$el
,访问data中的属性则不同,我们可以直接使用vue实例.data的属性名
这样的语法来访问,例如:vueApp.myData
生命周期钩子
vue生命周期钩子是在Vue对象生命周期的某一个阶段执行的已定义的方法。从初始化vue实例对象,到它被销毁,对象会遵循不同的生命阶段,具体内容我截了一张很著名的生命周期钩子图:
具体为:
- beforeCreate : 创建vue实例前的钩子函数
- created : 创建好了vue实例,但是尚未挂载到页面的DOM元素上
- beforeMount : 开始挂载
- mounted : 将编译好的html挂载到页面上 且 mounted在整个生命周期只执行一次
- beforeUpdate
- updated
- beforeDestroy : vue实例销毁之前执行的钩子函数
- 实例销毁时执行的钩子函数
vue生命周期有很多阶段,但是我们最常用到的是这三个阶段:
- created
- mounted
- beforeDestroy
举例说明:
var vueApp = new Vue({
el:'#app',
data:{
myData:'Hello World'
},
created:function () {
alert('vue实例创建完毕了,但是还没有挂载到DOM元素上');
},
mounted:function () {
alert('vue实例已经挂载到DOM上了');
}
});
对于上述示例页面来说,打开浏览器,首先会弹出"vue实例创建完毕了,但是还没有挂载到DOM元素上"这样的提示信息,对于created 这个阶段来说,实际上已经完成了vue实例的创建,但是并没有挂载至DOM元素上,然后会弹出“vue实例已经挂载到DOM上了”的提示框,mounted阶段是vue创建的实例挂载到DOM后进行调用的,我们的第一个业务逻辑也通常会在这里开始,对于beforeDestory 阶段,通过字眼意思也可以知道 这是vue实例销毁前的阶段,通过会在这个阶段进行解绑一些监听事件。
文本插值
对于本示例来讲,我们会在页面上看到Hello World 的字样,在html中,我们书写了这样的语法:
<div id="app">
{{myData}}
</div>
这种语法就是Vue的文本插值语法,它支持单个表达式,注意只是单个表达式:
{{1<0 ? myData:myData2}}
这是三元运算符表达式,因为它属于单个表达式
所以这是合法的,我们会在页面上看到Fuck World
{{var a = 6}}
注意:这是非法的
var a = 6 是一个声明表达式 + 一个赋值表达式
我们提到过 Vue 的文本插值语法只支持单个表达式
所以 这句话会报错
过滤器
vue.js 支持在{{}}插值的尾部添加一小管道符" | "对数据进行过滤,经常用于格式化文本,比如字母全部大写、货币千位使用逗号分隔等,同时也支持过滤器串联写法,传参等等。我们来看一个示例:
页面实时显示当前时间:
<div id="app">
{{currentDate | format}}
</div>
<script>
function formatTime(value) {
return value <10 ? '0'+value : value;
}
var vueApp = new Vue({
el:'#app',
data:{
currentDate: new Date()
},
filters:{
format:function (currentDate) {
var date = new Date(currentDate);
var year = date.getFullYear(); // 获取当前年
var month = formatTime(date.getMonth()+1); // 获取当前月(需要+1)
var days = formatTime(date.getDate()); // 获取当前天
var hours = formatTime(date.getHours()); // 获取当前小时
var minutes = formatTime(date.getMinutes()); // 获取当前分钟
var seconds = formatTime(date.getSeconds()); // 获取当前秒
return '当前时间为:'+year+'年'+month+'月'+days+'日'+hours+'时'+minutes+'分'+seconds+'秒'
}
},
mounted:function () {
this.timer = setInterval(()=>{
this.currentDate = new Date();
},1000)
},
beforeDestroy:function () {
window.clearInterval(this.timer);
}
});
</script>
我们还可以对过滤器进行传参,我们将代码做一个小小的改动:
<div id="app">
{{currentDate | format('当前时间为',':')}}
</div>
<script>
function formatTime(value) {
return value <10 ? '0'+value : value;
}
var vueApp = new Vue({
el:'#app',
data:{
currentDate: new Date()
},
filters:{
format:function (currentDate,arg1,arg2) {
var date = new Date(currentDate);
var year = date.getFullYear(); // 获取当前年
var month = formatTime(date.getMonth()+1); // 获取当前月(需要+1)
var days = formatTime(date.getDate()); // 获取当前天
var hours = formatTime(date.getHours()); // 获取当前小时
var minutes = formatTime(date.getMinutes()); // 获取当前分钟
var seconds = formatTime(date.getSeconds()); // 获取当前秒
return arg1+arg2+year+'年'+month+'月'+days+'日'+hours+'时'+minutes+'分'+seconds+'秒'
}
},
mounted:function () {
this.timer = setInterval(()=>{
this.currentDate = new Date();
},1000)
},
beforeDestroy:function () {
window.clearInterval(this.timer);
}
});
</script>
我们在过滤器中加入了参数,得到了和之前一样的结果。这里面需要注意的是:过滤器对应的函数中第一个传入的值是 “ | ” 前的data中的属性值,在本例中即:currentDate ,而并非是参数,传入的参数是在函数的第二个传递值及以后的值。同时过滤器也可以使用"串联写法" ,如:{{currentDate | filter1 | filter2 | filter3 }}
,过滤器的方法会依次执行,并且它们都被定义在 vue的属性 filters 当中。
指令和事件
支持指令操作是vue框架的优点之一,它是vue模板中最常用的一项功能,省去了复杂的DOM API,并且及其方便记忆和理解。接下来我们先简单了解一些指令操作:
-
v-text
v-text 的功能是解析文本 它的具体作用和文本插值{{}}是一毛一样的
-
v-html
将内容解析成html
-
v-bind
动态更新HTML上的元素,例如id,class等等
-
v-on
用来绑定事件监听
接下来我们依旧通过示例,来简单认识这几个指令事件。
<style>
.active{
background: red;
height: 1em;
}
</style>
---------------------------------------------------------------
<div id="app">
v-text: <br>
{{apple}} <br>
<!--v-text 的作用和文本插值 是一样的-->
<span v-text="apple"></span>
v-html: <br>
<span v-html="banana"></span> <br>
v-bind: <br>
<div v-bind:class="className"></div>
v-bind的简写格式: <br>
<div :class="className"></div>
v-on: <br>
<button v-on:click="plusOne">{{originalNum}}</button>
<br>
v-on的简写格式: <br>
<button @click="plusOne">{{originalNum}}</button>
</div>
<script>
var app = new Vue({
el:'#app',
data:{
apple:'苹果',
banana:'<span style="color: yellow;">香蕉</span>',
className:'active',
originalNum:0
},
methods:{
plusOne:function () {
this.originalNum = this.originalNum + 1;
}
}
});
</script>
部分html内容如上,对于 v-bind 和 v-on 两个指令 都有响应的简写格式,如示例中:
<div v-bind:class="className"></div>
可以简写为:
<div :class="className"></div>
<button v-on:click="plusOne">{{originalNum}}</button>
可以简写为:
<button @click="plusOne">{{originalNum}}</button>
计算属性
先看一个示例:我们要实现一个功能 反转字符串,对于字符串 '123,456,789' 我们要将字符串变为: '321,654,987' ,示例程序如下:
<div id="app">
{{text.split(',').map((e)=>{return e.split('').reverse().join('')}).join(',')}}
</div>
<script>
var app = new Vue({
el:'#app',
data:{
text:'123,456,789'
}
});
</script>
我们看到这个程序心里一定一句妈卖批,程序虽然没有什么问题,也符合文本插值的语法,但是看起来就是觉得很头疼,并且这样写的代码也不好进行维护。我们可以将内容做一个优化,创建一个函数并将函数放在methods属性当中:
<div id="app">
<!--将'123,456,789' 这串字符串 变为 '321,654,987'-->
令人头痛的代码: <br>
{{text.split(',').map((e)=>{return e.split('').reverse().join('')}).join(',')}} <br>
methods属性内的方法: <br>
{{reverseMethod()}} <br>
</div>
<script>
var app = new Vue({
el:'#app',
data:{
text:'123,456,789'
},
methods:{
reverseMethod:function () {
// ['123',456,'789']
return this.text.split(',').map((e)=>{
return e.split('').reverse().join('');
}).join(',');
}
}
});
</script>
除了将方法定义在methods中并调用,我们还可以使用计算属性:
<div id="app">
<!--将'123,456,789' 这串字符串 变为 '321,654,987'-->
令人头痛的代码: <br>
{{text.split(',').map((e)=>{return e.split('').reverse().join('')}).join(',')}} <br>
methods属性内的方法: <br>
{{reverseMethod()}} <br>
计算属性: <br>
{{reverseText}}
</div>
<script>
var app = new Vue({
el:'#app',
data:{
text:'123,456,789'
},
methods:{
reverseMethod:function () {
// ['123',456,'789']
return this.text.split(',').map((e)=>{
return e.split('').reverse().join('');
}).join(',');
}
},
computed:{
reverseText:function () {
return this.text.split(',').map((e)=>{
return e.split('').reverse().join('')
}).join(',');
}
}
});
</script>
我们将方法定义在了 vue示例的computed 属性当中,乍一看 和 methods并没有什么区别,实际上 计算属性可以实现的东西 在methods方法中 都可以实现 ,但是二者也是有差别的。具体的差别,我们在看完另一个示例后,再来介绍:
本示例的功能是对购物车的价格总和进行计算,具体代码如下:
<div id="app">
{{prices}}
</div>
<script>
var app2 = new Vue({
el:'#app2',
data:{
msg:'两个购物车的价格总和为:'
}
});
var app = new Vue({
el:'#app',
data:{
// 购物车1
package1:[
{
name:'iphone',
price:6999,
count:2
},
{
name:'ipad',
price:3299,
count:2
}
],
// 购物车 2
package2:[
{
name:'XiaoMi',
price:999,
count:6
},
{
name:'ipod',
price:1099,
count:2
}
]
},
computed:{
prices:{
get:function () {
var prices = 0;
// 第一个购物车的价格总和
for(var i = 0;i<this.package1.length;i++){
prices += this.package1[i].price * this.package1[i].count;
}
// 第一个购物车与第二个购物车的价格总和
for(var j = 0; j<this.package2.length;j++){
prices += this.package2[j].price * this.package2[j].count;
}
return app2.msg+prices;
},
set:function (options) {
if(options.package === 'package1'){
for(var i = 0;i<this.package1.length;i++){
if(this.package1[i].name === options.name){
this.package1[i].price = options.price;
this.package1[i].count = options.count;
break;
}
}
}else if(options.package === 'package2'){
for(var i = 0;i<this.package2.length;i++){
if(this.package2[i].name === options.name){
this.package2[i].price = options.price;
this.package2[i].count = options.count;
break;
}
}
}
}
}
}
});
</script>
每一个计算属性都有一个getter和 setter方法,当我们仅仅是调用了计算属性的方法名时,实际上调用的是 该属性的get 方法,当有必要设置一些参数时,我们也可以提供一个 set方法手动修改计算属性。另外在本示例中,我们可以看到 计算属性 prices不仅依赖了当前vue对象 而且也依赖了其他的实例,尽管 app2 并没有挂载到DOM上,但是执行的结果表明 计算属性可以依赖于其他的实例。
代码执行的结果如下:
我们可以通过set方法设置需要改动的参数:
当修改完毕后,页面会实时更新数据:
我们回过头谈论下methods 里的方法 和 计算属性有什么不同之处:
计算属性与methods方法
我们首先来看一个示例:
<div id="app">
{{text}} <br>
{{getNow()}} <br>
{{now}} <br>
</div>
<script>
var app = new Vue({
el:'#app',
data:{
text:666
},
methods:{
getNow:function () {
return Date.now();
}
},
computed:{
now:{
get:function () {
return Date.now(); // 获取 1970年 至现在的时间戳
}
}
}
});
</script>
我们在 methods 方法以及 计算属性中都定义了获取 1970年到现在为止的时间戳的函数 ,代码运行的结果如下:
现在,我对text的内容做一个小小的更改:
我们在更改了text的数据后,页面自然会发生改动,但是我们发现:
不仅仅是text的内容发生了变动,methods 的 getNow方法在页面上渲染出的html也发生了变动:
为什么会这样呢?这里面就涉及到了计算属性的缓存。对于调用方法而言,只要是页面重新渲染,那么方法就会执行,注意我提到的是只要页面重新渲染,如果没有渲染的话,方法是不会自动执行的,而计算属性则不同,无论页面是否发生渲染,只要计算属性依赖的数据没有发生变化,那么就永远不会变。计算属性和methods方法的区别就是两者依赖的东西是不同的,计算属性是基于它依赖的数据进行缓存的,当它依赖的数据产生变化时,它才会变,而methods不存在缓存,当页面发生了重新的渲染,方法就会执行。在上文中,提到过计算属性能实现的东西methods方法都可以实现,那么对于二者该如何取舍使用呢? 其实仔细想想也能想出结果,当我们需要进行大量计算时,因为数据的计算耗费内存,我们需要对数据进行缓存,那么就应该使用计算属性。
v-bind 的class与style绑定
v-bind 的作用只用一句话就可以描述,那就是绑定活的属性,我们通过一个简单的示例来回顾一下:
<div id="app">
<a v-bind:href="url">链接</a>
</div>
<script>
var app = new Vue({
el:'#app',
data:{
url:'http://www.baidu.com'
}
});
</script>
页面点击连接后则会跳转到baidu。
v-bind 的 class绑定
v-bind绑定 class 对象语法
见示例:
<style>
.red{
background: red;
}
.blue{
background: blue;
}
</style>
--------------------------------------------------
<div id="app">
v-bind绑定对象: 对象的key是类名,对象的value是布尔值<br>
<button v-bind:class="{red:isRed,blue:isBlue}" v-on:click="transColor">clickMe</button>
</div>
<script>
var app = new Vue({
el:'#app',
data:{
isRed:true,
isBlue:false,
},
methods:{
transColor:function () {
this.isRed = !this.isRed;
this.isBlue = !this.isBlue;
}
}
});
</script>
本示例实现的功能是一个按下去能转换颜色的按钮,同时v-bind:class使用的是对象语法,对象的key为 类名 对象的value则是布尔值,如果值为true则绑定这个类名,如果值为false则和此类名无关联。
v-bind绑定计算属性语法
见示例:
<style>
.red{
width: 100px;
height: 100px;
background: red;
}
</style>
-----------------------------------------------
<div id="app">
v-bind绑定计算属性: 计算属性返回的也是对象 key为类名 ; value为 布尔值 <br>
<div v-bind:class="computedClass"></div>
</div>
<script>
var app = new Vue({
el:'#app',
data:{
isRed:true,
},
computed:{
computedClass:function () {
return {
red:'isRed'
}
}
}
});
</script>
示例中 v-bind:class绑定了计算属性,但是即便为计算属性,返回的结果也是对象,对象中key为类名,value为布尔值,同上 当value值为真时,元素携带这个类,当value值为false时,元素则不携带这个类
v-bind绑定class数组语法
见示例:
<style>
.first{
font-size: 32px;
color: red;
}
.second{
background: #000;
}
</style>
---------------------------------------
<div id="app">
绑定class数组语法:数组中的成员为data中的属性,这些属性直接对应类名 <br>
<div v-bind:class="[classOne,classTwo]">VUE is cool!</div>
</div>
<script>
var app = new Vue({
el:'#app',
data:{
classOne:'first',
classTwo:'second'
}
});
</script>
v-bind绑定class数组的语法相比于前面而言更加简单,数组中的成员为data中的属性,这些属性直接对应类名,通过示例应该不难看懂~
v-bind绑定内联样式
除了绑定class较为常用,v-bind也可以绑定内联样式
<div id="app">
绑定内联样式:对象语法,key 代表style的属性,value代表属性对应的值 br>
<div v-bind:style="{'color':color,'fontSize':fontSize}">VUE is cool!</div>
</div>
<script>
var app = new Vue({
el:'#app',
data:{
color:'red',
fontSize:'32px'
}
});
</script>
除了这种对象语法之外,绑定内联样式也支持"数组"语法,但是据说这种语法比较蠢,因为谁也不会把样式分开写,所以我就不在这里列出来了。需要注意的是 css属性名称 使用驼峰命名,或者是短横线分隔命名,但是这里面还是推荐驼峰式写法~~~~