从零开始学习vue

一.起步

1.hello,world

在学习 vue 之前,需要有扎实的 HTML,CSS,JavaScript 基础。任何一个入门语言都离不开hello,world!例子,我们来写这样一个例子: 新建一个 html 文件,helloworld.html,如下:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>hello,world</title>
      </head>
      <body>
        <div id="app">
          {{ message }}
        </div>
        <script>
            var app = new Vue({
                el:"#app",
                data:{
                    message:"hello,world!"
                }
            })
        </script>
      </body>
    </html>
复制代码

现在我们已经成功创建了第一个vue应用,数据和DOM已经被关联,所有的东西都是响应式的,我们要如何确定呢,打开浏览器控制台,修改app.message的值。

在线示例

在这其中data对象的写法,我们还可以写成函数形式,如下:

    var app = new Vue({
      el:"#app",
      //这里是重点
      data(){
         return{
            message:"hello,world!"
         }
      }
    })

复制代码

2.文本插值

当然除了文本插值,我们还可以绑定元素属性,如下:

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <!-- 引入vue.js开发版本 -->
            <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
            <title>v-bind</title>
        </head>
        <body>
            <div id="app">
              <span v-bind:title="message">鼠标悬浮上去可以看到</span>
            </div>
            <script>
                var app = new Vue({
                    el:"#app",
                    data:{
                         message:"页面加载于:" + new Date().toLocaleString()
                    }
                })
            </script>
        </body>
    </html>
复制代码

同样的我们也可以修改message的值,这样的话,鼠标悬浮上去,悬浮的内容就会改变了。在这个例子中v-bind(或者也可以写成':')其实就是一个指令,指令通常前缀都带有v-,用于表示vue指定的特殊特性,在渲染DOM的时候,它会应用特殊的响应式行为。这个指令所表达的意思就是:将这个title属性的值与vue实例的message值保持一致。

在线示例

3.元素的显隐

当然,我们也可以控制一个元素的显隐,那也是非常的简单,只需要使用v-show指令即可:

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <!-- 引入vue.js开发版本 -->
            <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
            <title>v-if</title>
        </head>
        <body>
            <div id="app">
                <span v-show="seen">默认你是看不到我的哦</span>
            </div>
            <script>
                var app = new Vue({
                    el:"#app",
                    data:{
                        seen:false
                    }
                })   
            </script>
        </body>
    </html>
复制代码

尝试在控制台中修改seen的值,也就是app.seen = true,然后你就可以看到页面中的span元素了。

在线示例

4.列表渲染

还有v-for指令,用于渲染一个列表,如下:

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <!-- 引入vue.js开发版本 -->
            <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
            <title>v-for</title>
        </head>
        <body>
            <div id="app">
                <div v-for="(item,index) in list" :key="index">
                    <span>{{ item.name }}</span>
                    <p>{{ item.content }}</p>
                </div>
            </div>
            <script>
                var app = new Vue({
                    el:"#app",
                    data:{
                        list:[
                         { name:"项目一",content:"HTML项目"},
                         { name:"项目二",content:"CSS项目"},
                         { name:"项目三",content:"JavaScript项目"},
                        ]
                    }
                })  
            </script>
        </body>
    </html>            
复制代码

当然你也可以自己在控制台改变list的值。

在线示例

5.事件

vue通过v-on + 事件属性名(也可以写成'@' + 事件属性名)指令添加事件,例如v-on:click@click如下一个示例:

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <!-- 引入vue.js开发版本 -->
            <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
            <title>v-on</title>
        </head>
        <body>
            <div id="app">
                <span>{{ message }}</span>
                <button type="button" v-on:click="reverseMessage">反转信息</button>
                <!--也可以写成-->
                <!--<button type="button" @click="reverseMessage">反转信息</button>-->
            </div>
            <script>
                var app = new Vue({
                    el:"#app",
                    data:{
                        message:"hello,vue.js!"
                    },
                    methods:{
                        reverseMessage:function(){
                            //在这里this指向构造函数构造的vue实例
                            this.message = this.message.split('').reverse().join('');
                        }
                    }
                }) 
            </script>
        </body>
    </html>
复制代码

反转信息的思路就是使用split()方法将字符串转成数组,,然后使用数组的reverse()方法将数组倒序,然后再使用join()方法将倒序后的数组转成字符串。

在线示例

6.组件

组件是vue中的一个核心功能,它是一个抽象的概念,它把所有应用抽象成一个组件树,一个组件树就是一个预定义的vue实例,在vue中使用Vue.component()注册一个组件,它有两个参数,第一个参数为组件名(尤其要注意组件名的命名),第二个参数为组件属性配置对象,如:

    //定义一个简单的组件
    Vue.component('todo-item',{
       template:`<li>待办事项一</li>`
    })
复制代码

现在我们来看一个完整的例子:

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <!-- 引入vue.js开发版本 -->
            <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
            <title>component</title>
        </head>
        <body>
            <div id="app">
                <ul>
                    <todo-item v-for="(item,index) in todoList" v-bind:todo="item" v-bind:key="index"></todo-item>
                </ul>
            </div>
            <script>
                Vue.component('todo-item',{
                    props:['todo'],
                    template:`<li>{{ todo.number }}.{{ todo.text }}</li>`
                })
                var app = new Vue({
                    el:"#app",
                    data:{
                        todoList:[
                            { number:1,text:"html"},
                            { number:2,text:"css"},
                            { number:3,text:"javascript"}
                        ]
                    },
                    methods:{
             
                    }
                })
            </script>
        </body>
    </html>
复制代码

这样,一个简单的组件就完成了,在这里,我们知道了,父组件app可以通过props属性将数据传递给子组件todo-item,这是vue父子组件之间的一种通信方式。

在线示例

二.核心

1.vue实例

每个vue应用都是通过Vue构造函数创建的一个新的实例开始的:

    var vm = new Vue({
       //选项对象
    })
复制代码

在这其中vm(viewModel的简称)通常都表示vue实例的变量名。当创建一个vue实例,你都可以传入一个选项对象作为参数,完整的选项对象,你可能需要查看API文档

一个vue应用应该由一个通过new Vue构造的根实例和许多可嵌套可复用的组件构成,这也就是说所有的组件都是vue实例。

2.数据与方法

当一个vue实例被创建完成之后,就会向它的vue响应系统中加入了data对象中能找到的所有属性,当这些属性的值发生改变之后,视图就会发生响应,也就是更新相应的值。我们来看一个例子:

    //源数据对象
    var obj = { name:"eveningwater" };
    //构建实例
    var vm = new Vue({
       data:obj
    })
    
    //这两者是等价的
    vm.name === obj.name;
    //这也就意味着
    //修改data对象里的属性也会影响到源数据对象的属性
    vm.name = "waterXi";
    obj.name;//"waterXi"
    //同样的,修改源数据对象的属性也会影响到data对象里的属性
    obj.name = 'stranger';
    vm.name;//"stranger"
复制代码

可能需要注意的就是,只有data对象中存在的属性才是响应式的,换句话说,你为源数据对象添加一个属性,根本不会影响到data对象。如:

    obj.sex = "男";
    vm.sex;//undefined
    obj.sex;//'男'
    obj.sex = "哈哈哈";
    vm.sex;//undefined
复制代码

这也就意味着你对sex的修改并不会让视图更新,如此一来,你可能需要在data对象中初始化一些值,如下:

    data:{
       str:'',
       bool:false,
       arr:[],
       obj:{},
       err:null,
       num:0
    }
复制代码

在线示例

只是还有一个例外Object.freeze(),这个方法就相当于锁定(冻结)一个对象,使得我们无法修改现有属性的特性和值,并且也无法添加新属性。因此这会让vue响应系统无法追踪变化:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>freeze</title>
      </head>
      <body>
        <div id="app">
          <span>{{ message }}</span>
          <button type="button" v-on:click="reverseMessage">反转信息</button>
        </div>
        <script>
            var obj = {
              message: "hello,vue.js!"
          }
          //阻止对象
          Object.freeze(obj);
          var app = new Vue({
            el: "#app",
            data:obj,
            methods: {
              reverseMessage: function() {
                this.message = this.message.split("").reverse().join("");
              }
            }
          });  
        </script>
      </body>
    </html>  
复制代码

如此一来,无论我们怎么点击按钮,都不会将信息反转,甚至页面还会报错。

在线示例自行查看效果。

当然除了数据属性以外,vue还暴露了一些有用的实例属性和方法,它们通常都带有$前缀,这样做的方式是以便与用户区分开来。来看一个示例:

    <!DOCTYPE html>
    <html>
        <head>
          <meta charset="utf-8" />
          <!-- 引入vue.js开发版本 -->
          <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
          <title>property</title>
        </head>
        <body>
            <div id="app"></div>
            <script>
                var obj = {
                    name:'eveningwater'
                }
                var vm = new Vue({
                    data:obj,
                });
                //这行代码表示将vue实例挂载到id为app的DOM根节点上,相当于在实例的选项对象中的el选项,即
                //el:'#app'
                 vm.$mount(document.querySelector('#app')) 
                 //数据是相等的
                 vm.$data === obj;//true
                 //挂载的根节点
                 vm.$el === document.querySelector('#app');//true
                 //以上两个属性都是实例上的属性,接下来还有一个watch即监听方法是实例上的方法
                 vm.$watch('name',function(oldValue,newValue){
                   //数据原来的值
                   console.log(oldValue);
                   //数据最新的值
                    console.log(newValue);
                 })
            </script>
        </body>
    </html>
复制代码

接下来,可以尝试在浏览器控制台修改name的值,你就会发现watch()方法的作用了。

在线示例

3.实例生命周期

每个vue实例在被创建的时候都会经历一些初始化的过程,这其中提供了一些生命周期钩子函数,这些钩子函数代表不同的生命周期阶段,这些钩子函数的this就代表调用它的那个实例。对于生命周期,有一张图:

你不需要立即这张图所代表的含义,我们来看一个示例:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>vue life cycle</title>
      </head>
      <body>
        <div id="app">
            <span>vue生命周期</span>
        </div>
        <script>
          var obj = {
              name:'eveningwater'
          }
          var app = new Vue({
              data:obj,
              beforeCreate:function(){
                //此时this指向app这个vue实例,但并不能得到data属性,因此this.name的值是undefined
                console.log('实例被创建之前,此时并不能访问实例内的任何属性' + this.name)
              }
          });
        </script>
      </body>
    </html>     
复制代码

关于生命周期的全部理解,我们需要理解后续的组件知识,再来补充,此处跳过。

在线示例

4.模板语法

vue使用基于HTML的模板语法,在vue的底层是将绑定数据的模板渲染成虚拟DOM,并结合vue的响应式系统,从而减少操作DOM的次数,vue会计算出至少需要渲染多少个组件。

最简单的模板语法莫过于插值了,vue使用的是Mustache语法(也就是双大括号"{{}}")。这个只能对文本进行插值,也就是说无论是字符串还是标签都会被当作字符串渲染。如:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>Mustache</title>
      </head>
      <body>
        <div id="app">
            <span>{{ greeting }}World!</span>
        </div>
        <script>
          var obj = { greeting:"Hello,"};
          var vm = new Vue({
              data:obj
          });
          vm.$mount(document.getElementById('app'));
        </script>
      </body>
    </html>
复制代码

如此以来Mustache标签就会被data对象上的数据greeting给替代,而且我们无论怎么修改greeting的值,视图都会响应。

在线示例

我们还可以使用v-once指令对文本进行一次性插值,换句话说,就是这个指令让插值无法被更新:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>Mustache</title>
      </head>
      <body>
        <div id="app">
            <span v-once>{{ greeting }}World!</span>
        </div>
        <script>
          var obj = { greeting:"Hello,"};
          var vm = new Vue({
              data:obj
          });
          vm.$mount(document.getElementById('app'));
        </script>
      </body>
    </html>
复制代码

在浏览器控制台中我们输入vm.greeting="stranger!"可以看到视图并没有被更新,这就是这个指令的作用,我们需要注意这个指令对数据造成的影响。

在线示例

既然双大括号只能让我插入文本,那要是我们要插入HTML代码,我们应该怎么办呢?v-html这个指令就可以让我们插入真正的HTML代码。

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>v-html</title>
      </head>
      <body>
        <div id="app">
            <p>{{ message }}</p>
            <p v-html="message"></p>
        </div>
        <script>
          var obj = { message:"<span style='color:#f00;'>hello,world!</span>"};
          var vm = new Vue({
             data:obj
          });
          vm.$mount(document.getElementById('app'));
        </script>
      </body>
    </html>
复制代码

页面效果如图所示;

在线示例

关于HTML特性,也就是属性,我们需要用到v-bind指令,例如:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>v-bind</title>
      </head>
      <body>
        <div id="app">
            <div v-bind:id="propId">使用v-bind指令给该元素添加id属性</div>
        </div>
        <script>
           var obj = { propId:"myDiv"};
           var vm = new Vue({
              data:obj
           });
           vm.$mount(document.getElementById('app'));
        </script>
      </body>
    </html>
复制代码

打开浏览器控制台,定位到该元素,我们就能看到div元素的id属性为"myDiv",如下图所示:

在线示例

在绑定与元素实际作用相关的属性,比如disabled,这个指令就被暗示为true,在默认值是false,null,undefined,''等转换成false的数据类型时,这个指令甚至不会表现出来。如下例:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>v-bind</title>
      </head>
      <body>
        <div id="app">
            <button type="button" v-bind:disabled="isDisabled">禁用按钮</button>
        </div>
        <script>
          var obj = { isDisabled:123};
          var vm = new Vue({
             data:obj
          });
          vm.$mount(document.getElementById('app'));
        </script>
      </body>
    </html>
复制代码

这样一来,无论我们怎么点击按钮都没用,因为123被转换成了布尔值true,也就表示按钮已经被禁用了,我们可以打开控制台看到:

你可以尝试这个示例在线示例

在使用模板插值的时候,我们可以使用一些JavaScript表达式。如下例:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>expression</title>
      </head>
      <body>
        <div id="app">
          <p>{{ number + 1 }}</p>
          <p>{{ ok ? "确认" : "取消" }}</p>
          <p>{{message.split("").reverse().join("")}}</p>
          <div v-bind:id="'my' + elementId">
            元素的id为<span :style="{ 'color':color }">myDiv</span>
          </div>
        </div>
        <script>
            var obj = {
                number: 123,
                ok: true,
                message: "hello,vue.js!",
                elementId: "Div",
                color: "red"
            };
            var vm = new Vue({
              data: obj
            });
            vm.$mount(document.getElementById("app"));
        </script>
      </body>
    </html>
复制代码

这些JavaScript表达式都会被vue实例作为JavaScript代码解析。

在线示例

值得注意的就是有个限制,只能绑定单个表达式,像语句是无法生效的。如下例:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>sentence</title>
      </head>
      <body>
        <div id="app">
          <p>{{ var number = 1 }}</p>
          <p>{{ if(ok){ return '确认'} }}</p>
        </div>
        <script>
            var obj = {
                number: 123,
                ok: true
            };
            var vm = new Vue({
              data: obj
            });
            vm.$mount(document.getElementById("app")); 
        </script>
      </body>
    </html>
复制代码

像这样直接使用语句是不行的,浏览器控制台报错,如下图:

不信可以自己试试在线示例

指令(Directives)是带有v-前缀的特殊特性,通常指令的预期值就是单个JavaScript表达式(v-for除外),例如v-ifv-show指令,前者表示DOM节点的插入和删除,后者则是元素的显隐。所以,指令的职责就是根据表达式值的改变,响应式的作用于DOM。现在我们来看两个示例:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>v-if</title>
      </head>
      <body>
        <div id="app">
          <p v-if="value === 1">{{ value }}</p>
          <p v-else-if="value === 2">{{ value }}</p>
          <p v-else>{{ value }}</p>
        </div>
        <script>
            var obj = {
              value: 1
            };
            var vm = new Vue({
              el: "#app",
              data() {
                return obj;
              }
            });
        </script>
      </body>
    </html>
复制代码

运行在浏览器效果如图:

现在你可以尝试在浏览器控制台更改vm.value = 2vm.value = 3我们就可以看到页面的变化。

在线示例

我们再看v-show的示例:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>v-show</title>
      </head>
      <body>
        <div id="app">
          <p v-show="value === 1">{{ value }}</p>
          <p v-show="value === 2">{{ value }}</p>
          <p v-show="value === 3">{{ value }}</p>
        </div>
        <script>
          var obj = {
            value:1
          } 
          var vm = new Vue({
              data:obj
          });
          vm.$mount(document.querySelector('#app'))
        </script>
      </body>
    </html>
复制代码

然后查看效果如图:

尝试在控制台修改vm.value = 2vm.value = 3我们就可以看到页面的变化。

在线示例查看。

从上面两个示例的对比,我们就可以看出来v-showv-if指令的区别了,从切换效果来看v-if显然不如v-show,这说明v-if有很大的切换开销,因为每一次切换都要不停的执行删除和插入DOM元素操作,而从渲染效果来看v-if又比v-show要好,v-show只是单纯的改变元素的display属性,而如果我们只想页面存在一个元素之间的切换,那么v-if就比v-show要好,这也说明v-show有很大的渲染开销。

而且v-if还可以结合v-else-ifv-else指令使用,而v-show不能,需要注意的就是v-else必须紧跟v-if或者v-else-if之后。当需要切换多个元素时,我们还可以使用template元素来包含,比如:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>template</title>
      </head>
      <body>
        <div id="app">
            <template v-if="value > 1">
                <p>{{ value }}</p>
                <h1>{{ value }}</h1>
            </template>
            <template v-else>
                <span>{{ value }}</span>
                <h2>{{ value }}</h2>
            </template>
        </div>
        <script>
          var obj = {
            value: 1
          };
          var vm = new Vue({
            el: "#app",
            data() {
              return obj;
            }
          });
        </script>
      </body>
    </html>
复制代码

此时template相当于一个不可见元素,如下图所示:

尝试在控制台修改vm.value = 2就可以看到效果了。

在线示例

对于可复用的元素,我们还可以添加一个key属性,比如:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>key</title>
      </head>
      <body>
        <div id="app">
          <template v-if="loginType === 'username'">
            <label>username:</label>
            <input type="text" key="username" placeholder="enter your username" />
          </template>
          <template v-else-if="loginType === 'email'">
            <label>email:</label>
            <input type="text" key="email" placeholder="enter your email" />
          </template>
          <template v-else>
            <label>mobile:</label>
            <input type="text" key="mobile" placeholder="enter your mobile" />
          </template>
          <button type="button" @click="changeType">
            toggle login type
          </button>
        </div>
        <script>
          var obj = {
            loginType: "username",
            count:1
          };
          var vm = new Vue({
            el: "#app",
            data() {
              return obj;
            },
            methods: {
              changeType() {
                this.count++;
                if (this.count % 3 === 0) {
                  this.loginType = "username";
                } else if (this.count % 3 === 1) {
                  this.loginType = "email";
                } else {
                  this.loginType = "mobile";
                }
              }
            }
          });
        </script>
      </body>
    </html>
复制代码

效果如图:

你可以狠狠的点击在线示例查看。

从这几个示例我们也可以看出v-if就是惰性,只有当条件为真时,v-if才会开始渲染。值得注意的就是v-ifv-for不建议合在一起使用。来看一个示例:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>v-if与v-for</title>
      </head>
      <body>
        <div id="app">
            <ul>
                <li v-for="(item,index) in list" v-bind:key="index" v-if="item.active">
                    <span>{{ item.value }}</span>
                </li>
            </ul>
        </div>
        <script>
           var obj = {
              list:[
                  {
                      value:'html',
                      active:false
                  },
                  {
                      value:'css',
                      active:false
                  },
                  {
                      value:"javascript",
                      active:true
                  }
              ]
          };
          var vm = new Vue({
            el: "#app",
            data() {
              return obj;
            }
          });
        </script>
      </body>
    </html>
复制代码

虽然以上代码不会报错,但这会造成很大的渲染开销,因为v-for优先级高于v-if,这就造成每次执行v-if指令时总要先执行v-for遍历一遍数据。

在线示例查看。

遇到这种情况,我们可以使用计算属性。如:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>v-if和v-for</title>
      </head>
      <body>
        <div id="app">
          <ul v-if="newList">
            <li v-for="(item,index) in newList" v-bind:key="index">
              <span>{{ item.value }}</span>
            </li>
          </ul>
        </div>
        <script>
            var obj = {
                list: [
                  {
                    value: "html",
                    active: false
                  },
                  {
                    value: "css",
                    active: false
                  },
                  {
                    value: "javascript",
                    active: true
                  }
                ]
            };
            var vm = new Vue({
              el: "#app",
              //先过滤一次数组
              computed: {
                newList: function() {
                 return this.list.filter(function(item) {
                    return item.active;
                  });
                }
              },
              data() {
                return obj;
              }
            }); 
        </script>
      </body>
    </html>
复制代码

如此一来,就减少了渲染开销。

在线示例查看。

指令的用法还远不止如此,一些指令是可以带参数的,比如v-bind:title,在这里title其实就是被作为参数。基本上HTML5属性都可以被用作参数。比如图片路径的src属性,再比如超链接的href属性,甚至事件的添加也属于参数,如v-on:click中的click其实就是参数。来看一个示例:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>param</title>
      </head>
      <body>
        <div id="app">
            <a v-bind:href="url">思否</a>
            <img :src="src" alt="美女" />
        </div>
        <script>
          var obj = {
              url: "https://segmentfault.com/",
              src:"http://eveningwater.com/project/imggallary/img/15.jpg"
          };
          var vm = new Vue({
            el: "#app",
            data() {
              return obj;
            }
          });
        </script>
      </body>
    </html>
复制代码

效果如图所示:

在线示例查看。

v-on指令还可以添加修饰符,v-bindv-on指令还可以缩写成:@。缩写对于我们在繁琐的使用指令的项目当中是一个很不错的帮助。

5.计算属性

模板表达式提供给我们处理简单的逻辑,对于更复杂的逻辑,我们应该使用计算属性。来看两个示例的对比:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>mustache</title>
      </head>
      <body>
        <div id="app">
          <span>{{ message.split('').reverse().join('') }}</span>
        </div>
        <script>       
            var obj = {
              message:"hello,vue.js!"
            }
            var vm = new Vue({
                data:obj
            })
            vm.$mount(document.querySelector('#app'))
        </script>
      </body>
    </html>  
复制代码

第二个示例:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>mustache</title>
      </head>
      <body>
        <div id="app">
          <span>{{ reverseMessage }}</span>
        </div>
        <script>
            var obj = {
                message:"hello,vue.js!"
            }
            var vm = new Vue({
                data:obj,
                computed:{
                    reverseMessage:function(){
                       return this.message.split('').reverse().join('');
                    }
                }
            })
            vm.$mount(document.querySelector('#app')) 
        </script>
      </body>
    </html>
复制代码

与第一个示例有所不同的就是在这个示例当中,我们申明了一个计算属性reverseMessage,并且提供了一个getter函数将这个计算属性同数据属性message绑定在一起,也许有人会有疑问getter函数到底在哪里呢?

如果我们将以上示例修改一下:

    var obj = {
       message:"hello,vue.js!"
     }
     var vm = new Vue({
         data:obj,
         computed:{
             reverseMessage:{
                get:function(){
                   return this.message.split('').reverse().join('');
                }
             }
         }
     })
     vm.$mount(document.querySelector('#app'))
复制代码

相信如此一来,就能明白了。

在线示例

你可以通过控制台修改message的值,只要message的值发生改变,那么绑定的计算属性就会发生改变。事实上,在使用reverseMessage绑定的时候,我们还可以写成调用方法一样的方式,如:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>mustache</title>
      </head>
      <body>
        <div id="app">
          <span>{{ reverseMessage() }}</span>
        </div>
        <script>
            var obj = {
              message:"hello,vue.js!"
            }
            var vm = new Vue({
                data:obj,
                computed:{
                    reverseMessage:function(){
                       return this.message.split('').reverse().join('');
                    }
                }
            })
            vm.$mount(document.querySelector('#app'))
        </script>
      </body>
    </html>
复制代码

那么这两者有何区别呢?虽然两者的结果都一样,但计算属性是根据依赖进行缓存的,只有相关依赖发生改变时它们才会重新求值。比如这里计算属性绑定的依赖就是message属性,一旦message属性发生改变时,那么计算属性就会重新求值,如果没有改变,那么计算属性将会缓存上一次的求值。这也意味着,如果计算属性绑定的是方法,那么计算属性不是响应式的。如下:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>mustache</title>
      </head>
      <body>
        <div id="app">
          <span>{{ date }}</span>
        </div>
        <script>
           var vm = new Vue({
              data:obj,
              computed:{
                  reverseMessage:function(){
                     return Date.now();
                  }
              }
            })
            vm.$mount(document.querySelector('#app'))
        </script>
      </body>
    </html>
复制代码

与调用方法相比,调用方法总会在页面重新渲染之后再次调用方法。我们为什么需要缓存,假设你要计算一个性能开销比较大的数组,而且如果其它页面也会依赖于这个计算属性,如果没有缓存,那么无论是读取还是修改都会去多次修改它的getter函数,这并不是我们想要的。

计算属性默认只有getter函数,让我们来尝试使用一下setter函数,如下:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>computed</title>
      </head>
      <body>
        <div id="app">
           <input type="text" v-model="name">
        </div>
        <script>
             var vm = new Vue({
                el: "#app",
                data: {
                  first_name: "li",
                  last_name: "qiang"
                },
                computed: {
                  name: {
                    get: function() {
                      return this.first_name + ' ' + this.last_name;
                    },
                    set: function(newValue) {
                      var names = newValue.split(' ');
                      this.first_name = names[0];
                      this.last_name = names[names.length - 1];
                    }
                  }
                }
            });
        </script>
      </body>
    </html>
复制代码

现在,我们只需要修改vm.name的值就可以看到first_namelast_name的值相应的也改变了。

在线示例

6.侦听器

虽然计算属性在大多数情况下更合适,但有时候也可以使用侦听器。vue通过watch选项提供一个方法来响应数据的变化。如:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>
        <title>watch</title>
        <style>
            img{
               width:200px;
               height:200px;
            }
        </style>
      </head>
      <body>
        <div id="app">
           <p>
              可以给我提出一个问题,然后我来回答?
              <input type="text" v-model="question">
           </p>
           <p>{{ answer }}</p>
           <img :src="answerImg" alt="答案" v-if="answerImg"/>
        </div>
        <script>
            var vm = new Vue({
                el:"#app",
                data(){
                    return{
                        answer:"我不能回答你除非你提出一个问题!",
                        question:"",
                        answerImg:""
                    }
                },
                created:function(){
                   // `_.debounce` 是一个通过 Lodash 限制操作频率的函数。
                   // 在这个例子中,我们希望限制访问 yesno.wtf/api 的频率
                   // AJAX 请求直到用户输入完毕才会发出。想要了解更多关于
                   // `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识,
                   // 请参考:https://lodash.com/docs#debounce
                   this.debounceGetAnswer = _.debounce(this.getAnswer,500);
                },
                //如果question值发生改变
                watch:{
                   question:function(oldValue,newValue){
                      this.answer="正在等待你停止输入!";
                      this.debounceGetAnswer();
                   }
                },
                methods:{
                    getAnswer:function(){
                       //如果问题没有以问号结束,则返回
                       if(this.question.indexOf('?') === -1){
                         this.answer = "提出的问题需要用问号结束!";
                         return;
                       }
                       this.answer = "请稍等";
                       var self = this;
                       fetch('https://yesno.wtf/api').then(function(response){
                           //fetch发送请求,json()就是返回数据
                           response.json().then(function(data) {
                              self.answer = _.capitalize(data.answer);
                              self.answerImg = _.capitalize(data.image);
                           });
                       }).catch(function(error){
                          self.answer = "回答失败,请重新提问!";
                          console.log(error);
                       })
                    }
                }
            })
        </script>
      </body>
    </html>     
复制代码

现在咱们来看一下效果:

在线示例查看。

7.计算属性vs侦听器

当在页面中有一些数据需要根据其它数据的变动而改变时,就很容易滥用侦听器watch。这时候命令式的侦听还不如计算属性,请看:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>watch</title>
      </head>
      <body>
        <div id="app">
           <p>{{ fullName }}</p>
        </div>
        <script>
             var vm = new Vue({
                  el:"#app",
                  data:{
                     firstName:"li",
                     lastName:"qiang",
                     fullName:"li qiang"
                  },
                  watch:{
                     firstName:function(val){
                        this.fullName = val + ' ' + this.lastName;
                     },
                     lastName:function(val){
                        this.fullName = this.firstName + ' ' + val;
                     }
                  }
            })
        </script>
      </body>
    </html>
复制代码

再看通过计算属性实现的:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <!-- 引入vue.js开发版本 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <title>computed</title>
      </head>
      <body>
        <div id="app">
           <p>{{ fullName }}</p>
        </div>
        <script>
             var vm = new Vue({
                el:"#app",
                data:{
                   firstName:"li",
                   lastName:"qiang"
                },
                computed:{
                   fullName:function(){
                     return this.firstNmae + ' ' + this.lastName;
                   }
                }
            })
        </script>
      </body>
    </html>
复制代码

通过计算属性实现的功能看起来更好,不是吗?你可以自行尝试具体示例(watch)具体示例(computed)进行对比。

ps:本文转载于从零开始学习vue

鄙人创建了一个QQ群,供大家学习交流,希望和大家合作愉快,互相帮助,交流学习,以下为群二维码:

转载于:https://juejin.im/post/5ce35beef265da1bc41426de

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值