从零开始学习vue

夕水

重要说明:本文会在我有空闲时间时持续更新,相当于是将官网的示例给完全呈现,是为了帮助初学者,也是为了巩固我自己的技术,我决定将官网给过滤一道消化,敬请期待。

一.介绍

vue是一种渐进式框架,被设计为自底向上逐层应用。所谓渐进式框架,我的理解就是vue是循序渐进的,一步一步的用。
举个简单的例子,就算我们不会webpack,不会node,但也能很快的入门。更多详情参阅渐进式

二.起步

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>
    //这里写JavaScript代码
    </script>
  </body>
</html>

js代码如下:

var app = new Vue({
  el:"#app",
  data:{
    message:"hello,world!"
  }
})

现在我们已经成功创建了第一个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>
        //这里写JavaScript代码
        </script>
      </body>
    </html>
    

js代码如下:

 var app = new Vue({
  el:"#app",
  data:{
    message:"页面加载于:" + new Date().toLocaleString()
  }
})
   

查看效果,前往此处查看效果:
同样的我们也可以修改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>
            //这里写JavaScript代码
            </script>
          </body>
        </html>
     

js代码如下:

  var app = new Vue({
       el:"#app",
       data:{
          seen:false
       }
   })   

尝试在控制台中修改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>
            //这里写JavaScript代码
            </script>
          </body>
        </html>
        

js代码如下:

   var app = new Vue({
       el:"#app",
       data:{
          list:[
            { name:"项目一",content:"HTML项目"},
            { name:"项目二",content:"CSS项目"},
            { name:"项目三",content:"JavaScript项目"},
          ]
       }
   })             
        
        

当然你也可以自己在控制台改变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>
            //这里写JavaScript代码
            </script>
          </body>
        </html>

js代码如下:

    var app = new Vue({
       el:"#app",
       data:{
          message:"hello,vue.js!"
       },
       methods:{
         reverseMessage:function(){
             //在这里this指向构造函数构造的vue实例
            this.message = this.message.split('').reverse().join('');
         }
       }
   }) 

反转信息的思路就是使用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>
            //这里写JavaScript代码
            </script>
          </body>
        </html>
        

js代码如下:

 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:{
         
       }
   })
   

这样,一个简单的组件就完成了,在这里,我们知道了,父组件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>
        //这里写JavaScript代码
    </script>
  </body>
</html>  

js代码如下:

      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("");
          }
        }
      });  

如此一来,无论我们怎么点击按钮,都不会将信息反转,甚至页面还会报错。
可前往此处具体示例自行查看效果。

当然除了数据属性以外,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>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

  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);
 })

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

3.实例生命周期

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

lifecycle.png

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

<!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>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

  var obj = {
     name:'eveningwater'
  }
  var app = new Vue({
    data:obj,
    beforeCreate:function(){
        //此时this指向app这个vue实例,但并不能得到data属性,因此this.name的值是undefined
        console.log('实例被创建之前,此时并不能访问实例内的任何属性' + this.name)
    }
  });
  

关于生命周期的全部理解,我们需要理解后续的组件知识,再来补充,此处跳过。这个示例可前往具体示例

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>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

 var obj = { greeting:"Hello,"};
 var vm = new Vue({
    data:obj
 });
 vm.$mount(document.getElementById('app'));
 

如此以来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>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

 var obj = { greeting:"Hello,"};
 var vm = new Vue({
    data:obj
 });
 vm.$mount(document.getElementById('app'));

在浏览器控制台中我们输入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>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

 var obj = { message:"<span style='color:#f00;'>hello,world!</span>"};
 var vm = new Vue({
    data:obj
 });
 vm.$mount(document.getElementById('app'));
 

页面效果如图所示;

图片描述

可前往具体示例

关于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>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

 var obj = { propId:"myDiv"};
 var vm = new Vue({
    data:obj
 });
 vm.$mount(document.getElementById('app'));
 

打开浏览器控制台,定位到该元素,我们就能看到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>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

 var obj = { isDisabled:123};
 var vm = new Vue({
    data:obj
 });
 vm.$mount(document.getElementById('app'));
 

这样一来,无论我们怎么点击按钮都没用,因为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>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

 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"));
  

这些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>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

  var obj = {
    number: 123,
    ok: true
  };
  var vm = new Vue({
    data: obj
  });
  vm.$mount(document.getElementById("app")); 
  

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

图片描述

不信可以自己试试具体示例

指令(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>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

  var obj = {
    value: 1
  };
  var vm = new Vue({
    el: "#app",
    data() {
      return obj;
    }
  });
  

运行在浏览器效果如图:

图片描述

现在你可以尝试在浏览器控制台更改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>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

var obj = {
   value:1
} 
var vm = new Vue({
    data:obj
});
vm.$mount(document.querySelector('#app'))

然后查看效果如图:

图片描述
尝试在控制台修改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>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

  var obj = {
    value: 1
  };
  var vm = new Vue({
    el: "#app",
    data() {
      return obj;
    }
  });
  

此时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>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

      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";
            }
          }
        }
      });

效果如图:

图片描述
你可以狠狠的点击具体示例查看。

从这几个示例我们也可以看出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>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

  var obj = {
    list:[
        {
            value:'html',
            active:false
        },
        {
            value:'css',
            active:false
        },
        {
            value:"javascript",
            active:true
        }
    ]
  };
  var vm = new Vue({
    el: "#app",
    data() {
      return obj;
    }
  });
  

虽然以上代码不会报错,但这会造成很大的渲染开销,因为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>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

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;
    }
  });      

如此一来,就减少了渲染开销,你可以狠狠点击这里具体示例查看。

指令的用法还远不止如此,一些指令是可以带参数的,比如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>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

  var obj = {
    url: "https://segmentfault.com/",
    src:"http://eveningwater.com/project/imggallary/img/15.jpg"
  };
  var vm = new Vue({
    el: "#app",
    data() {
      return obj;
    }
  }); 
  

效果如图所示:

图片描述
你可以点击此处具体示例查看。

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>
        //这里写JavaScript代码
    </script>
  </body>
</html>  

js代码:

 var obj = {
   message:"hello,vue.js!"
 }
 var vm = new Vue({
     data:obj
 })
 vm.$mount(document.querySelector('#app'))

第二个示例:

<!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>
        //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

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')) 
 
 

与第一个示例有所不同的就是在这个示例当中,我们申明了一个计算属性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>
        //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

 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'))
 

那么这两者有何区别呢?虽然两者的结果都一样,但计算属性是根据依赖进行缓存的,只有相关依赖发生改变时它们才会重新求值。比如这里计算属性绑定的依赖就是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>
        //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

 var vm = new Vue({
     data:obj,
     computed:{
         reverseMessage:function(){
            return Date.now();
         }
     }
 })
 vm.$mount(document.querySelector('#app'))

与调用方法相比,调用方法总会在页面重新渲染之后再次调用方法。我们为什么需要缓存,假设你要计算一个性能开销比较大的数组,而且如果其它页面也会依赖于这个计算属性,如果没有缓存,那么无论是读取还是修改都会去多次修改它的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>
        //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

   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];
        }
      }
    }
  }); 
  

现在,我们只需要修改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>
        //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

    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);
               })
            }
        }
    })
    

现在咱们来看一下效果:

图片描述
你可以狠狠点击此处具体示例查看。

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>
        //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

 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;
         }
      }
 })
 

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

 
<!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>
        //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

 var vm = new Vue({
      el:"#app",
      data:{
         firstName:"li",
         lastName:"qiang"
      },
      computed:{
         fullName:function(){
           return this.firstNmae + ' ' + this.lastName;
         }
      }
 })

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

8.class与style绑定

操作元素的classstyle是构建一个页面所常见的需求,因为它们都是属性,所以利用v-bind指令就可以操作元素的classstyle样式。如;

<!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">
     <!-- 引入vue.js开发版本 -->
     <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>class</title>
    <style>
        .font-red{
            color: #f00;
        }
    </style>
</head>
<body>
    <div id="app">
        <span v-bind:class="className">添加一个class类,改变字体颜色为红色。</span>
    </div>
    <script>
    //这里写JavaScript代码
    </script>
</body>
</html>

js代码如下;

var vm = new Vue({
    el:"#app",
    data:{
        className:"font-red"
    }
})

你可以狠狠点击此处具体示例 查看。

再来看一个简单绑定style的示例。

<!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">
     <!-- 引入vue.js开发版本 -->
     <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>style</title>
</head>
<body>
    <div id="app">
        <span v-bind:style="styleProp">改变元素的字体颜色为红色。</span>
    </div>
    <script>
    //这里写javascript代码
    </script>
</body>
</html>


js代码:

var vm = new Vue({
    el:"#app",
    data:{
        styleProp:"color:#f00;"
    }
})

你可以狠狠点击此处具体示例查看。

这只是classstyle的简单用法,vue.js专门在这方面做了增强,使得绑定classstyle 的值可以是对象,也可以是数组,如:

<!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">
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>class</title>
    <style>
      .font-red{
          color: #f00;
      }
      .font-blue{
          color: #00f;
      }
    </style>
</head>
<body>
    <div id="app">
        <span v-bind:class="{ 'font-red':isRed,'font-blue':isBlue }">改变字体的颜色</span>
    </div>
    <script>
      //这里写JavaScript代码
    </script>
</body>
</html>  

js代码:

  var vm = new Vue({
      el:"#app",
      data:{
          isRed:true,
          isBlue:false
      }
  })
  

我们可以看到页面效果如图:

图片描述

你还可以通过控制台修改vm.isBluevm.isRed的值,这充分说明这两个值是响应式的。如:

图片描述
你可以狠狠的点击此处具体示例查看。

同样的,style一样也可以使用对象语法,如:

<!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" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>style</title>
  </head>
  <body>
    <div id="app">
      <span v-bind:style="{ color:fontColor,fontSize:font18,'font-weight':fontBold }"
        >字体大小为18px,字体颜色为红色,并且加粗的字体。</span
      >
    </div>
    <script>
    //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

  var vm = new Vue({
    el: "#app",
    data: {
      fontColor: "#f00",
      font18: "18px",
      fontBold:"bold"
    }
  });
  

效果如图:

图片描述
我们一样可以修改其中的值,这些值也是响应式的,比如修改vm.fontColor="#0f0"就表示将字体颜色改变为蓝色。从上例我们也可以看出,我们可以使用驼峰式 (camelCase) 短横线分隔 (kebab-case,需要用单引号括起来)来定义css属性名。
你可以狠狠点击此处具体示例查看。

当然在更多时候,我们直接绑定一个对象更有利于让模板变得清晰,也方便我们理解。

<!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" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>style</title>
  </head>
  <body>
    <div id="app">
      <span v-bind:style="styleObject"
        >字体大小为18px,字体颜色为红色,并且加粗的字体。</span
      >
    </div>
    <script>
    //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

  var vm = new Vue({
    el: "#app",
    data: {
        styleObject:{
           fontSize:"18px",
           color:"#f00",
           'font-weight':"bold"
        }
    }
  });  
  

这也是一样的效果,你可以点击此处具体示例查看。

除了对象语法,数组语法也同样适用于classstyle,如:

<!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" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>class</title>
    <style>
      .font-red {
        color: #f00;
      }
      .font-18 {
        font-size: 18px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <span v-bind:class="[fontColor,fontSize]">颜色为红色大小为18px的字体</span>
    </div>
    <script>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

  var vm = new Vue({
    el: "#app",
    data: {
      fontColor: "font-red",
      fontSize: "font-18"
    }
  });
  

运行效果如图:
图片描述
你同样可以修改class的名字,诸如vm.fontColor="font-blue",这样页面就会将font-red更改为font-blue,这毕竟是响应式的。你可以狠狠点击此处具体示例查看。

同样的,style也能如此做,如:

<!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" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>style</title>
  </head>
  <body>
    <div id="app">
      <span v-bind:style="[colorF,sizeF]">颜色为红色大小为18px的字体</span>
    </div>
    <script>
      //javascript代码
    </script>
  </body>
</html>

js代码:

 var vm = new Vue({
    el: "#app",
    data: {
        colorF: {
            color:"#f00"
        },
        sizeF: {
            fontSize:"18px"
        }
    }
  });

这里尤其注意如下的写法是错误的,vue.js并不能渲染出样式:

  //这说明style绑定的数组项只能是一个对象,而不能是字符串
 var vm = new Vue({
    el: "#app",
    data: {
        colorF: "color:#f00;",
        sizeF: "font-size:18px;"
    }
  });

同样,我们注意修改值的时候也应该修改成一个对象,如:

vm.sizeF = {
   'font-size':"20px"
}

这点是需要注意的,另外在遇到带有前缀的css属性,如transition时,我们不必写前缀,因为vue会自动帮我们添加前缀。你可以狠狠点击此处具体示例查看。

style的用法还不止如此,我们还可以绑定多重值。如下:

<!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" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>style</title>
  </head>
  <body>
    <div id="app">
      <span v-bind:style="{ display:[webkitD,nomarD] }">颜色为红色大小为18px的字体</span>
    </div>
    <script>
     //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

 var vm = new Vue({
    el: "#app",
    data: {
        webkitD:"-webkit-flex",
        nomarD:"flex"
    }
  });
  

这样一来,浏览器会根据支持-webkit-flexflex而采用支持的写法,这个是在vue2.3.0+版本中增加的功能。你可以点击此处具体示例查看。

9.条件渲染

v-if指令用于条件性的渲染一块内容,这个指令只在它绑定的值为truthy的时候才会渲染内容。例如:

<!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="true">正常显示</p>
        <span v-if="false">不显示</span>
        <div v-if="show">也是正常显示</div>
        <a href="#" v-if="hidden">也是不显示</a>
    </div>
    <script>
      var vm = new Vue({
        el: "#app",
        data:{
            show:true,
            hidden:false
        }
      });
    </script>
  </body>
</html>

你可以狠狠点击此处具体示例查看效果。

v-if指令也可以与v-else指令结合使用,注意v-else必须紧跟v-if或者v-else-if之后。比如:

<!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="true">正常显示</p>
        <span v-else="false">不显示</span>
        <div v-if="show">也是正常显示</div>
        <a href="#" v-else>也是不显示</a>
    </div>
    <script>
      var vm = new Vue({
        el: "#app",
        data:{
            show:true
        }
      });
    </script>
  </body>
</html>

你可以狠狠点击此处具体示例查看效果。

v-if也可以直接在<template></template>标签上使用,这种情况下,我们通常是为了切换多个元素,因为v-if必须添加到一个元素上,而且会把template当作不可见元素来渲染,也就是说最终渲染不会包含template元素。比如:

<!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">
        <template  v-if="show">
            <p>呵呵呵</p>
            <h1>哈哈哈</h1>
            <div>嘻嘻嘻</div>
            <span>嘿嘿嘿</span>
        </template>
    </div>
    <script>
      var vm = new Vue({
        el: "#app",
        data:{
            show:true
        }
      });
    </script>
  </body>
</html>

你可以狠狠点击此处具体示例查看效果。

vue2.1.0新增了v-else-if,顾名思义,也就是紧跟v-if之后,v-else之前的指令,可以使用多个v-else-if指令。比如:

<!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="type===0">哈哈哈</p>
        <span v-else-if="type===1">嘿嘿嘿</span>
        <div v-else-if="type===2">嘻嘻嘻</div>
        <h2 v-else>呵呵呵</h2>
    </div>
    <script>
      var vm = new Vue({
        el: "#app",
        data:{
            type:0
        }
      });
    </script>
  </body>
</html>

你可以狠狠点击此处具体示例查看效果。在这些示例中,只要绑定的是在vue实例data选项中的数据,那么值就是响应式的,我们可以直接在控制台中修改,比如以上的vm.type = 1,我们就可以看到页面的的元素以及内容被改变,并重新渲染。

由于vue是简单的复用元素,而不是重新渲染元素,因此,这会让vue非常的高效,但这不可避免出现了一个问题,如下:

<!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">
        <template v-if="type==='name'">
            <label>姓名:</label>
            <input type="text" placeholder="请输入姓名">
        </template>
        <template v-else-if="type==='email'">
            <label>邮箱:</label>
            <input type="text" placeholder="请输入邮箱">
        </template>
        <template v-else>
            <label>密码:</label>
            <input type="password" placeholder="请输入密码">
        </template>
        <button type="button" @click="changeType">切换</button>
    </div>
    <script>
        var vm = new Vue({
            el: "#app",
            data: {
                type: "name",
                count:0
            },
            methods:{
                changeType:function(){
                    this.count++;
                    if(this.count % 3 === 0){
                        this.type = 'name';
                    }else if(this.count % 3 === 1){
                        this.type = 'email';
                    }else{
                        this.type="password"
                    }
                }
            }
        });
    </script>
</body>

</html>

你可以狠狠点击此处具体示例查看效果。在输入框中输入值,然后再点击切换按钮,你会发现input的内容并没有被清空,这也说明vue并不是重新渲染元素,而是高效的复用元素而已。再实际开发中,这样肯定是不符合需求的,那么我们应该如何解决这个问题呢?

还好,vue提供了一个key属性,我们只需要给每个复用的元素绑定一个key属性,用于区分它们是不同的元素。如下:

<!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">
        <template v-if="type==='name'">
            <label>姓名:</label>
            <input type="text" placeholder="请输入姓名" key="name">
        </template>
        <template v-else-if="type==='email'">
            <label>邮箱:</label>
            <input type="text" placeholder="请输入邮箱" key="email">
        </template>
        <template v-else>
            <label>密码:</label>
            <input type="password" placeholder="请输入密码" key="password">
        </template>
        <button type="button" @click="changeType">切换</button>
    </div>
    <script>
        var vm = new Vue({
            el: "#app",
            data: {
                type: "name",
                count:0
            },
            methods:{
                changeType:function(){
                    this.count++;
                    if(this.count % 3 === 0){
                        this.type = 'name';
                    }else if(this.count % 3 === 1){
                        this.type = 'email';
                    }else{
                        this.type="password"
                    }
                }
            }
        });
    </script>
</body>

</html>

现在你再尝试在输入框中输入值,然后点击切换按钮,就会发现值会被清空了。请点击具体示例查看效果。

需要注意的是label元素其实也是被复用了,因为它们没有添加key属性。

v-show的指令用法跟v-if差不多,唯一需要注意的区别就是v-show仅仅只是改变了元素的display属性而已,其DOM元素仍然存在于文档中,并且v-show之后没有v-else-ifv-else指令。看一个简单的示例:

<!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="true">正常显示</p>
        <span v-show="false">不显示</span>
        <div v-show="show">也是正常显示</div>
        <a href="#" v-show="hidden">也是不显示</a>
    </div>
    <script>
      var vm = new Vue({
        el: "#app",
        data:{
            show:true,
            hidden:false
        }
      });
    </script>
  </body>
</html>

具体效果如下图所示:

clipboard.png

你可以狠狠点击此处具体示例查看效果。

所以我们也可以看得出来v-ifv-show的区别:

clipboard.png

还要注意的一点就是不推荐v-ifv-for一起使用,因为它们同时使用的时候,v-for的优先级高于v-if

10.列表渲染

vue.js使用v-for指令来渲染一个列表,形式类似item in itemsitem of items,在这之中,item是数组中每一项的迭代名称,而items则是源数据,在渲染列表的时候,通常都要添加一个key属性,用以给vue.js一个提示,以便vue.js更好的跟踪每个节点的变化。key属性工作方式就像一个属性,因此使用v-bind指令来绑定,并且key属性也是唯一的。理想的key属性就是每一个数组项的唯一id。来看一个示例:

<!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">
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-for</title>
</head>
<body>
    <div id="app">
        <ul v-for="item in items">
            <li>{{ item }}</li>
        </ul>
    </div>
    <script>
      //这里写JavaScript代码
    </script>
</body>
</html>


js代码:

   var vm = new Vue({
      el:"#app",
      data:{
          items:['html','css','javascript']
      }
  })
  

数组中的数组项还可以是对象,如:

<!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">
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-for</title>
</head>
<body>
    <div id="app">
        <ul v-for="item in items">
            <li>{{ item.id }}.{{ item.value }}</li>
        </ul>
    </div>
    <script>
      //这里写JavaScript代码
    </script>
</body>
</html> 

js代码:

  var vm = new Vue({
      el:"#app",
      data:{
          items:[
              {
                  id:1,
                  value:"html"
              },
              {
                  id:2,
                  value:"css"
              },
              {
                  id:3,
                  value:"javascript"
              }
          ]
      }
  })
  

你可以狠狠点击具体示例一具体示例二查看。

这也就是说,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">
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-for</title>
</head>
<body>
    <div id="app">
        <ul v-for="(item,index) in items">
            <li>索引:{{ index }}-{{ item }}</li>
        </ul>
    </div>
    <script>
      //这里写JavaScript代码
    </script>
</body>
</html>

js代码如下:

   var vm = new Vue({
      el:"#app",
      data:{
          items:['html','css','javascript']
      }
  }) 
  

你可以点击此处具体示例查看。

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">
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-for</title>
</head>
<body>
    <div id="app">
        <ul v-for="(value,key,index) in info">
            <li>索引:{{ index }} + 属性名:{{ key }} + 属性值:{{ value }}</li>
        </ul>
    </div>
    <script>
      //这里写JavaScript代码
    </script>
</body>
</html> 


js代码:

   var vm = new Vue({
      el:"#app",
      data:{
          info:{
              name:"李白",
              value:"李太白"
          }
      }
  })
  

你可以狠狠点击此处具体示例查看。

为了vue.js 能够高效的更新虚拟DOM,我们有必要给vuev-for提供一个key属性,理想的key属性就是每一项的id。这是一个属性,所以使用v-bind来绑定,添加key属性是方便vue跟踪每个节点,从而根据数据的变化来重排序节点,要理解key属性的意义,你可能需要理解虚拟DOM的DIFF算法的知识。虽然vue.js默认就采用就地复用的策略,但这只针对不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出,所以最好的方法就是添加key属性。

 <!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 in items" :key="item.id" :class="item.value">
            <p>{{ item.value }}</p>
        </div>
    </div>
    <script>
        //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

 var vm = new Vue({
    el: "#app",
    data() {
      return{
          items:[
              {
                  id:1,
                  value:"html"
              },
              {
                  id:2,
                  value:"css"
              },
              {
                  id:3,
                  value:"javascript"
              }
          ]
      }
    }
  }); 
  

为了方便理解key属性带来的高效性,可以尝试在控制台更改数据,比如增删改查操作。你可以点击此处具体示例查看。

vue包含一组数组的变异方法,他们也可以让视图随着数据的改变而更新。方法如下:
push(),pop(),unshift(),shift(),splice(),reverse(),sort()。我们可以来看一个示例:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>mutation methods</title>
  </head>
  <body>
    <div id="app">
        <ul>
            <li v-for="(item,index) in items" :key="index">{{ item.name }}</li>
        </ul>
    </div>
    <script>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码如下:

    var vm = new Vue({
        el:"#app",
        data:{
            items:[
                {
                    id:1,
                    name:"html"
                },
                {
                    id:2,
                    name:"css"
                },
                {
                    id:3,
                    name:"javascript"
                }
            ]
        }
  });
  //vm.items.push({ id:4,name:"java"})
  //vm.items.pop()
  //vm.items.shift()
  //vm.items.unshift({ id:4,name:"java"})
  //vm.items.splice(0,1,{ id:1,name:"java"})
  //vm.items.reverse()

尝试在控制台,使用这些变异方法修改数组items的值,我们就可以看到这些方法的作用了。你可以狠狠点击此处具体示例查看。

变异的方法,顾名思义,就是会改变原数组,既然有变异方法,那当然也有非变异的方法,比如:filter(),slice()concat(),虽然这些方法不会改变一个数组,但总是会返回要给新数组,我们可以用新数组替换原数组。如:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>mutation methods</title>
  </head>
  <body>
    <div id="app">
      <ul>
        <li v-for="(item,index) in items" :key="index">{{ item.name }}</li>
      </ul>
    </div>
    <script>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码如下:

var vm = new Vue({
    el: "#app",
    data: {
      items: [
        {
          id: 1,
          name: "html"
        },
        {
          id: 2,
          name: "css"
        },
        {
          id: 3,
          name: "javascript"
        }
      ]
    }
  });
  //   vm.items = vm.items.filter(function(item) {
  //     return item.name.match(/javascript/);
  //   });
//   vm.items = vm.items.concat([{ id: 4, name: "java" }]);
// vm.items = vm.items.slice(1); 

尝试在控制台将以上注释的js代码给测试一下,你可以狠狠点击此处具体示例尝试一下。

通过以上示例,你可能会认为vue抛弃了原数组,重新渲染了DOM,但实际上并不是这样,这也说明这样替换数组是非常高效的一个操作。

虽然我们可以使用变异方法修改数组,从而达到视图的更新,但我们也要注意两点,那就是以下两点并不会触发视图的更新:

1.根据数组的索引来修改数组项的值。示例如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>limit</title>
  </head>
  <body>
    <div id="app">
      <ul>
        <li v-for="(item,index) in items" :key="index">{{ item.name }}</li>
      </ul>
    </div>
    <script>
        //这里写JavaScript代码
    </script>
  </body>
</html>

js代码如下:

  var vm = new Vue({
    el: "#app",
    data: {
      items: [
        {
          id: 1,
          name: "html"
        },
        {
          id: 2,
          name: "css"
        },
        {
          id: 3,
          name: "javascript"
        }
      ]
    }
  });
//vm.items[0].value = "java";并不会触发视图的更新。

你可以点击此处具体示例亲自查看。

2.修改数组的长度。如以下示例:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>limit</title>
  </head>
  <body>
    <div id="app">
      <ul>
        <li v-for="(item,index) in items" :key="index">{{ item.name }}</li>
      </ul>
    </div>
    <script>
        //这里写JavaScript代码
    </script>
  </body>
</html>

js代码如下:

  var vm = new Vue({
    el: "#app",
    data: {
      items: [
        {
          id: 1,
          name: "html"
        },
        {
          id: 2,
          name: "css"
        },
        {
          id: 3,
          name: "javascript"
        }
      ]
    }
  });
//vm.items.length= 2;并不会触发视图的更新。  

你同样可以点击此处具体示例进行查看。

针对第一个问题,我们可以通过Vue.set()方法和变异方法splice()方法来解决,如下:

 //第一种方案
Vue.set(vm.items[0],'name','java')
//或this.$set(vm.items[0],'name','java');
//第二种方案
vm.items.splice(0,1,{ id:1,name:"java"});

你可以点击此处具体示例查看。

针对第二个问题,你可以使用splice()方法来改变。如下:

vm.items.splice(2); 

你可以点击此处具体示例进行查看。

从以上的示例,我们可以看出Vue.set()this.$set()[只是一个别名而已]的参数可以传3个,即要改变的数组项,数组项的索引或者属性名,新数组项的属性值。而splice()则可以传1到3个参数,即数组的起始项索引,删除几项,从第三个参数开始就是需要替换的项或者说是需要添加的项

对于对象,其实也仍然有限制,对象的新增和删除,vue是无法检测的。来看如下例子:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>limit</title>
  </head>
  <body>
    <div id="app">
      <ul>
        <li>{{ item.value }}</li>
        <li>{{ item.name }}</li>
      </ul>
    </div>
    <script>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

var vm = new Vue({
    el: "#app",
    data: {
      item: {
          value:"123"
      }
    }
  });
  //vm.item.name = "javascript";//无效
  //delete vm.item.value;//无效

你可以狠狠点击此处具体示例查看。

针对以上属性的添加的问题,我们还是使用Vue.set()方法来解决。而属性的删除,我们可以使用Vue.delete()来解决,理论上在项目开发中应该很少用到这个方法。

//对象属性的添加
Vue.set(vm.item,'name','javascript');//或this.$set(vm.item,'name','javascript'); 
//对象属性的删除
Vue.delete(vm.item,'value');

当我们需要添加多个对象时,可以使用Object.assign()或_.extend()[jquery方法],如下:

vm.item = Object.assign({},vm.item,{ age:26,skill:['html','css','javascript']})
    //或者vm.item = $.extend({},vm.item,{ age:26,skill:['html','css','javascript']})

你可以狠狠点击此处具体示例查看。

有时候,在某种情况下,我们会需要过滤或者排序一个数组,并且不能改变原数组,从而渲染出满足条件的数组项,这时候,我们可以通过计算属性结合数组的迭代方法filter() 这个方法不会改变原数组的值。我们来看一个示例:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>filter array</title>
  </head>
  <body>
    <div id="app">
        <ul>
            <li v-for="(item,index) in filterNumbers" :key="index">{{ item }}</li>
        </ul>
    </div>
    <script>
     //这里写JavaScript代码
    </script>
  </body>
</html>

js代码如下:

 var vm = new Vue({
        el:"#app",
        data:{
            items:[1,2,3,4,5,6,7,8,9]
        },
        computed:{
            filterNumbers(){
                return this.items.filter((num) => {
                    return num < 5;
                })
            }
        }
  });
  
  

你可以狠狠点击此处具体示例查看效果。
当然,你也可以在计算属性不适用的情况下,使用方法来替代。如:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>filter array</title>
  </head>
  <body>
    <div id="app">
        <ul>
            <li v-for="(item,index) in filterNumbers(items)" :key="index">{{ item }}</li>
        </ul>
    </div>
    <script>
     //这里写JavaScript代码
    </script>
  </body>
</html>

js代码如下:

 var vm = new Vue({
        el:"#app",
        data:{
            items:[1,2,3,4,5,6,7,8,9]
        },
        methods:{
            filterNumbers(numbers){
                return numbers.filter((num) => {
                    return num < 5;
                })
            }
        }
  });
  

你可以狠狠点击此处具体示例查看效果。

也许有时候,我们是没必要渲染每一个父元素的,只需要渲染子元素的,这时候我们就可以使用template元素来使用v-for指令渲染元素,渲染的数据也不一定是一个数组,也可以是一个整数。需要注意的就是当添加key属性时,需要将这个属性添加到真实的DOM元素上,这也就是说template元素是不推荐使用key属性的。如下:

<!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">
        <ul>
            <template v-for="n in number" >
                <span :key="n">{{ n }}</span>
            </template>
        </ul>
    </div>
    <script>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码如下:

  var vm = new Vue({
        el:"#app",
        data:{
            number:100
        },  
  });

你可以狠狠点击此处具体示例查看效果。

v-ifv-for指令结合一起使用时,由于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-for with v-if </title>
  </head>
  <body>
    <div id="app">
        <ul>
            <li v-for="(item,index) in items" :key="index" v-if="item.show">
                <span >{{ item.value }}</span>
            </li>
        </ul>
    </div>
    <script>
     //这里写JavaScript代码
    </script>
  </body>
</html>

js代码如下:

 var vm = new Vue({
        el:"#app",
        data:{
            items:[
                {
                    show:true,
                    value:'html'
                },
                {
                    show:false,
                    value:"css"
                },
                {
                    show:false,
                    value:"javascript"
                }
            ]                
        },  
  });

你可以狠狠点击此处具体示例进行查看。

v-for还可以用在组件当中,但在组件中使用v-for是必须要加上key属性的。如下是一个简易版的todolist组件示例:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>todolist demo</title>
  </head>
  <body>
    <div id="app">
      <ul>
        <form @submit.prevent="addNewToDo">
          <span>add a need to do thing</span>
          <input type="text" v-model="newToDo" placeholder="eg:write the code"/>
          <button type="button" @click="addNewToDo">add</button>
        </form>
        <todo-item         
          v-for="(item,index) in items" :key="index"
          :item="item"
          :index="index"
          @on-remove="items.splice(index,1)"
        ></todo-item>
      </ul>
    </div>
    <script>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码如下:

Vue.component("todoItem", {
    template: `
        <div>
            <li v-if="item.show">
                <span>{{ item.value }}</span>
                <button type="button" @click="$emit('on-remove')">remove</button>
            </li>
        </div>
      `,
    props: ["item"]
  });
  var vm = new Vue({
    el: "#app",
    data: {
      newToDo: "",
      items: [
        {
          show: true,
          value: "html"
        },
        {
          show: true,
          value: "css"
        },
        {
          show: true,
          value: "javascript"
        }
      ]
    },
    methods: {
      addNewToDo() {
        if (this.newToDo) {
          this.items.push({
            value: this.newToDo,
            show: true
          });
        }else{
            alert('please input your need to do thing!')
        }
      }
    }
  }); 
  

你可以狠狠点击此处具体示例进行查看。

11.事件处理

Vue中使用v-on(简写@)指令来为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-on</title>
  </head>
  <body>
    <div id="app">
        <button v-on:click="count += 1">click me!</button>
      <p>The button above has been clicked {{ count }} times!</p>
    </div>
    <script>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码:

 var vm = new Vue({
    data:{
        count:0
    }
  });
  vm.$mount(document.getElementById("app"));
  

你可以狠狠点击此处具体示例查看效果。

很多时候,事件的复杂程度远不止如此,因此,事件可以用方法来表示。如:

<!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">
        <button v-on:click="clickMethod">click me!</button>
      <p>The button above has been clicked {{ count }} times!</p>
    </div>
    <script>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码如下:

  var vm = new Vue({
    data:{
        count:0
    },
    methods:{
        clickMethod:function(event){
            //this指向vm这个vue实例
            this.count += 1;
            alert('你点击了按钮' + this.count + '次!');
            alert(event.target.tagName);
        }
    }
  });
  //还可以直接调用方法
//   vm.clickMethod();
  vm.$mount(document.getElementById("app"));

你可以狠狠点击此处具体示例查看效果。

除了直接绑定方法,当然还可以在内联JavaScript语句中调用方法。如:

<!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">
      <button v-on:click="clickMethod(msg1)">click me!</button>
      <button v-on:click="clickMethod(msg2)">click me!</button>
    </div>
    <script>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码如下:

var vm = new Vue({
    data: {
      msg1:'hello,',
      msg2:"world!"
    },
    methods: {
      clickMethod: function(message) {
        alert(message)
      }
    }
  });
  vm.$mount(document.getElementById("app"));

你可以狠狠点击此处具体示例查看效果。

当然有时候我们需要访问原生DOM事件,这时候可以使用一个特殊的变量$event传入方法。如:

<!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">
      <button v-on:click="clickMethod(msg,$event)">click me!</button>
    </div>
    <script>
      //这里写JavaScript代码
    </script>
  </body>
</html>

js代码如下:

var vm = new Vue({
    data: {
      msg: "hello,world!"
    },
    methods: {
      clickMethod: function(message, event) {
        //现在你可以访问原生的事件对象
        if (event) event.preventDefault();
        alert(message);
      }
    }
  });
  vm.$mount(document.getElementById("app")); 

你可以狠狠点击此处具体示例查看效果。

在事件中阻止事件的默认行为或者阻止事件冒泡是非常常见的需求,通常阻止事件的默认行为,都可以在事件方法内调用事件对象的preventDefault()方法。例如:

   var myDiv = document.getElementById('myDiv');
   myDiv.onclick = function(event){
       //event即事件对象
       event.preventDefault();
   }

再比如阻止事件冒泡,即调用stopPropagation()。例如以下一个示例:

<!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>Document</title>
    <style>
      #parent,#child{
        margin: 0;padding: 0;
      }
      #parent{
        width: 200px;
        height: 200px;
        text-align: center;
        line-height: 200px;
        background-color: #ff0000;
      }
      #child{
        width: 100px;
        height: 100px;
        text-align: center;
        line-height: 100px;
        background-color: #00ff00;
      }
    </style>
  </head>
  <body>
    <div id="parent">
        父元素
        <div id="child">子元素</div>
    </div>
    <script>
      parent.onclick = function(){
         alert('点击了父元素!')
      }
      child.onclick = function(e){
        // e.stopPropagation();
        alert('点击了子元素!')
      }
    </script>
  </body>
</html>

如果不对子元素调用stopPropagation(),那么父元素的事件就会传播到子元素上,也就是说会弹出两个弹出框,父元素的事件冒泡到了子元素上,而如果调用该方法,则表明父元素的事件被阻止冒泡了,结果点击子元素也就只弹出一个弹出框提示"点击了子元素",这是在原生当中的用法,而vue则提供了事件修饰符。
事件修饰符以点开头加上指令后缀。包含.stop,.prevent,.capture,.self,.once,.passive等事件修饰符。

<!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>
    <style>
        #parent{
            width: 200px;
            height: 200px;
            background-color: #ff0000;
            text-align: center;
            line-height: 200px;
        }
        #child{
            width: 100px;
            height: 100px;
            background-color: #00f000;
            text-align: center;
            line-height: 100px;
            display: inline-block;
        }
    </style>
  </head>
  <body>
    <div id="app">
       <div id="parent" @click="clickParent">
           父元素
           <div id="child" @click="clickChild">子元素</div>
           <!--<div id="child" @click.stop="clickChild">子元素</div> -->
       </div>
       <p>{{ count }}</p>
    </div>
    <script>
      var vm = new Vue({
        data:{
            count:0
        },
        methods:{
            clickParent(){
                this.count++;
            },
            clickChild(){
                this.count = 4;
            }
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

对比加了.stop和不加.stop的效果如下图所示:

//加了

图片描述

//不加

图片描述

你可以狠狠点击此处具体示例查看效果。

.prevent事件修饰符其实也就是阻止事件的默认行为,比如:

<!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">
     <form action="" method="post">
         <input type="text" placeholder="请输入账号">
         <input type="password" placeholder="请输入密码">
         <input type="submit" value="提交" @click.prevent="submit">
     </form>
    </div>
    <script>
      var vm = new Vue({
        data: {
         
        },
        methods: {
            submit(){
                alert('阻止了表单提交');
            }
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

尝试将.prevent事件修饰符去掉,你会发现当你点击了提交按钮提交的时候,页面会重载。你可以狠狠点击此处具体示例进行查看。

.capture修饰符的作用就是让元素最先执行事件,如果有多个元素拥有该事件修饰符,那么则由外到内依次触发该事件。如下例:

<!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">
      <div @click.capture="doWhat">
        点击我
        <p @click.capture="doWhat">
          <span @click="doWhat">请点击我</span>
          <br>
          点击我啊
        </p>
      </div>
      <span>{{ count }}</span>
    </div>
    <script>
      var vm = new Vue({
        data: {
          count: 0
        },
        methods: {
          doWhat() {
            this.count++;
          }
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

当点击最外层的div元素时,触发最外层的事件,当点击到p元素(不是span元素),则先执行div的事件,然后再执行p元素,因此count的值就会相加两次。同理,点击span元素,则相加三次。你可以狠狠点击此处具体示例查看效果。

.self事件修饰符在只有event.target的值为自身元素的时候才会触发,类似.stop事件修饰符,该事件修饰符阻止了事件的冒泡,因为event.target的值始终只会有一个值。如下例:

<!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">
      <div @click.self="doWhat($event)">
        点击我
        <p @click.self="doWhat($event)">
          <span @click="doWhat($event)">请点击我</span>
          <br>
          点击我啊
        </p>
      </div>
      <span>{{ count }}</span>
    </div>
    <script>
      var vm = new Vue({
        data: {
          count: 0
        },
        methods: {
          doWhat(e) {
              console.log(e.target)
            this.count++;
          }
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

如果点击div元素,那么event.target的值就是div元素,因此也就触发该元素的事件,count的值就加1,同理,点击pspan元素就是一样的道理,你可以狠狠点击此处具体示例查看效果。

.once事件修饰符只允许事件执行一次。如:

<!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">
      <button v-on:click.once="clickMethod">click me!</button>
      <p>{{ count }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
          count:0
        },
        methods: {
          clickMethod: function() {
            this.count++;
          }
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

运行点击按钮,我们会发现count的值只会加1,不管点击按钮多少次,都没有用,这也就是说事件只会执行一次。你可以狠狠的点击此处具体示例查看效果。

.passive事件修饰符主要是为了提升页面滑动的性能,主要是对passive事件监听器的一个封装,要理解这个事件修饰符,我们需要知道原生的Passive Event Listeners。这是chrome提出的一个新的浏览器特性:Web开发者通过一个新的属性passive来告诉浏览器,当前页面内注册的事件监听器内部是否会调用preventDefault函数来阻止事件的默认行为,以便浏览器根据这个信息更好地做出决策来优化页面性能。当属性passive的值为true的时候,代表该监听器内部不会调用preventDefault函数来阻止默认滑动行为,Chrome浏览器称这类型的监听器为被动(passive)监听器。目前Chrome主要利用该特性来优化页面的滑动性能,所以Passive Event Listeners特性当前仅支持mousewheel/touch相关事件。更多详情参阅。我们来看一个示例:

<!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>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      section {
        width: 100%;
        height: 500px;
      }
      #sec-1 {
        background-color: #ff0000;
      }
      #sec-2 {
        background-color: #00ff00;
      }
      #sec-3 {
        background-color: #0000ff;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <section
        v-for="(item,index) in items"
        :key="index"
        :id="'sec-' + (index + 1)"
        @mousewheel="onScroll"
      >
        {{ item.content }}
      </section>
    </div>
    <script>
      var vm = new Vue({
        data: {
          items: [
            { content: "页面内容一" },
            { content: "页面内容二" },
            { content: "页面内容三" }
          ]
        },
        mounted() {},
        methods: {
          onScroll() {
            console.log("页面滚轮滚动!");
            //如果去掉注释,加了passive事件修饰符,页面还能流畅的滑动,但是如果不加,就不会流畅的滑动了
            // for (let i = 0; i < 1; i--) {
            //   console.log(i);
            // }
          }
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

如以上注释所示,如果给section加了passive事件修饰符,并且将注释去掉,我们会发现页面还是能流畅的滑动的。你可以狠狠点击此处具体示例查看效果。

事件修饰符也可以组合使用,但组合的顺序非常重要,比如@click.self.prevent@click.prevent.self完全是两个不同的概念,前者会阻止event.target的值指向的那个元素的所有点击效果,因为是先执行.prevent事件修饰符,即阻止了该元素的所有点击事件。而后者则只是阻止了event.target的值指向的那个元素的点击事件,而不会阻止默认事件的执行。如下例:

<!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">
      <div @click="clickDiv1">
        <a href="https://www.baidu.com/"  @click.prevnt.self="clickA">
          <div @click="clickDiv2">点击我</div>
        </a>
        <!-- 这两个事件修饰符互换顺序是不同的效果的:<a href="https://www.baidu.com/"  @click.self.prevent="clickA">
          <div @click="clickDiv2">点击我</div>
        </a> -->
      </div>
    </div>
    <script>
      var vm = new Vue({
        data: {
          
        },
        methods: {
          clickDiv1(){
            alert('点击了div1!');
          },
          clickA(){
            alert('点击了a标签!')
          },
          clickDiv2(){
            alert('点击了div2!')
          }
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

你可以狠狠点击此处具体示例查看效果。

此外,值得注意的就是,不能将.passive.prevent事件修饰符组合在一起使用。否则浏览器会给出一个警告,如下图所示:

图片描述

在监听键盘事件时,往往需要监听键盘的键值,vue则提供了按键修饰符,允许为键盘事件添加键值。如:

<!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">
        <button v-on:keyup.13="clickMethod($event)">键盘按键事件</button>
        <p>你按下的键值为:{{ count }}</p>
    </div>
    <script>
      var vm = new Vue({
        data:{
            count:0
        },
        methods:{
            clickMethod(e){
                console.log(e.keyCode);
                this.count = e.keyCode;
            }
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

你可以狠狠点击此处具体示例查看效果。

13是键盘上enter键的键值,通常我们不容易记住键盘的键值,这时候vue提供了别名。如以上的键盘事件可以改写为:

v-on:keyup.enter //@keyup.enter

目前已有的全部按键别名有.enter,.tab,.delete,.esc,.space,.up,.down,.left,.right,当然我们还可以通过全局去自定义别名。
比如设置数字键0(字母键的正上方的数字键,键值为48)的别名,写法如:Vue.config.keyCodes.num0 = 48。如示例:

<!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">
      <button v-on:keyup.num0="clickMethod($event)">键盘按键事件,鼠标定位到按钮上</button>
      <p>你按下的键值为:{{ count }}</p>
    </div>
    <script>
      Vue.config.keyCodes.num0 = 48;
      var vm = new Vue({
        data: {
          count: 0
        },
        methods: {
          clickMethod(e) {
            console.log(e.keyCode);
            this.count = e.keyCode;
          }
        }
      });

      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

你可以狠狠点击具体示例查看效果。

也可以通过直接将keyboardEvent.key暴露的任意有效键名转换成kebab-case(短横线命名)来作为修饰符,比如键盘上的PageDown键可以写成page-down,PageUp可以写成page-up,End可以写成end等。如下例:

<!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">
      <button v-on:keyup.end="clickMethod($event)">键盘按键事件,鼠标定位到按钮上</button>
      <p>你按下的键值为:{{ count }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
          count: 0
        },
        methods: {
          clickMethod(e) {
            console.log(e.keyCode);
            this.count = e.keyCode;
          }
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

将鼠标定位到按钮上,按住键盘上的end键,查看效果。你可以狠狠点击此处具体示例查看效果。

值得注意的就是,有一些按键(.esc以及所有的方向键)在IE9中有不同的key值,如果想支持IE9,它们的内置别名应该是首选。

除了常用键以外,还有四个系统修饰键,即.ctrl,.alt,.shift,.meta(在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。在 Sun 操作系统键盘上,meta 对应实心宝石键 (◆)。在其他特定键盘上,尤其在 MIT 和 Lisp 机器的键盘、以及其后继产品,比如 Knight 键盘、space-cadet 键盘,meta 被标记为“META”。在 Symbolics 键盘上,meta 被标记为“META”或者“Meta”),这四个键通常是和其他常规按键一起组合使用,也就是说只有当按下这四个系统修饰键,再按下相应的常规键,才能触发键盘事件,而且这四个键单独使用是没有效果的。要想让四个键单独有效果,那么你就只能使用keyCode
比如@keyup.ctrl你应该写成@keyup.17这样才会单独按下ctrl键,发生键盘事件,产生效果。所以,我们只能像下面那样使用:

 <!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">
      <button v-on:keyup.shift.num0="clickMethod($event)">键盘按键事件,鼠标定位到按钮上</button>
      <p>你按下的键值为:{{ count }}</p>
    </div>
    <script>
      Vue.config.keyCodes.num0 = 48;
      var vm = new Vue({
        data: {
          count: 0
        },
        methods: {
          clickMethod(e) {
            console.log(e.keyCode);
            this.count = e.keyCode;
          }
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

鼠标光标定位到按钮上,按下shift键,再同时按下数字键0(左上字母键对应的数字键0),就能看到效果呢。你可以狠狠点击此处具体示例查看效果。

当然我们也可以将系统修饰符组合使用到非键盘事件,比如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">
      <button v-on:click.ctrl="clickMethod($event)">键盘按键事件,鼠标定位到按钮上</button>
      <p>事件名为:{{ eventname }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
          eventname: 0
        },
        methods: {
          clickMethod(e) {
            this.eventname = e.type;
          }
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

鼠标定位到按钮上,首先单独点击按钮是没有效果的,需要按住键盘上的ctrl键,然后再点击按钮,才能产生效果。你可以狠狠点击此处具体示例查看效果。

由此看来,单独使用.exact修饰符,就表示只要按下了系统修饰键,就不会执行点击事件。如下例:

<!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">
      <button @click.exact="clickMethod($event)">键盘按键事件,鼠标定位到按钮上</button>
      <p>你按下的键值为:{{ count }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
          count: 0
        },
        methods: {
          clickMethod(e) {
            this.count = e.type;
          }
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

当按住键盘上的ctrl,shift,meta,alt键,然后再点击按钮就不会有效果。你可以狠狠点击此处具体示例查看效果。

除了常用键盘修饰符以及系统修饰符之外,vue还提供了鼠标按钮修饰符,用于指定鼠标哪个按键按下才会执行事件。鼠标按钮修饰符有.left,.middle,.right。如下:

<!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">
      <button @click.middle="clickMethod($event)">按钮</button>
      <p>鼠标按钮按住中间的滚轮键触发:{{ event }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
          event:""
        },
        methods: {
          clickMethod(e) {
            this.event = e.type;
          }
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

用鼠标滚轮键点击按钮,就可以