我的Vue学习笔记(一)

274 阅读14分钟

我的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实例对象,到它被销毁,对象会遵循不同的生命阶段,具体内容我截了一张很著名的生命周期钩子图:


具体为:

  1. beforeCreate : 创建vue实例前的钩子函数
  2. created : 创建好了vue实例,但是尚未挂载到页面的DOM元素上
  3. beforeMount : 开始挂载
  4. mounted : 将编译好的html挂载到页面上 且 mounted在整个生命周期只执行一次
  5. beforeUpdate
  6. updated
  7. beforeDestroy : vue实例销毁之前执行的钩子函数
  8. 实例销毁时执行的钩子函数

vue生命周期有很多阶段,但是我们最常用到的是这三个阶段:

  1. created
  2. mounted
  3. 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,并且及其方便记忆和理解。接下来我们先简单了解一些指令操作:

  1. v-text

    v-text 的功能是解析文本 它的具体作用和文本插值{{}}是一毛一样的

  2. v-html

    将内容解析成html

  3. v-bind

    动态更新HTML上的元素,例如id,class等等

  4. 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属性名称 使用驼峰命名,或者是短横线分隔命名,但是这里面还是推荐驼峰式写法~~~~