该账号已被查封

该账号已被查封 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

该账号已被查封 收藏了文章 · 7月20日

从零开始学习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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 :data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script data-original="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 :data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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>

用鼠标滚轮键点击按钮,就可以查看效果,你可以狠狠点击此处具体示例查看效果。

12.表单输入绑定

vue可以通过v-model指令在<input>,<select>,<textarea>这三种表单元素上创建数据双向绑定,这个指令会根据表单控件类型选择正确的方法来更新元素。v-model只不过是一个语法糖,通过监听用户输入事件从而达到更新数据的目的,这在一些前端极端场景中尤为重要。

在使用v-model指令时,需要注意它会忽略所有表单元素中的valued,selected,checked等特性的初始值,而将vue实例的data对象作为数据初始值,所以在使用过程中需要在data选项中声明初始值。

一个最简单的示例如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
  </head>
  <body>
    <div id="app">
      <input type="text" v-model="message" />
      <p>绑定的数据:{{ message }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
          message: "hello,vue.js!"
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

当用户在输入框中输入数据的时候,对应绑定在data选项中的数据就会发生改变。你可以狠狠点击此处具体示例查看效果。

还可以绑定在textarea标签上,如:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
  </head>
  <body>
    <div id="app">
      <textarea type="text" v-model="message"></textarea>
      <p>绑定的数据:{{ message }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
          message: "hello,vue.js!"
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

绑定到单个复选框示例如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
  </head>
  <body>
    <div id="app">
      <input type="checkbox" v-model="isChecked">
      <p>是否选中:{{ isChecked }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
            isChecked: false
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

除此之外,还可以将多个复选框绑定到一个数组,如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
  </head>
  <body>
    <div id="app">
      <input type="checkbox" id="name" value="eveningwater" v-model="checkArr"/>
      <label for="name">eveningwater</label>
      <input type="checkbox" id="sex" value="male" v-model="checkArr"/>
      <label for="sex">male</label>
      <input type="checkbox" id="skill" value="write code" v-model="checkArr" />
      <label for="skill">write code</label>
      <p>选中的值:{{ checkArr }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
            checkArr: []
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

还可以绑定单选按钮,如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
  </head>
  <body>
    <div id="app">
      <input type="radio" id="first" value="first" v-model="checkedValue">
      <label for="first">first</label>
      <input type="radio" id="second" value="second" v-model="checkedValue" />
      <label for="second">second</label>
      <p>选中的值:{{ checkedValue }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
            checkedValue: []
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

绑定到select的示例如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
  </head>
  <body>
    <div id="app">
      <select v-model="message">
          <option disabled value="">请选择值</option>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
      </select>
      <p>选中的值:{{ message }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
          message: ""
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

在这里需要注意的就是,如果v-model绑定的初始值未匹配任何选项,那么select元素就会渲染为'未选中状态',在IOS中,这会导致用户无法选中第一项,因为并没有触发change事件,因此更推荐像以上那样提供一个默认禁用的选项。

select多选时:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
  </head>
  <body>
    <div id="app">
      <select v-model="message" multiple>
          <option disabled value="">请选择值</option>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
      </select>
      <p>选中的值:{{ message }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
          message: []
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

还可以用v-for指令渲染动态选项:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
  </head>
  <body>
    <div id="app">
      <select v-model="selected">
        <option v-for="(option,index) in selects" :value="option.value">{{
          option.text
        }}</option>
      </select>
      <p>选中的值:{{ selected }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
          selected: "first",
          selects: [
            {
              value: "first",
              text: "第一项"
            },
            {
              value: "second",
              text: "第二项"
            },
            {
              value: "third",
              text: "第三项"
            }
          ]
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

对于单选按钮,复选框,选择框来说,v-model指令通常绑定的是静态字符串,对于复选框,还可以是布尔值。它们的value值,我们也是可以绑定到vue实例的data选项上的,而且绑定的值不一定是静态字符串。一个完整的示例如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      .form-radio,
      .form-checkbox,
      .form-select {
        width: 600px;
        padding: 16px 8px;
        background-color: #e2e3e4;
        margin: 15px auto;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="form-radio">
        <input type="radio" id="male" v-model="sex" value="男" />
        <label for="male">男</label>
        <input type="radio" id="female" v-model="sex" value="女" />
        <label for="female">女</label>
        <p>你选择的是:{{ sex }}</p>
        <input type="radio" id="male" v-model="bindSex" v-bind:value="bindMale" />
        <label for="male">{{ bindMale }}</label>
        <input type="radio" id="female" v-model="bindSex" v-bind:value="bindFemale" />
        <label for="female">{{ bindFemale}}</label>
        <p>将value值动态绑定到vue实例上:{{ bindSex }}</p>
      </div>
      <div class="form-checkbox">
        <input type="checkbox" id="checked" v-model="isChecked" />
        <label for="checked">绑定的是布尔值</label>
        <p>布尔值:{{ isChecked }}</p>
        <input
          type="checkbox"
          v-model="toggle"
          true-value="是"
          false-value="否"
        />
        <p>是否选中:{{ toggle }}</p>
      </div>
      <div class="form-select">
        <select v-model="selected">
          <option value="请选择你喜欢做的事情" disabled
            >请选择你喜欢做的事情</option
          >
          <option
            v-for="(option,index) in options"
            :key="index"
            :value="option.text"
            v-text="option.text"
          ></option>
        </select>
        <p>你喜欢:{{ selected }}</p>
      </div>
    </div>
    <script>
      var vm = new Vue({
        el: "#app",
        data: {
          bindSex: "",
          bindMale: "男",
          bindFemale: "女",
          isChecked: false,
          sex: "男",
          toggle: "是",
          selected: "",
          options: [
            {
              text: "读书"
            },
            {
              text: "游戏"
            },
            {
              text: "唱歌"
            }
          ]
        }
      });
      console.log(vm.toggle === "是");
    </script>
  </body>
</html>

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

在表单绑定的时候,vue也提供了3个修饰符.lazy,.number,.trim,.lazy的作用就是改变表单元素触发的input事件,一般说来,v-model通常是在每次input事件(即用户只要输入内容时),将输入框的值与绑定的数据进行同步更新,如果不想要与input事件同步,而是与change事件同步,那么就可以使用.lazy修饰符,.number修饰符的作用就是将输入的值转换成number类型,因为默认输入的值时string(字符串)类型。而.trim修饰符的作用就是自动过滤掉首位的空白。示例如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      .font-green {
        color: #00ff31;
      }
      .box {
        width: 400px;
        padding: 20px;
        line-height: 35px;
        margin: 20px auto;
        background-color: #e2e2e2;
      }
      .box input{
          width: 100%;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="box">
        <h2>未使用<span class="font-green">.lazy</span>修饰符:</h2>
        <input type="text" v-model="notMsg" />
        <p>{{ notMsg }}</p>
      </div>
      <div class="box">
        <h2>使用<span class="font-green">.lazy</span>修饰符:</h2>
        <input type="text" v-model.lazy="msg" />
        <p>{{ msg }}</p>
      </div>
      <div class="box">
        <h2>未使用<span class="font-green">.number</span>修饰符:</h2>
        <input type="text" v-model="notCount" />
        <p>
          {{ typeof notCount === 'string' ? '字符串值:"'+notCount+'"' : notCount }}
        </p>
      </div>
      <div class="box">
        <h2>使用<span class="font-green">.number</span>修饰符:</h2>
        <input type="text" v-model.number="count" />
        <p>
          {{ typeof count === 'number' ? '数值:' + count : count }}
        </p>
      </div>
      <div class="box">
        <h2>未使用<span class="font-green">.trim</span>修饰符:</h2>
        <input type="text" v-model="notTrim" />
        <p>{{ notTrim }}</p>
      </div>
      <div class="box">
        <h2>使用<span class="font-green">.trim</span>修饰符:</h2>
        <input type="text" v-model.trim="trim" />
        <p>{{ trim }}</p>
      </div>
    </div>
    <script>
      var vm = new Vue({
        el: "#app",
        data: {
          notMsg: "未使用.lazy修饰符",
          msg: "使用.lazy修饰符",
          notCount: "1",
          count: 1,
          notTrim: "首尾如果有空白,值就会有空白 ",
          trim: "首尾如果有空白,值也不会有空白,因为.trim消除了空白"
        }
      });
    </script>
  </body>
</html>

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

13.组件基础

组件是可复用的vue实例,除了el选项不一致以外,其余的data,prop,computed,methods,watch等选项都是一样的,并且组件都需要一个名字。一个基本的示例如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
      <button-num></button-num>
    </div>
    <script>
      Vue.component("button-num", {
        template: `<button v-on:click="num++">You click me {{ num }} times!</button>`,
        data: function() {
          return {
            num: 0
          };
        }
      });
      var vm = new Vue().$mount(document.getElementById("app"));
    </script>
  </body>
</html>

以上定义了一个简单的示例,通过这个示例我们可以知道组件是可复用的vue实例因此可以接收与new Vue()一样的选项参数,组件的名字就是button-num,通过Vue.component()来全局注册一个组件,这个方法接受2个参数,第一个参数就是组件名,第二个参数则是组件配置对象,配置对象的选项与vue实例是一致的。
你可以狠狠点击此处具体示例进行查看。

组件还可以复用,当基础组件要使用的多的时候,可以使用v-for来渲染。如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
      <button-num></button-num>
      <button-num></button-num>
      <button-num v-for="n in 5" :key="n"></button-num>
    </div>
    <script>
      Vue.component("button-num", {
        template: `<button v-on:click="num++">You click me {{ num }} times!</button>`,
        data: function() {
          return {
            num: 0
          };
        }
      });
      var vm = new Vue().$mount(document.getElementById("app"));
    </script>
  </body>
</html>

你可以点击这里具体示例查看该示例。在这里,我们需要注意一点,组件中的data选项必须是一个函数,如果不是一个函数,控制台则会弹出一个警告,如下图所示:

图片描述

一个大型的网站项目,比如一个博客,我们可以将博客分割成多个小组件,比如导航组件,比如内容组件等等,这由许多个小组件组织起来,就成了一个SPA单页应用。如下图所示:

为了能够在模板中使用,需要先注册这些组件以便vue根实例来识别。注册组件有两种方式:全局注册局部注册,在这里,我们都是通过Vue.component()来全局注册的。
全局注册组件之后,就表示你可以在你被注册之后任何vue根实例中使用,也包括所有子组件中,到目前为止,我们不需要考虑局部注册。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
        <blog-component title="博客组件的标题"></blog-component>
    </div>
    <script>
      Vue.component("blog-component", {
        template: `<h3>{{title}}</h3>`,
        props:['title']
      });
      var vm = new Vue().$mount(document.getElementById("app"));
    </script>
  </body>
</html>

在上例中,我们可以像访问data选项一样访问props传递的值,props可以接收任意类型传递的值,你可以狠狠点击此处具体示例进行查看。

一个prop被注册之后,我们其实可以像下面这样把数据作为一个自定义的特性传递:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
        <blog-component title="博客组件的标题1"></blog-component>
        <blog-component title="博客组件的标题2"></blog-component>
        <blog-component title="博客组件的标题3"></blog-component>
    </div>
    <script>
      Vue.component("blog-component", {
        template: `<h3>{{title}}</h3>`,
        props:['title']
      });
      var vm = new Vue().$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

然而,在一个典型的应用当中,我们需要传递的数据不可能是如此简单的一个字符串,有可能是一个对象,甚至是一个数组。这样,以上的示例,我们需要如此修改:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
      <blog-component v-for="(blog,index) in blogData" :key="index" v-bind:title="blog.title"></blog-component>
    </div>
    <script>
      Vue.component("blog-component", {
        template: `<h3>{{title}}</h3>`,
        props: ["title"]
      });
      var vm = new Vue({
        data: {
          blogData: [
            { title: "博客组件的标题1", content: "博客组件的内容1" },
            { title: "博客组件的标题2", content: "博客组件的内容2" },
            { title:"博客组件的标题3",content:"博客组件的内容3"}
          ]
        }
      }).$mount(document.getElementById("app"));
    </script>
  </body>
</html>

如上所示,你应该会发现我们使用v-bind来动态传递prop,这在你不知道要渲染的具体内容,尤其是当渲染一个API数据接口时是非常有用的。到目前为止,prop需要了解的就只有这些呢。你可以点击此处具体示例查看该示例。

但在一篇博文当中,我们不仅仅是需要添加标题,最起码我们需要渲染博文的内容,因此你的模板内容就需要做修改:

template:`<h3>{{ title }}</h3><div v-html="content"></div>`;

然而如果这样写的话,vue会显示一个错误,every component must have a single root element (每个组件必须只有一个根元素),因此我们需要用一个根元素来包裹,如下:

template:`<div class="blog-demo"><h3>{{ title }}</h3><div v-html="content"></div></div>`;

当组件变得越来越复杂时,如果单独定义一个prop,这会看起来越来越复杂。一篇博文可能会包含发布日期,评论等,我们如此做法,代码看起来也不够简洁:

<blog-component v-for="(blog,index) in blogData" :key="index" v-bind:title="blog.title" :content="content" :date="date" :recommen="commen"></blog-component>

重构一下这个组件,让它接收一个单独的prop,如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
      <blog-component v-for="(blog,index) in blogData" :key="index" :blog="blog"></blog-component>
    </div>
    <script>
      Vue.component("blog-component", {
        props: ["blog"],
        template: `
            <div class="blog-demo">
                <h3>{{blog.title}}</h3>
                <div v-html="blog.content"></div>
                <p>
                    <span class="date">{{ blog.date }}</span>
                    </span class="recommen">{{ blog.commen }}</span>
                </p>
            </div>
        `
      });
      var vm = new Vue({
        data: {
          blogData: [
            { title: "博客组件的标题1", content: "博客组件的内容1",date:"2019年2月20日",commen:"真棒!" },
            { title: "博客组件的标题2", content: "博客组件的内容2",date:"2019年2月20日",commen:"真的好棒!" },
            { title:"博客组件的标题3",content:"博客组件的内容3",date:"2019年2月20日",commen:"真的真的非常棒!"}
          ]
        }
      }).$mount(document.getElementById("app"));
    </script>
  </body>
</html>

你可以狠狠点击此处具体示例查看。在开发blog组件时候,有时候,我们不仅需要建立父组件向子组件的通信,我们也许还需要建立子组件向父组件传递数据之间的通信,这时候,我们可以采用自定义事件来向父组件传递数据。例如,我们想要添加一个按钮,用于改变字体的颜色,我们可以自定义一个颜色框,然后让用户选择字体颜色来改变字体的颜色。如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
      <blog-component v-for="(blog,index) in blogData" :key="index" :blog="blog" @change-color="setColor($event,index)"></blog-component>
    </div>
    <script>
      Vue.component("blog-component", {
        props: ["blog"],
        template: `
            <div class="blog-demo" :style="{color:blog.color}">
                <h3>{{blog.title}}</h3>
                <div v-html="blog.content"></div>
                <p>
                    <span class="date">{{ blog.date }}</span>
                    </span class="recommen">{{ blog.commen }}</span>
                </p>
                <div class="setFontColor">
                    <input type="color" v-model="colorValue" />
                    <button type="button" @click="$emit('change-color',colorValue)">改变字体颜色</button>
                </div>
            </div>
        `,
        data:function(){
            return{
                colorValue:""
            }
        }
      });
      var vm = new Vue({
        data: {
          blogData: [
            { title: "博客组件的标题1", content: "博客组件的内容1",date:"2019年2月20日",commen:"真棒!" },
            { title: "博客组件的标题2", content: "博客组件的内容2",date:"2019年2月20日",commen:"真的好棒!" },
            { title:"博客组件的标题3",content:"博客组件的内容3",date:"2019年2月20日",commen:"真的真的非常棒!"}
          ]
        },
        methods:{
            setColor:function(color,index){
                this.$set(this.blogData[index],'color',color);
            }
        }
      }).$mount(document.getElementById("app"));
    </script>
  </body>
</html>

通过以上示例我们可以看出,父组件通过像处理native DOM事件一样通过v-on指令监听子组件的任意事件,然后子组件通过$emit()方法来自定义一个事件,这个方法接收两个参数,第一个参数就是自定义的事件名(在这里是change-color),第二个参数则是要传递的参数,在这里,传递了颜色选择器选择的颜色值colorValue,然后父组件使用这个事件,有了这个自定义的v-on:change-color监听器,父组件就可以为数组的每一项对象新增一个color属性,通过将设置style,将color属性绑定到style标签内,然后父组件就可以更新数据,并渲染结果了。你可以狠狠点击此处具体示例来查看。

通过自定义事件我们还可以实现在组件上使用v-model指令,如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
      <ew-input v-model="msg"></ew-input>
      <p>{{ msg }}</p>
    </div>
    <script>
      Vue.component("ew-input", {
        props: ["value"],
        template: `
          <input type="text" :value="value"  v-on:input="$emit('input',$event.target.value)" />
        `
      });
      var vm = new Vue({
        data: {
          msg: "在组件上使用v-model指令"
        }
      }).$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

和HTML元素一样,通常我们也需要向组件传递内容,这时候,我们可以使用插槽。如:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        .ew-alert{
            position:absolute;
            left: 50%;
            top: 50%;
            min-width: 300px;
            min-height: 200px;
            background-color: #ffffff;
            box-shadow: 0 0 15px #939393;
            text-align: center;
            transform: translate(-50%,-50%);
            border-radius: 15px;
        }
        .ew-alert > .ew-alert-title{
            padding: 10px 0;
        }
        .ew-alert > .ew-alert-content{
            padding: 8px 9px;
        }
    </style>
  </head>
  <body>
    <div id="app">
      <ew-alert title="弹出框标题"><p>{{ msg }}</p></ew-alert>
    </div>
    <script>
      Vue.component("ew-alert", {
        props: ["title"],
        template: `
          <div class="ew-alert">
            <div class="ew-alert-title">{{ title }}</div>
            <div class="ew-alert-content"><slot></slot></div>
          </div>
        `
      });
      var vm = new Vue({
        data: {
          msg: "弹出框主题内容"
        }
      }).$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

有时候,我们需要动态切换组件,也就是在做一个选项卡组件的时候,我们把每一个导航对应一个导航组件,这时候就需要动态切换组件。如果要动态切换组件,我们可以使用一个特性is来实现。例如:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      .tab-component-demo{
          width: 900px;
          margin: 15px auto;
          padding: 15px 10px;
      }
      button {
        outline: none;
        border: 1px solid #ebebeb;
        background-color: transparent;
        display: inline-block;
        padding: 6px 7px;
        text-align: center;
        line-height: 1;
        transition: all 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
        cursor: pointer;
        font-size: 16px;
        border-radius: 4px;
      }
      button:hover,
      button:active {
        border-color: #58e9fc;
        background-color: #0abce9;
        color: #ffffff;
      }
      .ew-page{
          border: 1px solid #999;
          padding: 15px 8px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="tab-component-demo">
        <button
          v-for="(tab,index) in tabs"
          :key="index"
          @click="currentTab = tab"
        >
          {{ tab }}
        </button>
        <component :is="currentTabComponent"></component>
      </div>
    </div>
    <script>
      Vue.component("tab-home", {
        template: `
          <div class="ew-page">
           home page!
          </div>
        `
      });
      Vue.component("tab-list", {
        template: `
          <div class="ew-page">
           list page!
          </div>
        `
      });
      Vue.component("tab-contact", {
        template: `
          <div class="ew-page">
          contact page!
          </div>
        `
      });
      var vm = new Vue({
        data: {
          currentTab: "home",
          tabs: ["home", "list", "contact"]
        },
        computed: {
          currentTabComponent: function() {
            return "tab-" + this.currentTab.toLowerCase();
          }
        }
      }).$mount(document.getElementById("app"));
    </script>
  </body>
</html>

你可以狠狠点击此处具体示例查看。通过这个示例,我们可以知道is绑定的值currentTabComponent可以是一个组件名,但事实上,我们还可以绑定一个组件的选项对象,如下例:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      .tab-component-demo {
        width: 900px;
        margin: 15px auto;
        padding: 15px 10px;
      }
      button {
        outline: none;
        border: 1px solid #ebebeb;
        background-color: transparent;
        display: inline-block;
        padding: 6px 7px;
        text-align: center;
        line-height: 1;
        transition: all 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
        cursor: pointer;
        font-size: 16px;
        border-radius: 4px;
      }
      button:hover,
      button:active {
        border-color: #58e9fc;
        background-color: #0abce9;
        color: #ffffff;
      }
      .ew-page {
        border: 1px solid #999;
        padding: 15px 8px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="tab-component-demo">
        <button
          v  v-for="(tab,index) in tabs"
          :key="index"
          @click="currentTab = tab"
        >
          {{ tab.name.slice(4) }}
        </button>
        <component :is="currentTab.component"></component>
      </div>
    </div>
    <script>
      var tabs = [
        {
          name: "tab-home",
          component: {
            template: <div class="ew-page">home page! </div>`
          }
        },
        {
          name: "tab-list",
          component:{
            template: `<div class="ew-page">list page!</div>`
          }
        },
        {
          name: "tab-contact",
          component:{
            template: `<div class="ew-page">contact page!</div>`
          }
        }
      ];
      var vm = new Vue({
        data: {
          currentTab: tabs[0],
          tabs: tabs
        }
      }).$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

在解析DOM模板时,我们还需要注意一点,那就是有些HTML元素,诸如<ul>,<ol>,<table>,<select>,对于哪些元素出现在其内部是有严格限制的,而有些元素,诸如<li>,<tr>,<option>,只能出现在特定的元素内部。这会导致我们在使用这些有约束的元素时出现问题,如:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
        <table>
            <ew-div></ew-div>
        </table>
    </div>
    <script>
      Vue.component("ew-div", {
        template: `
           <div>测试</div>
        `
      });
      var vm = new Vue().$mount(document.getElementById("app"));
    </script>
  </body>
</html>

我们可以打开浏览器控制台,看到以上自定义的ew-div组件中的元素,被渲染到最外部去了,如下图所示:

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

幸运的是,你可以使用is解决这个问题。如:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
        <table>
            <tr is="ew-div"></tr>
        </table>
    </div>
    <script>
      Vue.component("ew-div", {
        template: `
           <div>测试</div>
        `
      });
      var vm = new Vue().$mount(document.getElementById("app"));
    </script>
  </body>
</html>

此时再看浏览器控制台,你会发现自定义的组件已经被渲染在table内部,如下图所示:

图片描述

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

当然,如果我们从如下来源使用模板的话,这条限制是不存在的。

1.字符串(如:template:'')
2.单文件组件(.vue)
3.<script type="text/x-template"></script>

到目前为止,需要了解的组件基础知识大概就只有这些呢,接下来是深入组件的了解。

四.组件深入

1.组件注册

在注册一个组件的时候,我们需要为组件起一个名字,比如全局注册的时候,我们已经知道了

Vue.component('my-component-name',{});

也就是Vue.component()的第一个参数。在给组件起名的时候,如果不是字符串模板或者单文件组件,直接在DOM中使用组件,组件名是有规范的(字母全小写,并且单词之间用连字符连接)。你可以查阅API风格指南

这也就是说定义组件名有两种方式,即(kebab-case)短横线分隔命名(PascalCase)首字母大写命名。当使用短横线命名时,我们在使用组件的时候,也必须是采用短横线命名,而使用首字母大写命名则既可以采用首字母大写也可以采用短横线命名,当然在DOM模板而非字符串模板或者单文件组件中,也还是只能采用短横线命名。

到目前为止,我们只是用Vue.component来全局注册一个组件,如:

Vue.component('com-a',{})
Vue.component('com-b',{})
Vue.component('com-c',{})

然后,你就可以在new Vue()根实例内的任何组件中使用这三个组件,比如:

//js
new Vue(el:"#app")
//html
<div id="app">
    <com-a>
      <com-b></com-b>
    </com-a>
    <com-b>
      <com-c></com-c>
    </com-b>
    <com-c>
      <com-a></com-a>
    </com-c>
</div>

这往往是不够理想的,比如在一个通过webpack构建的系统应用中,全局注册所有的组件,则意味着即便你不再使用一个组件了,但组件仍然被包含在最终构建的结果中,这也就增加了一些无谓的JavaScript代码。

不过,我们可以局部注册组件。局部注册也就是将一个组件当作普通的JavaScript对象来定义。如下例:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
        <component-a></component-a>
        <component-b></component-b>
    </div>
    <script>
      var componentA = {
          template:`<div>a</div>`
      },
      componentB = {
          template:`<div>b</div>`
      }
      var vm = new Vue(
          {
              el:"#app",
              components:{
                  'component-a':componentA,
                  'component-b':componentB
              }
          }
      )
    </script>
  </body>
</html>

通过JavaScript定义一个组件对象,然后在vue实例中添加components选项,components选项的属性名就是组件名,属性值就是这个组件的选项对象。你可以狠狠点击此处具体示例查看效果。

但是局部注册组件是无法在其子组件中使用另一局部注册的组件的,如果想要这样的效果,那么代码需要写成下面那样:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
      <component-a></component-a>
      <component-b></component-b>
    </div>
    <script>
      var componentA = {
          template: `<div>a</div>`
        },
        componentB = {
          template: `<div>b<component-a></component-a></div>`,
          components:{
              'component-a':componentA
          }
        };
      var vm = new Vue({
        el: "#app",
        components: {
          "component-a": componentA,
          "component-b": componentB
        }
      });
    </script>
  </body>
</html>

也就是往局部注册组件对象中添加一个components选项,注册需要添加的局部注册的组件而已。你可以狠狠点击此处具体示例查看效果。

如果是使用es6与babel以及webpack,那么代码,则更像是如下:

import componentA from './componentA.vue'

export default{
  components:{ componentA }
}

在对象中放一个componentA其实也就是componentA:componentA的缩写,也就是说这个变量名同时是:

用在模板中的自定义元素的名称。
包含了这个组件的选项的变量名。

如果你还不熟悉模块化构建,那最好还是跳过这里。

2.prop

由于HTML特性是不区分大小写的,也就是说浏览器会将所有的大写字母转换成小写字母,这也就意味着当你使用camelCase(驼峰命名法)来为prop定义的时候,你在子组件使用prop的时候需要使用等价的kebab-case(短横线命名)来命名,比如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>prop</title>
    <style>
        .ew-button {
            outline: none;
            border: 1px solid transparent;
            display: inline-block;
            border-radius: 4px;
            font-size: 14px;
            text-align: center;
            transition: color 0.2s linear, backgroud-color 0.1s linear,
                border-color 0.1s linear, box-shadow 0.2s linear;
            cursor: pointer;
            border-color: #f3f3f3;
            color: #a9adb1;
            background-color: #ffffff;
            padding: 5px 15px 6px;
        }

        .ew-button-success {
            background-color: #2196ff;
            border-color: #4ca4f1;
            color: #ffffff;
        }

        .ew-button-success:hover,
        .ew-button-success:active {
            box-shadow: 0 0 3px #943321;
            opacity: 0.8;
        }
    </style>
</head>

<body>
    <div id="app">
        <ew-button ew-type="success">{{ content }}</ew-button>
    </div>
    <script>
        var ewButton = {
            props: ["ewType"],
            template: `<button type="button" :class="'ew-button-'+ ewType" class="ew-button"><slot></slot></button>`
        };
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    content: "success"
                };
            },
            components: {
                ewButton: ewButton
            }
        });
    </script>
</body>

</html>

在这里,我们是使用的局部注册一个组件,也就是用一个对象表示一个组件,然后在Vue实例中添加components选项来注册这个组件,组件名虽然使用的camelCase(驼峰命名法),也就是ewButton,但我们也看到了,在使用这个组件的时候,需要使用kebab-case(短横线命名),即ew-button来表示,同样的,定义的prop名也是如此,在子组件中定义成ewType,但在使用的时候则是ew-prop,当然在单文件组件以及字符串模板中是不存在这个限制的,这里是使用DOM模板来表示的。

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

到目前为止,我们只看到了prop的形式只是一个字符串数组:

props:['str','num','bool','arr','obj']

但是,通常我们想要每个prop都有指定的值和类型,这时候,我们可以以对象的形式来列出prop,这些属性的名称和值分别代表prop名类型。如下:

props:{
  str:String,
  num:Number,
  bool:Boolean,
  arr:Array,
  obj:Object
}

如果指定的prop类型不对,那么浏览器就会在控制台报一个invalid prop的错误,这是vue内部实现的。

在此之前,我们可以看到prop有两种用法,第一种是静态的prop,如上例的:

<ew-button ew-type="success">{{ content }}</ew-button>

这里的ew-type就是一个静态的prop,但实际上,我们还可以用v-bind来表示一个动态的prop,以上的静态prop,我们就可以表示成如下:

<ew-button v-bind:ew-type="'success'">{{ content }}</ew-button>
//或者<ew-button :ew-type="'success'">{{ content }}</ew-button>

有了v-bind,我们就可以为prop绑定任意类型的值。例如:

以下是传递一个数值:

//即便100是静态的,我们也需要使用v-bind来告诉vue,这是一个JavaScript表达式,而不是字符串
<ew-button v-bind:ew-type="100">{{ content }}</ew-button>
//绑定一个变量名
<ew-button v-bind:ew-type="number">{{ content }}</ew-button>

在js代码中,我们就可以如此写:

new Vue({
    ...
    data(){
      return{
          number:100
      }
    }
    ...
})

以下是传递一个布尔值:

//如果不传入值,默认就是一个布尔值true
<ew-button ew-type>{{ content }}</ew-button>
//即便false是静态的,我们也需要使用v-bind来告诉vue,这是一个JavaScript表达式,而不是字符串
<ew-button :ew-type="false">{{ content }}</ew-button>
/绑定一个变量名
<ew-button :ew-type="bool">{{ content }}</ew-button>

在js代码中,我们就可以如此写:

new Vue({
    ...
    data(){
      return{
          bool:false
      }
    }
    ...
})

以下是传递一个数组:

//即便数组是静态的,我们也需要使用v-bind来告诉vue,这是一个JavaScript表达式,而不是字符串
<ew-button :ew-type="[1,2,3]">{{ content }}</ew-button>
/绑定一个变量名
<ew-button :ew-type="arr">{{ content }}</ew-button>

在js代码中,我们就可以如此写:

new Vue({
    ...
    data(){
      return{
          arr:[1,2,3]
      }
    }
    ...
})

以下是传递一个对象:

//即便对象是静态的,我们也需要使用v-bind来告诉vue,这是一个JavaScript表达式,而不是字符串
<ew-button :ew-type="{ name:'eveningwater',sex:'male'}">{{ content }}</ew-button>
/绑定一个变量名
<ew-button :ew-type="obj">{{ content }}</ew-button>

在js代码中,我们就可以如此写:

new Vue({
    ...
    data(){
      return{
          obj:{
            name:"eveningwater",
            sex:"male"
          }
      }
    }
    ...
})

如果你想将一个对象的所有属性都作为prop传入,那么你可以使用不带参数的v-bind来取代v-bind:propName。例如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>prop</title>
</head>

<body>
    <div id="app">
        <ew-div v-bind="item"></ew-div>
        //等价于<ew-div v-bind:name="item.name" v-bind:sex="item.sex"></ew-div>
    </div>
    <script>
        Vue.component('ew-div',{
            props:['name','sex'],
            template:`<div class="myDiv"><h1>{{ name }}</h1><p v-text="sex"></p></div>`,

        })
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                   item:{
                       name:"eveningwater",
                       sex:"male"
                   }
                };
            }
        });
    </script>
</body>

</html>

以上定义的v-bind="item"实际上就等价于v-bind:name="item.name"v-bind:sex="item.sex",所以,我们才可以在子组件的props里获取到namesex,并且将props的值绑定到元素中去。你可以狠狠点击此处具体示例查看效果。

所有的prop都使得父子之间的prop形成了一个单向下行绑定:父组件的prop如果发生了更新,则会向下流动到子组件中,但是反过来修改子组件的prop就不行。这样也就防止了子组件意外改变父组件的状态,从而导致应用的数据流难以理解。

这也就是说一旦父组件更新了,那么子组件也就会立刻刷新为最新的值,这也意味着你不应该修改一个子组件的prop,如果你如此做了,浏览器会发出一个警告,这是vue内部实现的。来看一个示例:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>prop</title>
</head>

<body>
    <div id="app">
        <ew-div :content="content"></ew-div>
    </div>
    <script>
        Vue.component('ew-div',{
            props:['content'],
            template:`<div class="myDiv"><p>{{ content }}</p><button type="button" @click="updateContent">改变content的值</button></div>`,
            methods:{
                updateContent(){
                    this.content = '修改了值,浏览器控制台会给出一个警告!'
                }
            }
        })
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                   content:"文档的内容"
                };
            }
        });
    </script>
</body>

</html>

如上例,当我们点击了按钮,去修改prop的值,尽管最终值发生了改变,但是浏览器控制台还是给出了一个警告信息,如下图所示:

clipboard.png

你可以狠狠点击此处具体示例查看效果。实际上,这个警告信息也就是提示,不能直接在子组件中修改prop,但是我们有两种办法解决这个问题。

1.我们将父组件传给子组件的prop作为一个初始值,然后在子组件的data选项中定义一个新的数据,将prop的值用作其初始值,然后在绑定数据的时候就不用绑定prop,而是绑定定义在data选项中的值。如下所示:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>prop</title>
</head>
<body>
    <div id="app">
        <ew-div :content="content"></ew-div>
    </div>
    <script>
        Vue.component('ew-div',{
            props:['content'],
            template:`<div class="myDiv"><p>{{ curContent }}</p><button type="button" @click="updateContent">改变content的值</button></div>`,
            data(){
                return{
                    curContent:this.content
                }
            },
            methods:{
                updateContent(){
                    this.curContent = '修改了值,浏览器控制台不会给出一个警告!'
                }
            }
        })
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                   content:"文档的内容"
                };
            }
        });
    </script>
</body>
</html>

现在,我们再点击按钮,值发生了改变,而且浏览器也没有给出警告。你可以狠狠点击此处具体示例查看效果。

2.如果是这个prop作为初始值,并且要进行转换,那么,最好还是使用一个计算属性来表示。例如:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>prop</title>
</head>
<body>
    <div id="app">
        <ew-div :content="content"></ew-div>
    </div>
    <script>
        Vue.component('ew-div',{
            props:['content'],
            template:`<div class="myDiv"><p>{{ curContent }}</p></div>`,
            computed:{
                curContent(){
                    return '计算属性:' + this.content;
                }
            }
        })
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                   content:"文档的内容"
                };
            }
        });
    </script>
</body>
</html>

你可以狠狠点击此处具体示例查看效果。又或者,综合以上两个方法,修改如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>prop</title>
</head>
<body>
    <div id="app">
        <ew-div :content="content"></ew-div>
    </div>
    <script>
        Vue.component('ew-div',{
            props:['content'],
            template:`<div class="myDiv"><p>{{ curContent }}</p><button type="button" @click="updateContent">改变content的值</button></div>`,
            data(){
                return{
                    newContent:""
                }
            },
            computed:{
                curContent:{
                    get(){
                        if(this.newContent){
                            return '计算属性:' + this.newContent;
                        }else{
                            return '计算属性:' + this.content;
                        }
                    },
                    set(newVal){
                        this.newContent = newVal;
                    }
                }           
            },
            methods:{
                updateContent(){
                    this.curContent = '修改了值,浏览器控制台不会给出一个警告!'
                }
            }
        })
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                   content:"文档的内容"
                };
            }
        });
    </script>
</body>
</html>

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

注意在JavaScript数组和对象中,由于数组和对象是通过引用传入的,因此修改子组件的数据可能会影响到父组件的状态。

在使用prop的时候,我们还可以为prop指定验证要求,如果不满足验证要求,则vue会在浏览器控制台中警告你。这在对于开发一个被别人用到的组件时尤为重要。

为了指定prop的验证需求,你应该为props的值指定一个带有验证需求的对象,而不是字符串数组。如下一个示例:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>validate-prop</title>
    <style>
        .font-red {
            color: #ff1944;
        }
    </style>
</head>

<body>
    <div id="app">
        <ew-div :num-success="count1" :num-fail="count2" :str-success="str1" :str-fail="str2" :str-require="str3"
            :num-or-str-success="numorstr1" :num-or-str-fail="numorstr2" define-by-self="字符串"></ew-div>
    </div>
    <script>
        Vue.component('ew-div', {
            props: {
                numSuccess: Number,//验证数值成功
                numFail: Number,//验证数值失败
                strSuccess: String,//验证字符串成功
                strFail: String,//验证字符串失败
                strRequire: {
                    type: String,
                    required: true //验证字符串必填
                },
                numOrStrSuccess: [Number, String],//验证字符串或者数值成功
                numOrStrFail: [Number, String],//验证字符串或者数值失败
                defaultNum: {
                    type: Number,
                    default: 50
                },//带有默认值的验证
                defaultObj: {
                    type: Object,
                    default: function () {
                        return { name: "eveningwater", sex: "male" }
                    } //默认值为对象,对象或数组的默认值必须通过一个函数返回
                },
                defineBySelf: {
                    validator: function (value) {
                        // 值必须是以下数组中的几个
                        return [123, '字符串', [1, 2, 3], { name: "eveningwater" }].indexOf(value) > -1;
                    },
                    default: 123
                }
            },
            template: `
                <div class="myDiv">
                    <p>数值类型验证成功:{{ numSuccess }}</p>
                    <p>需要注意的就是null和undefined会通过任何类型的验证,除了设置required为true。</p>
                    <p>
                        数值类型验证失败:{{ numFail }}
                        浏览器控制台会报如此的警告:<span class="font-red">[Vue warn]: Invalid prop: type check failed for prop "numFail". 
                        Expected Number with value 123, got String with value "123".</span>。大致意思就是:
                        不可用的prop,prop'numFail'的类型验证失败,使用数值型的123替代字符串的"123"。
                    </p>
                    <p>字符串类型验证成功:{{ strSuccess }}</p>
                    <p>
                        字符串类型验证失败:{{ strFail }}
                        浏览器控制台会报如此的警告:<span class="font-red">Invalid prop: type check failed for prop "strFail". 
                            Expected String with value "124", got Number with value 124.</span>。大致意思就是:
                        不可用的prop,prop'strFail'的类型验证失败,使用字符串的"124"替代数值的124。
                    </p>
                    <p>
                        必填字符串(包含空字符串):{{ strRequire }}
                        如果不满足条件,比如是null,则一样会抛出类似警告:
                        <span class="font-red">
                        [Vue warn]: Invalid prop: type check failed for prop "strRequire".
                         Expected String with value "null", got Null.     
                        </span>
                    </p>
                    <p>字符串或数值类型验证成功:{{ numOrStrSuccess }}</p>
                    <p>
                        字符串或数值类型验证失败:{{ numOrStrFail }}
                        浏览器控制台会报如此的警告:<span class="font-red">Invalid prop: type check failed for prop "numOrStrFail". 
                            Expected Number, String, got Object </span>。大致意思就是:
                        不可用的prop,prop'numOrStrFail'的类型验证失败,使用字符串,数值替代对象。
                    </p>
                    <p>带有默认值的验证:{{ defaultNum }}</p>
                    <p>
                        对象或数组的默认值必须以一个函数返回:
                        <span>{{ defaultObj.name }}</span><br>
                        <span>{{ defaultObj.sex }}</span>
                    </p>
                    <p>自定义验证函数:{{ defineBySelf }}如果验证失败,则控制台会提示:
                        <span class="font-red">[Vue warn]: Invalid prop: custom validator check failed for prop "defineBySelf".</span>
                    </p>
                </div>
            `
        })
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    count1: 100,
                    count2: '123',
                    str1: "成功!",
                    str2: 124,
                    str3: '123',
                    numorstr1: '字符串',
                    numorstr2: {}
                };
            }
        });
    </script>
</body>

</html>

根据以上示例,我们可以得知一个prop可以验证js当中的基本数据类型,也可以验证对象或数组,可以指定必要值以及默认值,还可以自定义验证函数。你可以狠狠点击此处具体示例查看效果。

这里需要注意一点,就是prop会在一个组件实例创建之前进行验证,因此实例中的data,computed等属性在defaultvalidator函数中是不可用的。

前面提到prop可以通过一个type属性来进行验证,在这里,type的值可以是如下几个原生构造函数:

Number,String,Boolean,Object,Array,Function,Date,Symbol

额外的,type其实也可以是一个自定义的构造函数,并且通过instanceof来检查确认。比如以下一个示例:

<!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 data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>prop type</title>
</head>

<body>
    <div id="app">
        <name-component :name="fullName"></name-component>
    </div>
    <script>
        function FullName(firstName,lastName){
            this.firstName = firstName;
            this.lastName = lastName;
            this.fullName = firstName + ' ' + lastName;
        }
        var f = new FullName('夕','水');
        var nameComponent = {
            props:{
                name:{
                    type:FullName
                }
            },
            template:`
                <div class="name">
                    <p>
                        <span>姓:</span>
                        <span>{{ name.firstName }}</span>
                    </p>
                    <p>
                        <span>名:</span>
                        <span>{{ name.lastName }}</span>
                    </p>
                    <p>
                        <span>姓名:</span>
                        <span>{{ name.fullName }}</span>
                    </p>
                </div>
            `
        }
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    fullName:f
                }
            },
            components:{
                nameComponent:nameComponent
            }
        })
    </script>
</body>

</html>

在这里,只要name是自定义构造函数FullName的实例,那么数据就验证成功,如果不是,那么vue就会在浏览器控制台给出警告。你可以狠狠点击此处具体示例查看效果。

在前面,我们都是显式的定义prop,然而,当我们不显式的定义prop的时候,实际上,它也作为了一个特性,传给了组件,只不过它是一个非prop特性,因为该组件并没有显式的定义该prop特性,显式的定义prop固然适用于传向一个组件,但是组件库的作者并不能总是预见到prop特性会应用于什么样的场景。所以如果不显式的定义prop特性,那么该特性就会被添加到组件的根元素上。如下例:

<!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 data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>not prop</title>
</head>

<body>
    <div id="app">
        <name-component :name="nameValue"></name-component>
    </div>
    <script>
        var nameComponent = {
            template:`
                <div class="name">
                   <p>组件并没有显式的定义prop,所以特性被添加到了根元素上。</p>
                </div>
            `
        }
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    nameValue:"eveningwater"
                }
            },
            components:{
                nameComponent:nameComponent
            }
        })
    </script>
</body>

</html>

在浏览器控制台,我们可以看到,name特性被添加到class为name的根元素上,如下图所示:

clipboard.png

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

在这里,对于绝大多数的特性来说,外部提供的特性都会替换组件内部的特性,例如想象一下,如果组件内部是这样一个模板:

<input type="text" v-model="newValue" class="ew-input"/>

如果我们在组件外部提供一个type="number"的特性,一旦替换掉组件内部的特性,那么就改变了原有的DOM元素,这并不是我们所想要的。来看如下一个例子:

<!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 data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>replace prop</title>
</head>

<body>
    <div id="app">
        <ew-input :type="type"></ew-input>
    </div>
    <script>
        var ewInput = {
            template:`
                <input type="text" v-model="value" class="ew-input">
            `,
            data(){
                return{
                    value:"eveningwater"
                }
            }
        }
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    type:"number"
                }
            },
            components:{
                ewInput:ewInput
            }
        })
    </script>
</body>

</html>

显然,type="number"已经替换掉了原有的type="text"属性,这并不是我们所想要的。
你可以狠狠点击此处具体示例查看效果。

庆幸的是,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 data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>replace prop</title>
</head>

<body>
    <div id="app">
        <ew-input :class="className" :style="stylePadding"></ew-input>
    </div>
    <script>
        var ewInput = {
            template:`
                <input type="text" v-model="value" class="ew-input" style="border:2px solid #2396fe;">
            `,
            data(){
                return{
                    value:"eveningwater"
                }
            }
        }
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    className:"ew-success-input",
                    stylePadding:"padding:6px 7px;"
                }
            },
            components:{
                ewInput:ewInput
            }
        })
    </script>
</body>

</html>

现在,你在控制台中查看该组件的根元素,你会发现classstyle都已经被合并了。你可以狠狠点击此处具体示例查看效果。

虽然是如此,但我们有时候并不想要这样的效果,也就是说我们想要组件的根元素不能继承特性,那么我们可以在组件的选项中设置inheritAttrs:false。格式如下:

Vue.component('my-component',{
    inheritAttrs:false,
    ......
})

当然,这个属性的设置并不会影响styleclass的绑定。我们可以将这个属性与实例的$attrs属性一起结合使用,在构造基础组件的时候非常常用,$attrs属性也是一个对象,包含传递给一个组件的特性名和特性值。例如:

{
  placeholder:"请输入你的用户名",
  id:"#myInput"
}

这个模式更允许你像写原始的HTML元素一样写一个组件,而不需要担心组件的根元素到底是哪个元素。如下例:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>inherit prop</title>
</head>

<body>
    <div id="app">
        <ew-input v-model="name" label="姓名:" placeholder="请输入你的姓名" name="eveningwater"></ew-input>
    </div>
    <script>
        var ewInput = {
            inheritAttrs:false,
            props:['label','value'],
            template:`
                <label>
                    {{ label }}
                    <input type="text" v-bind="$attrs" :value="value" class="ew-input">
                </label>
            `
        }
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                   name:""
                }
            },
            components:{
                ewInput:ewInput
            }
        })
    </script>
</body>

</html>

这样,我们就已经完成了一个基本的表单输入框组件,再加点样式扩展,我们可以完美的构造一个媲美常用的UI框架的表单输入框组件。你可以狠狠的点击此处具体示例查看效果。

3.自定义事件

事件名不同于prop和组件名的注册,是不存在大小写的转换的。也就是说事件名注册的是什么,使用的时候就应该是什么。比如如果用camelCase来定义事件名的话,那么用kebab-case的事件名是无效的。如下例:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>emit</title>
  </head>
  <body>
    <div id="app">
        <p>{{ count }}</p>
        <emit-button @add-count="count+=1"></emit-button>
    </div>
    <script>
        Vue.component('emit-button',{
            template:`<button class="btn" @click="$emit('addCount')">click me</button>`
        })
      var vm = new Vue({
        el: "#app",
        data:{
            count:0
        }
      });
    </script>
  </body>
</html>

在这个示例中,无论我们怎么点击按钮,都不会触发自定义事件,因为我们事件定义的名称是camelCase,而在使用的时候则是kebab-case,这也说明了事件名是不存在大小写的转换的。你可以狠狠点击此处具体示例查看效果。

所以,推荐用kebab-case来定义事件名。

v-model指令默认是利用一个props和一个名为input的事件来完成的,但在像单选框,复选框,下拉框等类型的控件中可能会将value特性用于其它目的。所以vue提供了model选项来避免这样的冲突,来看一个自定义v-model的示例:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>自定义v-model</title>
</head>

<body>
    <div id="app">
        <div class="form">
            <base-check v-model="gameChecked">游戏</base-check>
            <div class="form-item">
                <p>是否选中:<span>{{ gameChecked }}</span></p>
            </div>
        </div>
    </div>
    <script>
        const baseCheck = {
            model: {
                prop: 'checked',
                event: "change"
            },
            props: {
                checked: Boolean
            },
            template: `
                <div class="checkbox">
                    <input type="checkbox" :value="checked" @change="onChecked">
                    <slot></slot>
                </div>
            `,
            methods:{
                onChecked(e){
                    this.$emit('change',e.target.checked)
                }
            }
        }
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    gameChecked:false
                }
            },
            components: {
                baseCheck: baseCheck
            }
        });
    </script>
</body>

</html>

这里的gameChecked的值会被传入名为checkedprop,同时当base-checkbox被更新的时候,这个作为属性的值也会发生相应的改变。你可以狠狠点击此处具体示例查看效果。

在这里你需要注意的就是,需要显式的声明checked这个prop

在某些时候,我们是想要直接在组件上监听一个元素的原生事件的,这个时候,我们可以直接使用.native事件修饰符。比如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>native</title>
</head>

<body>
    <div id="app">
        <base-input @on-focus="count+=1"></base-input>
        <p>{{ count }}</p>
    </div>
    <script>
        const baseInput = {
            template: `
                <input type="text" @focus.natve="$emit('on-focus',$event.target.value)">
            `
        }
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    count:0
                }
            },
            methods:{

            },
            components: {
                baseInput: baseInput
            }
        });
    </script>
</body>

</html>

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

在有的时候,这样确实不错,但事实上,在有些情况下,这样做却并不是一个好主意。比如在编写一个基本的base-input组件时,这个组件的根元素不一定是input元素,这个时候监听input的元素的原生事件时,则会无效,尽管浏览器控制台并不会报错。如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>base-input</title>
</head>

<body>
    <div id="app">
        <base-input @focus.native="msg='hello'" label="姓名"></base-input>
        <p>{{ msg }}</p>
    </div>
    <script>
        const baseInput = {
            props:['label','value'],
            template: `
               <div class="base-input-container">
                    <div class="label">{{ label }}</div>
                    <input type="text" v-bind="$attrs" :value="value" @input="$emit('on-input',$event.target.value)">
               </div>
            `
        }
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    msg:"base input component"
                }
            },
            methods:{

            },
            components: {
                baseInput: baseInput
            }
        });
    </script>
</body>

</html>

显然,这个事件的监听并没有产生效果,你可以狠狠点击此处具体示例查看效果。

为了解决这个问题,vue提供了一个$listeners属性,它是一个对象,里面就包含了这个组件的所有监听器。例如:

{
    focus:function(event){/*业务逻辑代码*/},
    input:function(event){/*业务逻辑代码*/}
    ...
}

有了这个属性,就可以配合v-on:$listeners将所有的事件监听器指向组件特定的子元素。对于类似input的你希望它也可以配合v-model工作的组件来说,使用计算属性来创建这些监听器是非常有用的。如下:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>base-input</title>
</head>

<body>
    <div id="app">
        <base-input @focus="msg='hello,vue.js!'" label="姓名" v-model="msg"></base-input>
        <p>{{ msg }}</p>
    </div>
    <script>
        const baseInput = {
            inheritAttrs:false,
            props:['label','value'],
            computed:{
                input$Listeners:function(){
                    let vm = this;
                    return Object.assign({},this.$listeners,{
                        focus(e){
                            vm.$emit('focus',e.target.value)
                        },
                        input(e){
                            vm.$emit('input',e.target.value)
                        }
                    })
                }
            },
            template: `
               <div class="base-input-container">
                    <div class="label">{{ label }}</div>
                    <input type="text" v-bind="$attrs" :value="value" v-on="input$Listeners">
               </div>
            `
        }
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    msg:"base input component"
                }
            },
            methods:{
                
            },
            components: {
                baseInput: baseInput
            }
        });
    </script>
</body>

</html>

现在,base-input已经完全和一个普通的input元素一摸一样了,你可以狠狠点击此处具体示例查看效果。

在前面,我们知道props是单向数据流,只能从父组件中修改,而不能从子组件中修改,这会让vue在浏览器中给出一个警告,可是在实际场景中,我们难免不会遇到想让props成为双向绑定的存在,这会比用vuex更为方便一些。当然,很不幸的是,这种愿望是不便实现的,因为双向绑定会带来维护上的问题。虽然子组件可以修改父组件,但父组件与子组件之间并没有明显的改动来源。

当然,这也并不是说我们就不能修改子组件从而触发父组件的改变,vue2.3.0推荐v-on:update:propName的模式来触发事件并修改prop的值。如下一个示例:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>sync修饰符</title>
  </head>
  <body>
    <div id="app">
        <text-document :title="title" v-on:update:title="title = $event"></text-document>
    </div>
    <script>
        Vue.component('text-document',{
            props:['title'],
            template:`<div class="text">
                <p>{{ title }}</p>
                <button type="button" @click="$emit('update:title',$event.target.textContent)">change props</button>
            </div>`
        })
      var vm = new Vue({
        el: "#app",
        data:{
            title:'标题'
        }
      });
    </script>
  </body>
</html>

在这里,定义了一个名为titleprop,然后绑定父组件实例data选项中的title数据,传入子组件,子组件定义了一个按钮,通过点击触发update:title的事件,将按钮的文本传递给父组件,然后父组件v-on:update:title监听事件,并修改data选项中的值,这样也算是真正的让prop实现了双向绑定吧。你可以狠狠点击此处具体示例查看该示例。

v-on:update:title这样看着总有些麻烦,为了方便起见,vue将这种模式缩写为v-bind:title.sync。需要注意的就是这种模式是不能和JavaScript表达式一起使用的,例如:v-bind:title.sync=myTitle + '!'这种写法就是无效的。也就是说,我们只能为其绑定一个属性名,类似v-model指令的功能。

当我们用一个对象设置多个prop的时候,我们还可以将这种模式简写为:v-bind.sync=object,这样会把object对象的每一个属性作为一个独立的prop传进去,然后通过v-on添加各自的事件监听器。例如:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>sync修饰符</title>
  </head>
  <body>
    <div id="app">
        <text-document  v-bind.sync="articleObj"></text-document>
    </div>
    <script>
        Vue.component('text-document',{
            props:['title','content'],
            template:`<div class="text">
                <p>{{ title }}</p>
                <article>{{ content }}</article>
                <button type="button" @click="$emit('update:title',$event.target.textContent)">出师表</button>
                <button type="button" @click="$emit('update:content',$event.target.textContent)">先帝创业未半而中道崩殂。。。</button>
            </div>`
        })
      var vm = new Vue({
        el: "#app",
        data:{
            articleObj:{
                title:"标题",
                content:"内容"
            }
        }
      });
    </script>
  </body>
</html>

在这里,我们绑定了一个articleObj对象,有文章标题title,文章内容content属性,然后在组件,分别接收它们,通过增加update事件模式,我们就可以修改数据了。你可以狠狠点击此处具体示例查看效果。

不过我们也要注意一点,就是v-bind.sync不能绑定一个字面量对象,例如v-bind.sync="{ title:"标题"}"是无法正常工作的,这是vue基于很多边缘情况考虑而决定的。

4.插槽

vue自定义组件当中,如果嵌套了内容,是会被抛弃的。也就是说,组件标签之间的内容是无法被渲染的。可有时候,我们又需要在组件当中渲染嵌套的内容,就像标签里嵌套标签一样。vue提供了<slot>元素,来作为内容分发的一个API。在组件当中,<slot>元素更像是一个分发内容的出口。如下例:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-nav v-bind="nav">
            navgation url
        </base-nav>
    </div>
    <script>
        Vue.component('base-nav', {
            props: ['url', 'target'],
            template: `
                <a :href="url" :target="target">
                    <slot></slot>
                </a>
            `
        })
        var vm = new Vue({
            el: "#app",
            data: {
                nav: {
                    url: "https://www.baidu.com/",
                    target: "_blank"
                }
            }
        });
    </script>
</body>

</html>

以上定义了一个base-nav组件,我们可以看到在组件标签之间,我们传入了navgation url作为分发的内容。不止是文本,我们还可以在里面嵌套HTML标签,甚至是另一组件,这都是可以的。你可以狠狠点击此处具体示例查看效果。

我们来看嵌入标签和组件的示例:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-nav v-bind="nav">
            navgation url
        </base-nav>
        <base-nav v-bind="nav">
            <p>这是一个p标签</p>
            简单的文本内容
            <base-li></base-li>
        </base-nav>
    </div>
    <script>
        Vue.component('base-nav', {
            props: ['url', 'target'],
            template: `
                <a :href="url" :target="target">
                    <slot></slot>
                </a>
            `
        })
        const baseLi = {
            template:`<li>这是一个li标签</li>`
        }
        var vm = new Vue({
            el: "#app",
            data: {
                nav: {
                    url: "https://www.baidu.com/",
                    target: "_blank"
                }
            },
            components:{
                baseLi:baseLi
            }
        });
    </script>
</body>

</html>

我们可以看到如下图所示:

clipboard.png

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

当我们在插槽中使用数据时,我们可以使用相同作用域下的实例中的数据,但我们不能使用不同作用域的数据。我们只需要记住一条规则:父模板的所有内容都是在父级作用域中编译的,子模版的所有内容都是在子作用域编译的。来看一个示例:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-nav v-bind="nav" title="导航">
            navgation url:{{ nav.url}}{{ title }}
        </base-nav>
    </div>
    <script>
        Vue.component('base-nav', {
            props: ['url', 'target','title'],
            template: `
                <a :href="url" :target="target">
                    <slot></slot>
                </a>
            `
        })
        var vm = new Vue({
            el: "#app",
            data: {
                nav: {
                    url: "https://www.baidu.com/",
                    target: "_blank"
                }
            }
        });
    </script>
</body>

</html>

如上例所示,我们可以看到在组件的插槽中,我们也可以访问vm实例中的数据对象nav对象,可是不同作用域的title,也就是组件上的数据,是无法被访问到了,所以浏览器控制台会报一个错误,如下图所示:

clipboard.png

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

有时候,我们需要为插槽提供默认的内容,vue也叫做后备内容。比如在封装一个提交按钮组件时,我们可以提供submit或提交作为后备内容。如下图所示:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot default content</title>
    <style>
        .ew-btn{
            display: inline-block;
            transition: all .2s linear;
            outline: none;
            border: 1px solid transparent;
            text-align: center;
            padding: 6px 7px;
            line-height: 1;
            cursor: pointer;
            border-radius: 4px;
        }
        .ew-primary-btn{
            background-color: #2396ef;
            color: #ffffff;
        }
        .ew-primary-btn:hover,
        .ew-primary-btn:active{
            background-color: #23f8ff;
            color: #535353;
        }
    
    </style>
</head>

<body>
    <div id="app">
        <base-button type="primary"></base-button>
        <base-button type="primary">提交</base-button>
    </div>
    <script>
        Vue.component('base-button', {
            props:['type'],
            template: `
                <button type="button" :class="'ew-'+ type +'-btn'" class="ew-btn">
                    <slot>submit</slot>
                </button>
            `
        })
        var vm = new Vue({
            el: "#app",
            data: {
                
            }
        });
    </script>
</body>

</html>

如上例所示,当我们在组件之间并没有提供内容的时候,就会将submit作为按钮文本渲染出来,而当我们提供了内容之后,就会渲染我们所提供的内容。你可以狠狠点击此处具体示例查看效果。

有时候,我们需要用到多个插槽,比如在一个页面布局当中,我们可能会有头部,内容,底部组件,网页DOM结构大概如下所示:

<div class="page">
    <header class="base-header">
        <!-- 头部 -->
    </header>
    <main class="base-main">
      <!-- 主体内容 -->
    </main>
    <footer class="base-footer">
      <!-- 底部 -->
    </footer>
</div>

这时候,头部,主体和底部都会用到插槽,我们可以为每个插槽指定一个属性:name。如果没有显示的指定这个属性的值,那么这个值会默认的指定为default。然后,我们在父组件中使用该组件并且提供插槽内容的时候,我们可以通过v-slot的参数形式结合template元素来指定添加到哪个插槽中。比如:

定义的组件:

<div class="page">
    <header class="base-header">
        <!-- 头部 -->
        <slot name="header"></slot>
    </header>
    <main class="base-main">
      <!-- 主体内容 -->
       <slot name="main"></slot>
    </main>
    <footer class="base-footer">
      <!-- 底部 -->
       <slot name="footer"></slot>
    </footer>
</div>

假定这个布局组件的组件名为base-layout,现在我们在使用这个组件:

<base-layout>
  <template v-slot:header>
      <h1>这是头部内容</h1>
  </template>
  <template v-slot:main>
    <p>这是主题内容</p>
  </template>
  <template v-slot:footer>
    <span>这是底部的内容</span>
  </template>
</base-layout>

当然主题内容,我们也可以不必指定v-slot,这样就是默认的default。这样我们就完成了一个基本的布局页面:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-layout>
            <template v-slot:header>
                <h1>这是头部内容</h1>
            </template>
            <template v-slot:main>
                <p>这是主题内容</p>
            </template>
            <template v-slot:footer>
                <span>这是底部的内容</span>
            </template>
        </base-layout>
    </div>
    <script>
        Vue.component('base-layout', {
            template: `
                <div class="page">
                    <header class="base-header">
                        <!-- 头部 -->
                        <slot name="header"></slot>
                    </header>
                    <main class="base-main">
                      <!-- 主体内容 -->
                       <slot name="main"></slot>
                    </main>
                    <footer class="base-footer">
                      <!-- 底部 -->
                       <slot name="footer"></slot>
                    </footer>
                </div>
            `
        })
        var vm = new Vue({
            el: "#app",
            data: {

            }
        });
    </script>
</body>

</html>

你可以狠狠点击具体示例查看效果。注意这里的v-slot只能添加到一个template元素上,只有一种例外,后续会赘述。

在有的时候,我们需要将插槽内容访问子组件的数据。比如有一个base-button组件如下:

<base-button>
  <span>{{ btn.text }}</span>
</base-button>

在子组件中,我们可能会有如下结构:

<button type="button">
  <slot>{{ btn.text }}</slot>
</button>

子组件的数据应该如下:

data(){
  return{
    btn:{
      text:"submit"
    }
  }
}

我们在子组件中是可以访问到btn.text数据的,但是在父组件中,我们是访问不到的,它们是不同的作用域,这时候我们可以使用v-bind特性将数据绑定到slot元素上,这种特性也被叫做插槽prop。如下:

<button type="button">
  <slot :btn="btn"></slot>
</button>

然后在父级组件中,我们可以使用v-slot带一个值来定义插槽prop的名字,结构如:v-slot="slotProps"v-slot:default="slotProps"。如下:

<base-button v-slot="slotprops">
  <span>{{ btn.text }}</span>
</base-button>

当然我们也可以在父级组件中使用template元素,将v-slot绑定到template元素上,例如:

<base-button>
  <template v-slot="slotProps">
    <span>{{ btn.text }}</span>
  </template>
</base-button>

然后父级中绑定的数据,我们应该修改成slotProps.btn.text。这样,我们就完成了插槽之间的数据传递。完整代码如下:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-button v-slot="slotProps">
            <template >
                <span>{{ slotProps.text }}</span>
            </template>
        </base-button>
    </div>
    <script>
        Vue.component('base-button', {
            template: `
                <button type="button" class="btn">
                    <slot :text="text"></slot>
                </button>
            `,
            data(){
                return{
                    text:"submit"
                }
            }
        })
        var vm = new Vue({
            el: "#app",
            data: {

            }
        });
    </script>
</body>

</html>

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

在这个例子中,我们将包含所有插槽prop的对象命名为slotProps,当然,也可以换其它的命名,这取决于每个人自己的想法。在这里,我们需要注意的就是,当被提供的插槽只有默认插槽的时候,组件的标签才可以被当做模板使用,我们才可以将v-slot绑定到组件标签上,如下:

<base-button v-slot:default="slotProps">
    <span>{{ slotProps.text }}</span>
</base-button>

当然,这种写法是还可以更简单的,vue把不带参数的v-slot指定为默认插槽。如下:

<base-button v-slot="slotProps">
    <span>{{ slotProps.text }}</span>
</base-button>

当有多个插槽,也就是有具名插槽的时候,以上的写法是不被允许的,因为这会导致作用域不明确。如以下示例:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-layout v-slot="footer">
            <template v-slot:header>
                <h1>这是头部内容</h1>
            </template>
        </base-layout>
    </div>
    <script>
        Vue.component('base-layout', {
            template: `
                <div class="page">
                    <header class="base-header">
                        <!-- 头部 -->
                        <slot name="header"></slot>
                    </header>
                    <main class="base-main">
                      <!-- 主体内容 -->
                       <slot name="main"></slot>
                    </main>
                    <footer class="base-footer">
                      <!-- 底部 -->
                       <slot name="footer"></slot>
                    </footer>
                </div>
            `
        })
        var vm = new Vue({
            el: "#app",
            data: {

            }
        });
    </script>
</body>

</html>

vue会在浏览器控制台给出一个提示,如下图:

clipboard.png

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

只要出现多个插槽,就应该将v-slot绑定到<template>元素上。如下:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-layout>
            <template v-slot:header>
                <h1>这是头部内容</h1>
            </template>
            <template v-slot:footer>
                <h1>这是底部内容</h1>
            </template>
        </base-layout>
    </div>
    <script>
        Vue.component('base-layout', {
            template: `
                <div class="page">
                    <header class="base-header">
                        <!-- 头部 -->
                        <slot name="header"></slot>
                    </header>
                    <main class="base-main">
                      <!-- 主体内容 -->
                       <slot name="main"></slot>
                    </main>
                    <footer class="base-footer">
                      <!-- 底部 -->
                       <slot name="footer"></slot>
                    </footer>
                </div>
            `
        })
        var vm = new Vue({
            el: "#app",
            data: {

            }
        });
    </script>
</body>

</html>

这样;vue就不会在浏览器中给出警告了。你可以狠狠点击此处具体示例查看效果。

作用域插槽的工作原理其实就是将插槽内容包括在一个封装的单个参数的函数里面,就像如下所示:

function slot(slotProps){
    //插槽内容
}

这也就意味着v-slot的值实际上就是任何可以用作函数参数的JavaScript表达式,因此,在浏览器支持或者单文件组件的情况下,我们也是可以使用es2015的解构赋值来作为传入的插槽。例如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-button>
            <template v-slot="{ user }">
                <span>{{ user.firstName }}</span>
            </template>
        </base-button>
    </div>
    <script>
        Vue.component('base-button', {
            template: `
                <button type="button">
                    <slot :user="user"></slot>
                </button>
            `,
            data(){
                return{
                    user:{
                        firstName:"evening",
                        lastName:"water"
                    }
                }
            }
        })
        var vm = new Vue({
            el: "#app",
            data: {
                
            }
        });
    </script>
</body>

</html>

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

你甚至还可以将插槽prop重命名,例如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-button>
            <template v-slot="{ user:person }">
                <span>{{ person.firstName }}</span>
            </template>
        </base-button>
    </div>
    <script>
        Vue.component('base-button', {
            template: `
                <button type="button">
                    <slot :user="user"></slot>
                </button>
            `,
            data(){
                return{
                    user:{
                        firstName:"evening",
                        lastName:"water"
                    }
                }
            }
        })
        var vm = new Vue({
            el: "#app",
            data: {
                
            }
        });
    </script>
</body>

</html>

如上,我们将user重命名为person,你可以狠狠点击此处具体示例查看效果。

我们甚至还可以定义后备内容,用于插槽propundefined的情况,比如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-button>
            <template v-slot="{ user = { firstName:'evening'} }">
                <span>{{ user.firstName }}</span>
            </template>
        </base-button>
    </div>
    <script>
        Vue.component('base-button', {
            template: `
                <button type="button">
                    <slot>{{ user.lastName }}</slot>
                </button>
            `,
            data(){
                return{
                    user:{
                       lastName:"water"
                    }
                }
            }
        })
        var vm = new Vue({
            el: "#app",
            data: {
                
            }
        });
    </script>
</body>

</html>

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

我们也可以将具名插槽进行缩写,也就是把v-slot:缩写为#。例如,将v-slot:footer缩写为#footer,值得注意的就是这种写法是不被允许的,#={user}。我们必须要指定插槽名,哪怕是默认插槽也一样。如#default={user}。如下例:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-layout>
            <template #default>
                <span>这是默认插槽的内容</span>
            </template>
            <template #header>
                <h1>这是头部内容</h1>
            </template>
            <template #main>
                <p>这是主题内容</p>
            </template>
            <template #footer>
                <span>这是底部的内容</span>
            </template>
        </base-layout>
    </div>
    <script>
        Vue.component('base-layout', {
            template: `
                <div class="page">
                    <slot></slot>
                    <header class="base-header">
                        <!-- 头部 -->
                        <slot name="header"></slot>
                    </header>
                    <main class="base-main">
                      <!-- 主体内容 -->
                       <slot name="main"></slot>
                    </main>
                    <footer class="base-footer">
                      <!-- 底部 -->
                       <slot name="footer"></slot>
                    </footer>
                </div>
            `
        })
        var vm = new Vue({
            el: "#app",
            data: {

            }
        });
    </script>
</body>

</html>

如果我们将#default中的default去掉,那么vue会在浏览器中给出一个警告,如下图所示:

clipboard.png

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

我们也可以绑定动态插槽,如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-layout>
            <template #default>
                <span>这是默认插槽的内容</span>
            </template>
            <template v-slot:[slotname]>
                <h1>这是头部内容</h1>
            </template>
            <template #main>
                <p>这是主题内容</p>
            </template>
            <template #footer>
                <span>这是底部的内容</span>
            </template>
        </base-layout>
    </div>
    <script>
        Vue.component('base-layout', {
            template: `
                <div class="page">
                    <slot></slot>
                    <header class="base-header">
                        <!-- 头部 -->
                        <slot name="header"></slot>
                    </header>
                    <main class="base-main">
                      <!-- 主体内容 -->
                       <slot name="main"></slot>
                    </main>
                    <footer class="base-footer">
                      <!-- 底部 -->
                       <slot name="footer"></slot>
                    </footer>
                </div>
            `
        })
        var vm = new Vue({
            el: "#app",
            data: {
                slotname:'header'
            }
        });
    </script>
</body>

</html>

动态插槽就是以v-slot:[slotName]的形式,但在这里需要注意一点,那就是动态插槽的插槽名不能包含大写字母,否则,vue会在浏览器中给出一个警告,如下图所示:

clipboard.png

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

关于插槽的基础就只有这些,但要深入的了解可以浏览这些诸如Vue Virtual Scroller,Vue Promised,Portal Vue 等库。

5.动态组件与异步组件

在前面,我们曾完成这样利用is特性的组件切换示例,is绑定组件名is绑定组件选项对象。现在我们来尝试修改一下示例的代码,如下:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>keep-alive</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .tab-component-demo {
            width: 900px;
            margin: 15px auto;
            padding: 15px 10px;
        }

        button {
            outline: none;
            border: 1px solid #ebebeb;
            background-color: transparent;
            display: inline-block;
            padding: 6px 7px;
            text-align: center;
            line-height: 1;
            transition: all 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
            cursor: pointer;
            font-size: 16px;
            border-radius: 4px;
        }

        button:hover,
        button:active {
            border-color: #58e9fc;
            background-color: #0abce9;
            color: #ffffff;
        }

        .ew-page {
            border: 1px solid #999;
            padding: 15px 8px;
        }
    </style>
</head>

<body>
    <div id="app">
        <div class="tab-component-demo">
            <button v-for="(tab,index) in tabs" :key="index" @click="currentTab = tab">
                {{ tab }}
            </button>
            <component :is="currentTabComponent"></component>
        </div>
    </div>
    <script>
        const childComponents = [
            {
                name: "tab-header",
                component: {
                    template: `<div class='home-content'>the header of home page!</div>`
                }
            },
            {
                name: "tab-content",
                component: {
                    template: `<div class='home-content'>the content of home page!</div>`
                }
            },
            {
                name: "tab-footer",
                component: {
                    template: `<div class='home-content'>the footer of home page!</div>`
                }
            }
        ];
        Vue.component("tab-home", {
            template: `
                <div class="ew-page">
                  <button
                      v-for="(childTab,index) in childTabs"
                      :key="index"
                      @click="currentChildTab = childTab"
                      >
                      {{ childTab.name.slice(4) }}
                   </button>
                   <component :is="currentChildTab.component"></component>
                </div>
            `,
            data() {
                return {
                    childTabs: childComponents,
                    currentChildTab: childComponents[0]
                }
            }
        });
        Vue.component("tab-list", {
            template: `
                <div class="ew-page">
                 list page!
                </div>
            `
        });
        Vue.component("tab-contact", {
            template: `
                <div class="ew-page">
                contact page!
                </div>
            `
        });
        var vm = new Vue({
            data: {
                currentTab: "home",
                tabs: ["home", "list", "contact"]
            },
            computed: {
                currentTabComponent: function () {
                    return "tab-" + this.currentTab.toLowerCase();
                }
            }
        }).$mount(document.getElementById("app"));
    </script>
</body>

</html>

你会注意到,当我们第一个导航(即home)所展示的页面中又继续包含3个子导航,我们可以再继续切换。但是当我们切换了子导航之后,再去切换父导航,最后切换回到父导航home,我们会发现之前切换过的子导航会回到初始状态(即header),这是因为每次切换,vue都新创建了一个组件实例。你可以狠狠点击此处具体实例查看效果。

尽管重新创建动态组件是非常有用的,但在这个例子中,我们可能更希望组件在第一次被创建的时候就缓存下来。为了解决这个问题,vue提供了<keep-alive>组件。我们只需要用这个组件包裹动态组件即可,如下:

  <keep-alive>
    <component :is="currentTabComponent"></component>
  </keep-alive>

现在我们再来测试一下这个修改后的示例具体示例。我们可以看到,现在组件已经保持了它的状态。

注意一点,<keep-alive>组件要求被切换的组件都有自己的名字,不论是通过组件的name选项还是局部或者全局注册。

你可以在keep-alive API文档中查看更多细节内容。

查看原文

该账号已被查封 赞了文章 · 7月20日

从零开始学习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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 :data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script data-original="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 :data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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 data-original="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>

用鼠标滚轮键点击按钮,就可以查看效果,你可以狠狠点击此处具体示例查看效果。

12.表单输入绑定

vue可以通过v-model指令在<input>,<select>,<textarea>这三种表单元素上创建数据双向绑定,这个指令会根据表单控件类型选择正确的方法来更新元素。v-model只不过是一个语法糖,通过监听用户输入事件从而达到更新数据的目的,这在一些前端极端场景中尤为重要。

在使用v-model指令时,需要注意它会忽略所有表单元素中的valued,selected,checked等特性的初始值,而将vue实例的data对象作为数据初始值,所以在使用过程中需要在data选项中声明初始值。

一个最简单的示例如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
  </head>
  <body>
    <div id="app">
      <input type="text" v-model="message" />
      <p>绑定的数据:{{ message }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
          message: "hello,vue.js!"
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

当用户在输入框中输入数据的时候,对应绑定在data选项中的数据就会发生改变。你可以狠狠点击此处具体示例查看效果。

还可以绑定在textarea标签上,如:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
  </head>
  <body>
    <div id="app">
      <textarea type="text" v-model="message"></textarea>
      <p>绑定的数据:{{ message }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
          message: "hello,vue.js!"
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

绑定到单个复选框示例如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
  </head>
  <body>
    <div id="app">
      <input type="checkbox" v-model="isChecked">
      <p>是否选中:{{ isChecked }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
            isChecked: false
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

除此之外,还可以将多个复选框绑定到一个数组,如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
  </head>
  <body>
    <div id="app">
      <input type="checkbox" id="name" value="eveningwater" v-model="checkArr"/>
      <label for="name">eveningwater</label>
      <input type="checkbox" id="sex" value="male" v-model="checkArr"/>
      <label for="sex">male</label>
      <input type="checkbox" id="skill" value="write code" v-model="checkArr" />
      <label for="skill">write code</label>
      <p>选中的值:{{ checkArr }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
            checkArr: []
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

还可以绑定单选按钮,如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
  </head>
  <body>
    <div id="app">
      <input type="radio" id="first" value="first" v-model="checkedValue">
      <label for="first">first</label>
      <input type="radio" id="second" value="second" v-model="checkedValue" />
      <label for="second">second</label>
      <p>选中的值:{{ checkedValue }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
            checkedValue: []
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

绑定到select的示例如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
  </head>
  <body>
    <div id="app">
      <select v-model="message">
          <option disabled value="">请选择值</option>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
      </select>
      <p>选中的值:{{ message }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
          message: ""
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

在这里需要注意的就是,如果v-model绑定的初始值未匹配任何选项,那么select元素就会渲染为'未选中状态',在IOS中,这会导致用户无法选中第一项,因为并没有触发change事件,因此更推荐像以上那样提供一个默认禁用的选项。

select多选时:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
  </head>
  <body>
    <div id="app">
      <select v-model="message" multiple>
          <option disabled value="">请选择值</option>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
      </select>
      <p>选中的值:{{ message }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
          message: []
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

还可以用v-for指令渲染动态选项:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
  </head>
  <body>
    <div id="app">
      <select v-model="selected">
        <option v-for="(option,index) in selects" :value="option.value">{{
          option.text
        }}</option>
      </select>
      <p>选中的值:{{ selected }}</p>
    </div>
    <script>
      var vm = new Vue({
        data: {
          selected: "first",
          selects: [
            {
              value: "first",
              text: "第一项"
            },
            {
              value: "second",
              text: "第二项"
            },
            {
              value: "third",
              text: "第三项"
            }
          ]
        }
      });
      vm.$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

对于单选按钮,复选框,选择框来说,v-model指令通常绑定的是静态字符串,对于复选框,还可以是布尔值。它们的value值,我们也是可以绑定到vue实例的data选项上的,而且绑定的值不一定是静态字符串。一个完整的示例如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      .form-radio,
      .form-checkbox,
      .form-select {
        width: 600px;
        padding: 16px 8px;
        background-color: #e2e3e4;
        margin: 15px auto;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="form-radio">
        <input type="radio" id="male" v-model="sex" value="男" />
        <label for="male">男</label>
        <input type="radio" id="female" v-model="sex" value="女" />
        <label for="female">女</label>
        <p>你选择的是:{{ sex }}</p>
        <input type="radio" id="male" v-model="bindSex" v-bind:value="bindMale" />
        <label for="male">{{ bindMale }}</label>
        <input type="radio" id="female" v-model="bindSex" v-bind:value="bindFemale" />
        <label for="female">{{ bindFemale}}</label>
        <p>将value值动态绑定到vue实例上:{{ bindSex }}</p>
      </div>
      <div class="form-checkbox">
        <input type="checkbox" id="checked" v-model="isChecked" />
        <label for="checked">绑定的是布尔值</label>
        <p>布尔值:{{ isChecked }}</p>
        <input
          type="checkbox"
          v-model="toggle"
          true-value="是"
          false-value="否"
        />
        <p>是否选中:{{ toggle }}</p>
      </div>
      <div class="form-select">
        <select v-model="selected">
          <option value="请选择你喜欢做的事情" disabled
            >请选择你喜欢做的事情</option
          >
          <option
            v-for="(option,index) in options"
            :key="index"
            :value="option.text"
            v-text="option.text"
          ></option>
        </select>
        <p>你喜欢:{{ selected }}</p>
      </div>
    </div>
    <script>
      var vm = new Vue({
        el: "#app",
        data: {
          bindSex: "",
          bindMale: "男",
          bindFemale: "女",
          isChecked: false,
          sex: "男",
          toggle: "是",
          selected: "",
          options: [
            {
              text: "读书"
            },
            {
              text: "游戏"
            },
            {
              text: "唱歌"
            }
          ]
        }
      });
      console.log(vm.toggle === "是");
    </script>
  </body>
</html>

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

在表单绑定的时候,vue也提供了3个修饰符.lazy,.number,.trim,.lazy的作用就是改变表单元素触发的input事件,一般说来,v-model通常是在每次input事件(即用户只要输入内容时),将输入框的值与绑定的数据进行同步更新,如果不想要与input事件同步,而是与change事件同步,那么就可以使用.lazy修饰符,.number修饰符的作用就是将输入的值转换成number类型,因为默认输入的值时string(字符串)类型。而.trim修饰符的作用就是自动过滤掉首位的空白。示例如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>v-model</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      .font-green {
        color: #00ff31;
      }
      .box {
        width: 400px;
        padding: 20px;
        line-height: 35px;
        margin: 20px auto;
        background-color: #e2e2e2;
      }
      .box input{
          width: 100%;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="box">
        <h2>未使用<span class="font-green">.lazy</span>修饰符:</h2>
        <input type="text" v-model="notMsg" />
        <p>{{ notMsg }}</p>
      </div>
      <div class="box">
        <h2>使用<span class="font-green">.lazy</span>修饰符:</h2>
        <input type="text" v-model.lazy="msg" />
        <p>{{ msg }}</p>
      </div>
      <div class="box">
        <h2>未使用<span class="font-green">.number</span>修饰符:</h2>
        <input type="text" v-model="notCount" />
        <p>
          {{ typeof notCount === 'string' ? '字符串值:"'+notCount+'"' : notCount }}
        </p>
      </div>
      <div class="box">
        <h2>使用<span class="font-green">.number</span>修饰符:</h2>
        <input type="text" v-model.number="count" />
        <p>
          {{ typeof count === 'number' ? '数值:' + count : count }}
        </p>
      </div>
      <div class="box">
        <h2>未使用<span class="font-green">.trim</span>修饰符:</h2>
        <input type="text" v-model="notTrim" />
        <p>{{ notTrim }}</p>
      </div>
      <div class="box">
        <h2>使用<span class="font-green">.trim</span>修饰符:</h2>
        <input type="text" v-model.trim="trim" />
        <p>{{ trim }}</p>
      </div>
    </div>
    <script>
      var vm = new Vue({
        el: "#app",
        data: {
          notMsg: "未使用.lazy修饰符",
          msg: "使用.lazy修饰符",
          notCount: "1",
          count: 1,
          notTrim: "首尾如果有空白,值就会有空白 ",
          trim: "首尾如果有空白,值也不会有空白,因为.trim消除了空白"
        }
      });
    </script>
  </body>
</html>

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

13.组件基础

组件是可复用的vue实例,除了el选项不一致以外,其余的data,prop,computed,methods,watch等选项都是一样的,并且组件都需要一个名字。一个基本的示例如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
      <button-num></button-num>
    </div>
    <script>
      Vue.component("button-num", {
        template: `<button v-on:click="num++">You click me {{ num }} times!</button>`,
        data: function() {
          return {
            num: 0
          };
        }
      });
      var vm = new Vue().$mount(document.getElementById("app"));
    </script>
  </body>
</html>

以上定义了一个简单的示例,通过这个示例我们可以知道组件是可复用的vue实例因此可以接收与new Vue()一样的选项参数,组件的名字就是button-num,通过Vue.component()来全局注册一个组件,这个方法接受2个参数,第一个参数就是组件名,第二个参数则是组件配置对象,配置对象的选项与vue实例是一致的。
你可以狠狠点击此处具体示例进行查看。

组件还可以复用,当基础组件要使用的多的时候,可以使用v-for来渲染。如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
      <button-num></button-num>
      <button-num></button-num>
      <button-num v-for="n in 5" :key="n"></button-num>
    </div>
    <script>
      Vue.component("button-num", {
        template: `<button v-on:click="num++">You click me {{ num }} times!</button>`,
        data: function() {
          return {
            num: 0
          };
        }
      });
      var vm = new Vue().$mount(document.getElementById("app"));
    </script>
  </body>
</html>

你可以点击这里具体示例查看该示例。在这里,我们需要注意一点,组件中的data选项必须是一个函数,如果不是一个函数,控制台则会弹出一个警告,如下图所示:

图片描述

一个大型的网站项目,比如一个博客,我们可以将博客分割成多个小组件,比如导航组件,比如内容组件等等,这由许多个小组件组织起来,就成了一个SPA单页应用。如下图所示:

为了能够在模板中使用,需要先注册这些组件以便vue根实例来识别。注册组件有两种方式:全局注册局部注册,在这里,我们都是通过Vue.component()来全局注册的。
全局注册组件之后,就表示你可以在你被注册之后任何vue根实例中使用,也包括所有子组件中,到目前为止,我们不需要考虑局部注册。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
        <blog-component title="博客组件的标题"></blog-component>
    </div>
    <script>
      Vue.component("blog-component", {
        template: `<h3>{{title}}</h3>`,
        props:['title']
      });
      var vm = new Vue().$mount(document.getElementById("app"));
    </script>
  </body>
</html>

在上例中,我们可以像访问data选项一样访问props传递的值,props可以接收任意类型传递的值,你可以狠狠点击此处具体示例进行查看。

一个prop被注册之后,我们其实可以像下面这样把数据作为一个自定义的特性传递:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
        <blog-component title="博客组件的标题1"></blog-component>
        <blog-component title="博客组件的标题2"></blog-component>
        <blog-component title="博客组件的标题3"></blog-component>
    </div>
    <script>
      Vue.component("blog-component", {
        template: `<h3>{{title}}</h3>`,
        props:['title']
      });
      var vm = new Vue().$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

然而,在一个典型的应用当中,我们需要传递的数据不可能是如此简单的一个字符串,有可能是一个对象,甚至是一个数组。这样,以上的示例,我们需要如此修改:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
      <blog-component v-for="(blog,index) in blogData" :key="index" v-bind:title="blog.title"></blog-component>
    </div>
    <script>
      Vue.component("blog-component", {
        template: `<h3>{{title}}</h3>`,
        props: ["title"]
      });
      var vm = new Vue({
        data: {
          blogData: [
            { title: "博客组件的标题1", content: "博客组件的内容1" },
            { title: "博客组件的标题2", content: "博客组件的内容2" },
            { title:"博客组件的标题3",content:"博客组件的内容3"}
          ]
        }
      }).$mount(document.getElementById("app"));
    </script>
  </body>
</html>

如上所示,你应该会发现我们使用v-bind来动态传递prop,这在你不知道要渲染的具体内容,尤其是当渲染一个API数据接口时是非常有用的。到目前为止,prop需要了解的就只有这些呢。你可以点击此处具体示例查看该示例。

但在一篇博文当中,我们不仅仅是需要添加标题,最起码我们需要渲染博文的内容,因此你的模板内容就需要做修改:

template:`<h3>{{ title }}</h3><div v-html="content"></div>`;

然而如果这样写的话,vue会显示一个错误,every component must have a single root element (每个组件必须只有一个根元素),因此我们需要用一个根元素来包裹,如下:

template:`<div class="blog-demo"><h3>{{ title }}</h3><div v-html="content"></div></div>`;

当组件变得越来越复杂时,如果单独定义一个prop,这会看起来越来越复杂。一篇博文可能会包含发布日期,评论等,我们如此做法,代码看起来也不够简洁:

<blog-component v-for="(blog,index) in blogData" :key="index" v-bind:title="blog.title" :content="content" :date="date" :recommen="commen"></blog-component>

重构一下这个组件,让它接收一个单独的prop,如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
      <blog-component v-for="(blog,index) in blogData" :key="index" :blog="blog"></blog-component>
    </div>
    <script>
      Vue.component("blog-component", {
        props: ["blog"],
        template: `
            <div class="blog-demo">
                <h3>{{blog.title}}</h3>
                <div v-html="blog.content"></div>
                <p>
                    <span class="date">{{ blog.date }}</span>
                    </span class="recommen">{{ blog.commen }}</span>
                </p>
            </div>
        `
      });
      var vm = new Vue({
        data: {
          blogData: [
            { title: "博客组件的标题1", content: "博客组件的内容1",date:"2019年2月20日",commen:"真棒!" },
            { title: "博客组件的标题2", content: "博客组件的内容2",date:"2019年2月20日",commen:"真的好棒!" },
            { title:"博客组件的标题3",content:"博客组件的内容3",date:"2019年2月20日",commen:"真的真的非常棒!"}
          ]
        }
      }).$mount(document.getElementById("app"));
    </script>
  </body>
</html>

你可以狠狠点击此处具体示例查看。在开发blog组件时候,有时候,我们不仅需要建立父组件向子组件的通信,我们也许还需要建立子组件向父组件传递数据之间的通信,这时候,我们可以采用自定义事件来向父组件传递数据。例如,我们想要添加一个按钮,用于改变字体的颜色,我们可以自定义一个颜色框,然后让用户选择字体颜色来改变字体的颜色。如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
      <blog-component v-for="(blog,index) in blogData" :key="index" :blog="blog" @change-color="setColor($event,index)"></blog-component>
    </div>
    <script>
      Vue.component("blog-component", {
        props: ["blog"],
        template: `
            <div class="blog-demo" :style="{color:blog.color}">
                <h3>{{blog.title}}</h3>
                <div v-html="blog.content"></div>
                <p>
                    <span class="date">{{ blog.date }}</span>
                    </span class="recommen">{{ blog.commen }}</span>
                </p>
                <div class="setFontColor">
                    <input type="color" v-model="colorValue" />
                    <button type="button" @click="$emit('change-color',colorValue)">改变字体颜色</button>
                </div>
            </div>
        `,
        data:function(){
            return{
                colorValue:""
            }
        }
      });
      var vm = new Vue({
        data: {
          blogData: [
            { title: "博客组件的标题1", content: "博客组件的内容1",date:"2019年2月20日",commen:"真棒!" },
            { title: "博客组件的标题2", content: "博客组件的内容2",date:"2019年2月20日",commen:"真的好棒!" },
            { title:"博客组件的标题3",content:"博客组件的内容3",date:"2019年2月20日",commen:"真的真的非常棒!"}
          ]
        },
        methods:{
            setColor:function(color,index){
                this.$set(this.blogData[index],'color',color);
            }
        }
      }).$mount(document.getElementById("app"));
    </script>
  </body>
</html>

通过以上示例我们可以看出,父组件通过像处理native DOM事件一样通过v-on指令监听子组件的任意事件,然后子组件通过$emit()方法来自定义一个事件,这个方法接收两个参数,第一个参数就是自定义的事件名(在这里是change-color),第二个参数则是要传递的参数,在这里,传递了颜色选择器选择的颜色值colorValue,然后父组件使用这个事件,有了这个自定义的v-on:change-color监听器,父组件就可以为数组的每一项对象新增一个color属性,通过将设置style,将color属性绑定到style标签内,然后父组件就可以更新数据,并渲染结果了。你可以狠狠点击此处具体示例来查看。

通过自定义事件我们还可以实现在组件上使用v-model指令,如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
      <ew-input v-model="msg"></ew-input>
      <p>{{ msg }}</p>
    </div>
    <script>
      Vue.component("ew-input", {
        props: ["value"],
        template: `
          <input type="text" :value="value"  v-on:input="$emit('input',$event.target.value)" />
        `
      });
      var vm = new Vue({
        data: {
          msg: "在组件上使用v-model指令"
        }
      }).$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

和HTML元素一样,通常我们也需要向组件传递内容,这时候,我们可以使用插槽。如:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        .ew-alert{
            position:absolute;
            left: 50%;
            top: 50%;
            min-width: 300px;
            min-height: 200px;
            background-color: #ffffff;
            box-shadow: 0 0 15px #939393;
            text-align: center;
            transform: translate(-50%,-50%);
            border-radius: 15px;
        }
        .ew-alert > .ew-alert-title{
            padding: 10px 0;
        }
        .ew-alert > .ew-alert-content{
            padding: 8px 9px;
        }
    </style>
  </head>
  <body>
    <div id="app">
      <ew-alert title="弹出框标题"><p>{{ msg }}</p></ew-alert>
    </div>
    <script>
      Vue.component("ew-alert", {
        props: ["title"],
        template: `
          <div class="ew-alert">
            <div class="ew-alert-title">{{ title }}</div>
            <div class="ew-alert-content"><slot></slot></div>
          </div>
        `
      });
      var vm = new Vue({
        data: {
          msg: "弹出框主题内容"
        }
      }).$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

有时候,我们需要动态切换组件,也就是在做一个选项卡组件的时候,我们把每一个导航对应一个导航组件,这时候就需要动态切换组件。如果要动态切换组件,我们可以使用一个特性is来实现。例如:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      .tab-component-demo{
          width: 900px;
          margin: 15px auto;
          padding: 15px 10px;
      }
      button {
        outline: none;
        border: 1px solid #ebebeb;
        background-color: transparent;
        display: inline-block;
        padding: 6px 7px;
        text-align: center;
        line-height: 1;
        transition: all 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
        cursor: pointer;
        font-size: 16px;
        border-radius: 4px;
      }
      button:hover,
      button:active {
        border-color: #58e9fc;
        background-color: #0abce9;
        color: #ffffff;
      }
      .ew-page{
          border: 1px solid #999;
          padding: 15px 8px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="tab-component-demo">
        <button
          v-for="(tab,index) in tabs"
          :key="index"
          @click="currentTab = tab"
        >
          {{ tab }}
        </button>
        <component :is="currentTabComponent"></component>
      </div>
    </div>
    <script>
      Vue.component("tab-home", {
        template: `
          <div class="ew-page">
           home page!
          </div>
        `
      });
      Vue.component("tab-list", {
        template: `
          <div class="ew-page">
           list page!
          </div>
        `
      });
      Vue.component("tab-contact", {
        template: `
          <div class="ew-page">
          contact page!
          </div>
        `
      });
      var vm = new Vue({
        data: {
          currentTab: "home",
          tabs: ["home", "list", "contact"]
        },
        computed: {
          currentTabComponent: function() {
            return "tab-" + this.currentTab.toLowerCase();
          }
        }
      }).$mount(document.getElementById("app"));
    </script>
  </body>
</html>

你可以狠狠点击此处具体示例查看。通过这个示例,我们可以知道is绑定的值currentTabComponent可以是一个组件名,但事实上,我们还可以绑定一个组件的选项对象,如下例:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      .tab-component-demo {
        width: 900px;
        margin: 15px auto;
        padding: 15px 10px;
      }
      button {
        outline: none;
        border: 1px solid #ebebeb;
        background-color: transparent;
        display: inline-block;
        padding: 6px 7px;
        text-align: center;
        line-height: 1;
        transition: all 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
        cursor: pointer;
        font-size: 16px;
        border-radius: 4px;
      }
      button:hover,
      button:active {
        border-color: #58e9fc;
        background-color: #0abce9;
        color: #ffffff;
      }
      .ew-page {
        border: 1px solid #999;
        padding: 15px 8px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="tab-component-demo">
        <button
          v  v-for="(tab,index) in tabs"
          :key="index"
          @click="currentTab = tab"
        >
          {{ tab.name.slice(4) }}
        </button>
        <component :is="currentTab.component"></component>
      </div>
    </div>
    <script>
      var tabs = [
        {
          name: "tab-home",
          component: {
            template: <div class="ew-page">home page! </div>`
          }
        },
        {
          name: "tab-list",
          component:{
            template: `<div class="ew-page">list page!</div>`
          }
        },
        {
          name: "tab-contact",
          component:{
            template: `<div class="ew-page">contact page!</div>`
          }
        }
      ];
      var vm = new Vue({
        data: {
          currentTab: tabs[0],
          tabs: tabs
        }
      }).$mount(document.getElementById("app"));
    </script>
  </body>
</html>

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

在解析DOM模板时,我们还需要注意一点,那就是有些HTML元素,诸如<ul>,<ol>,<table>,<select>,对于哪些元素出现在其内部是有严格限制的,而有些元素,诸如<li>,<tr>,<option>,只能出现在特定的元素内部。这会导致我们在使用这些有约束的元素时出现问题,如:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
        <table>
            <ew-div></ew-div>
        </table>
    </div>
    <script>
      Vue.component("ew-div", {
        template: `
           <div>测试</div>
        `
      });
      var vm = new Vue().$mount(document.getElementById("app"));
    </script>
  </body>
</html>

我们可以打开浏览器控制台,看到以上自定义的ew-div组件中的元素,被渲染到最外部去了,如下图所示:

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

幸运的是,你可以使用is解决这个问题。如:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
        <table>
            <tr is="ew-div"></tr>
        </table>
    </div>
    <script>
      Vue.component("ew-div", {
        template: `
           <div>测试</div>
        `
      });
      var vm = new Vue().$mount(document.getElementById("app"));
    </script>
  </body>
</html>

此时再看浏览器控制台,你会发现自定义的组件已经被渲染在table内部,如下图所示:

图片描述

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

当然,如果我们从如下来源使用模板的话,这条限制是不存在的。

1.字符串(如:template:'')
2.单文件组件(.vue)
3.<script type="text/x-template"></script>

到目前为止,需要了解的组件基础知识大概就只有这些呢,接下来是深入组件的了解。

四.组件深入

1.组件注册

在注册一个组件的时候,我们需要为组件起一个名字,比如全局注册的时候,我们已经知道了

Vue.component('my-component-name',{});

也就是Vue.component()的第一个参数。在给组件起名的时候,如果不是字符串模板或者单文件组件,直接在DOM中使用组件,组件名是有规范的(字母全小写,并且单词之间用连字符连接)。你可以查阅API风格指南

这也就是说定义组件名有两种方式,即(kebab-case)短横线分隔命名(PascalCase)首字母大写命名。当使用短横线命名时,我们在使用组件的时候,也必须是采用短横线命名,而使用首字母大写命名则既可以采用首字母大写也可以采用短横线命名,当然在DOM模板而非字符串模板或者单文件组件中,也还是只能采用短横线命名。

到目前为止,我们只是用Vue.component来全局注册一个组件,如:

Vue.component('com-a',{})
Vue.component('com-b',{})
Vue.component('com-c',{})

然后,你就可以在new Vue()根实例内的任何组件中使用这三个组件,比如:

//js
new Vue(el:"#app")
//html
<div id="app">
    <com-a>
      <com-b></com-b>
    </com-a>
    <com-b>
      <com-c></com-c>
    </com-b>
    <com-c>
      <com-a></com-a>
    </com-c>
</div>

这往往是不够理想的,比如在一个通过webpack构建的系统应用中,全局注册所有的组件,则意味着即便你不再使用一个组件了,但组件仍然被包含在最终构建的结果中,这也就增加了一些无谓的JavaScript代码。

不过,我们可以局部注册组件。局部注册也就是将一个组件当作普通的JavaScript对象来定义。如下例:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
        <component-a></component-a>
        <component-b></component-b>
    </div>
    <script>
      var componentA = {
          template:`<div>a</div>`
      },
      componentB = {
          template:`<div>b</div>`
      }
      var vm = new Vue(
          {
              el:"#app",
              components:{
                  'component-a':componentA,
                  'component-b':componentB
              }
          }
      )
    </script>
  </body>
</html>

通过JavaScript定义一个组件对象,然后在vue实例中添加components选项,components选项的属性名就是组件名,属性值就是这个组件的选项对象。你可以狠狠点击此处具体示例查看效果。

但是局部注册组件是无法在其子组件中使用另一局部注册的组件的,如果想要这样的效果,那么代码需要写成下面那样:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>component</title>
  </head>
  <body>
    <div id="app">
      <component-a></component-a>
      <component-b></component-b>
    </div>
    <script>
      var componentA = {
          template: `<div>a</div>`
        },
        componentB = {
          template: `<div>b<component-a></component-a></div>`,
          components:{
              'component-a':componentA
          }
        };
      var vm = new Vue({
        el: "#app",
        components: {
          "component-a": componentA,
          "component-b": componentB
        }
      });
    </script>
  </body>
</html>

也就是往局部注册组件对象中添加一个components选项,注册需要添加的局部注册的组件而已。你可以狠狠点击此处具体示例查看效果。

如果是使用es6与babel以及webpack,那么代码,则更像是如下:

import componentA from './componentA.vue'

export default{
  components:{ componentA }
}

在对象中放一个componentA其实也就是componentA:componentA的缩写,也就是说这个变量名同时是:

用在模板中的自定义元素的名称。
包含了这个组件的选项的变量名。

如果你还不熟悉模块化构建,那最好还是跳过这里。

2.prop

由于HTML特性是不区分大小写的,也就是说浏览器会将所有的大写字母转换成小写字母,这也就意味着当你使用camelCase(驼峰命名法)来为prop定义的时候,你在子组件使用prop的时候需要使用等价的kebab-case(短横线命名)来命名,比如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>prop</title>
    <style>
        .ew-button {
            outline: none;
            border: 1px solid transparent;
            display: inline-block;
            border-radius: 4px;
            font-size: 14px;
            text-align: center;
            transition: color 0.2s linear, backgroud-color 0.1s linear,
                border-color 0.1s linear, box-shadow 0.2s linear;
            cursor: pointer;
            border-color: #f3f3f3;
            color: #a9adb1;
            background-color: #ffffff;
            padding: 5px 15px 6px;
        }

        .ew-button-success {
            background-color: #2196ff;
            border-color: #4ca4f1;
            color: #ffffff;
        }

        .ew-button-success:hover,
        .ew-button-success:active {
            box-shadow: 0 0 3px #943321;
            opacity: 0.8;
        }
    </style>
</head>

<body>
    <div id="app">
        <ew-button ew-type="success">{{ content }}</ew-button>
    </div>
    <script>
        var ewButton = {
            props: ["ewType"],
            template: `<button type="button" :class="'ew-button-'+ ewType" class="ew-button"><slot></slot></button>`
        };
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    content: "success"
                };
            },
            components: {
                ewButton: ewButton
            }
        });
    </script>
</body>

</html>

在这里,我们是使用的局部注册一个组件,也就是用一个对象表示一个组件,然后在Vue实例中添加components选项来注册这个组件,组件名虽然使用的camelCase(驼峰命名法),也就是ewButton,但我们也看到了,在使用这个组件的时候,需要使用kebab-case(短横线命名),即ew-button来表示,同样的,定义的prop名也是如此,在子组件中定义成ewType,但在使用的时候则是ew-prop,当然在单文件组件以及字符串模板中是不存在这个限制的,这里是使用DOM模板来表示的。

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

到目前为止,我们只看到了prop的形式只是一个字符串数组:

props:['str','num','bool','arr','obj']

但是,通常我们想要每个prop都有指定的值和类型,这时候,我们可以以对象的形式来列出prop,这些属性的名称和值分别代表prop名类型。如下:

props:{
  str:String,
  num:Number,
  bool:Boolean,
  arr:Array,
  obj:Object
}

如果指定的prop类型不对,那么浏览器就会在控制台报一个invalid prop的错误,这是vue内部实现的。

在此之前,我们可以看到prop有两种用法,第一种是静态的prop,如上例的:

<ew-button ew-type="success">{{ content }}</ew-button>

这里的ew-type就是一个静态的prop,但实际上,我们还可以用v-bind来表示一个动态的prop,以上的静态prop,我们就可以表示成如下:

<ew-button v-bind:ew-type="'success'">{{ content }}</ew-button>
//或者<ew-button :ew-type="'success'">{{ content }}</ew-button>

有了v-bind,我们就可以为prop绑定任意类型的值。例如:

以下是传递一个数值:

//即便100是静态的,我们也需要使用v-bind来告诉vue,这是一个JavaScript表达式,而不是字符串
<ew-button v-bind:ew-type="100">{{ content }}</ew-button>
//绑定一个变量名
<ew-button v-bind:ew-type="number">{{ content }}</ew-button>

在js代码中,我们就可以如此写:

new Vue({
    ...
    data(){
      return{
          number:100
      }
    }
    ...
})

以下是传递一个布尔值:

//如果不传入值,默认就是一个布尔值true
<ew-button ew-type>{{ content }}</ew-button>
//即便false是静态的,我们也需要使用v-bind来告诉vue,这是一个JavaScript表达式,而不是字符串
<ew-button :ew-type="false">{{ content }}</ew-button>
/绑定一个变量名
<ew-button :ew-type="bool">{{ content }}</ew-button>

在js代码中,我们就可以如此写:

new Vue({
    ...
    data(){
      return{
          bool:false
      }
    }
    ...
})

以下是传递一个数组:

//即便数组是静态的,我们也需要使用v-bind来告诉vue,这是一个JavaScript表达式,而不是字符串
<ew-button :ew-type="[1,2,3]">{{ content }}</ew-button>
/绑定一个变量名
<ew-button :ew-type="arr">{{ content }}</ew-button>

在js代码中,我们就可以如此写:

new Vue({
    ...
    data(){
      return{
          arr:[1,2,3]
      }
    }
    ...
})

以下是传递一个对象:

//即便对象是静态的,我们也需要使用v-bind来告诉vue,这是一个JavaScript表达式,而不是字符串
<ew-button :ew-type="{ name:'eveningwater',sex:'male'}">{{ content }}</ew-button>
/绑定一个变量名
<ew-button :ew-type="obj">{{ content }}</ew-button>

在js代码中,我们就可以如此写:

new Vue({
    ...
    data(){
      return{
          obj:{
            name:"eveningwater",
            sex:"male"
          }
      }
    }
    ...
})

如果你想将一个对象的所有属性都作为prop传入,那么你可以使用不带参数的v-bind来取代v-bind:propName。例如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>prop</title>
</head>

<body>
    <div id="app">
        <ew-div v-bind="item"></ew-div>
        //等价于<ew-div v-bind:name="item.name" v-bind:sex="item.sex"></ew-div>
    </div>
    <script>
        Vue.component('ew-div',{
            props:['name','sex'],
            template:`<div class="myDiv"><h1>{{ name }}</h1><p v-text="sex"></p></div>`,

        })
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                   item:{
                       name:"eveningwater",
                       sex:"male"
                   }
                };
            }
        });
    </script>
</body>

</html>

以上定义的v-bind="item"实际上就等价于v-bind:name="item.name"v-bind:sex="item.sex",所以,我们才可以在子组件的props里获取到namesex,并且将props的值绑定到元素中去。你可以狠狠点击此处具体示例查看效果。

所有的prop都使得父子之间的prop形成了一个单向下行绑定:父组件的prop如果发生了更新,则会向下流动到子组件中,但是反过来修改子组件的prop就不行。这样也就防止了子组件意外改变父组件的状态,从而导致应用的数据流难以理解。

这也就是说一旦父组件更新了,那么子组件也就会立刻刷新为最新的值,这也意味着你不应该修改一个子组件的prop,如果你如此做了,浏览器会发出一个警告,这是vue内部实现的。来看一个示例:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>prop</title>
</head>

<body>
    <div id="app">
        <ew-div :content="content"></ew-div>
    </div>
    <script>
        Vue.component('ew-div',{
            props:['content'],
            template:`<div class="myDiv"><p>{{ content }}</p><button type="button" @click="updateContent">改变content的值</button></div>`,
            methods:{
                updateContent(){
                    this.content = '修改了值,浏览器控制台会给出一个警告!'
                }
            }
        })
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                   content:"文档的内容"
                };
            }
        });
    </script>
</body>

</html>

如上例,当我们点击了按钮,去修改prop的值,尽管最终值发生了改变,但是浏览器控制台还是给出了一个警告信息,如下图所示:

clipboard.png

你可以狠狠点击此处具体示例查看效果。实际上,这个警告信息也就是提示,不能直接在子组件中修改prop,但是我们有两种办法解决这个问题。

1.我们将父组件传给子组件的prop作为一个初始值,然后在子组件的data选项中定义一个新的数据,将prop的值用作其初始值,然后在绑定数据的时候就不用绑定prop,而是绑定定义在data选项中的值。如下所示:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>prop</title>
</head>
<body>
    <div id="app">
        <ew-div :content="content"></ew-div>
    </div>
    <script>
        Vue.component('ew-div',{
            props:['content'],
            template:`<div class="myDiv"><p>{{ curContent }}</p><button type="button" @click="updateContent">改变content的值</button></div>`,
            data(){
                return{
                    curContent:this.content
                }
            },
            methods:{
                updateContent(){
                    this.curContent = '修改了值,浏览器控制台不会给出一个警告!'
                }
            }
        })
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                   content:"文档的内容"
                };
            }
        });
    </script>
</body>
</html>

现在,我们再点击按钮,值发生了改变,而且浏览器也没有给出警告。你可以狠狠点击此处具体示例查看效果。

2.如果是这个prop作为初始值,并且要进行转换,那么,最好还是使用一个计算属性来表示。例如:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>prop</title>
</head>
<body>
    <div id="app">
        <ew-div :content="content"></ew-div>
    </div>
    <script>
        Vue.component('ew-div',{
            props:['content'],
            template:`<div class="myDiv"><p>{{ curContent }}</p></div>`,
            computed:{
                curContent(){
                    return '计算属性:' + this.content;
                }
            }
        })
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                   content:"文档的内容"
                };
            }
        });
    </script>
</body>
</html>

你可以狠狠点击此处具体示例查看效果。又或者,综合以上两个方法,修改如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>prop</title>
</head>
<body>
    <div id="app">
        <ew-div :content="content"></ew-div>
    </div>
    <script>
        Vue.component('ew-div',{
            props:['content'],
            template:`<div class="myDiv"><p>{{ curContent }}</p><button type="button" @click="updateContent">改变content的值</button></div>`,
            data(){
                return{
                    newContent:""
                }
            },
            computed:{
                curContent:{
                    get(){
                        if(this.newContent){
                            return '计算属性:' + this.newContent;
                        }else{
                            return '计算属性:' + this.content;
                        }
                    },
                    set(newVal){
                        this.newContent = newVal;
                    }
                }           
            },
            methods:{
                updateContent(){
                    this.curContent = '修改了值,浏览器控制台不会给出一个警告!'
                }
            }
        })
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                   content:"文档的内容"
                };
            }
        });
    </script>
</body>
</html>

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

注意在JavaScript数组和对象中,由于数组和对象是通过引用传入的,因此修改子组件的数据可能会影响到父组件的状态。

在使用prop的时候,我们还可以为prop指定验证要求,如果不满足验证要求,则vue会在浏览器控制台中警告你。这在对于开发一个被别人用到的组件时尤为重要。

为了指定prop的验证需求,你应该为props的值指定一个带有验证需求的对象,而不是字符串数组。如下一个示例:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>validate-prop</title>
    <style>
        .font-red {
            color: #ff1944;
        }
    </style>
</head>

<body>
    <div id="app">
        <ew-div :num-success="count1" :num-fail="count2" :str-success="str1" :str-fail="str2" :str-require="str3"
            :num-or-str-success="numorstr1" :num-or-str-fail="numorstr2" define-by-self="字符串"></ew-div>
    </div>
    <script>
        Vue.component('ew-div', {
            props: {
                numSuccess: Number,//验证数值成功
                numFail: Number,//验证数值失败
                strSuccess: String,//验证字符串成功
                strFail: String,//验证字符串失败
                strRequire: {
                    type: String,
                    required: true //验证字符串必填
                },
                numOrStrSuccess: [Number, String],//验证字符串或者数值成功
                numOrStrFail: [Number, String],//验证字符串或者数值失败
                defaultNum: {
                    type: Number,
                    default: 50
                },//带有默认值的验证
                defaultObj: {
                    type: Object,
                    default: function () {
                        return { name: "eveningwater", sex: "male" }
                    } //默认值为对象,对象或数组的默认值必须通过一个函数返回
                },
                defineBySelf: {
                    validator: function (value) {
                        // 值必须是以下数组中的几个
                        return [123, '字符串', [1, 2, 3], { name: "eveningwater" }].indexOf(value) > -1;
                    },
                    default: 123
                }
            },
            template: `
                <div class="myDiv">
                    <p>数值类型验证成功:{{ numSuccess }}</p>
                    <p>需要注意的就是null和undefined会通过任何类型的验证,除了设置required为true。</p>
                    <p>
                        数值类型验证失败:{{ numFail }}
                        浏览器控制台会报如此的警告:<span class="font-red">[Vue warn]: Invalid prop: type check failed for prop "numFail". 
                        Expected Number with value 123, got String with value "123".</span>。大致意思就是:
                        不可用的prop,prop'numFail'的类型验证失败,使用数值型的123替代字符串的"123"。
                    </p>
                    <p>字符串类型验证成功:{{ strSuccess }}</p>
                    <p>
                        字符串类型验证失败:{{ strFail }}
                        浏览器控制台会报如此的警告:<span class="font-red">Invalid prop: type check failed for prop "strFail". 
                            Expected String with value "124", got Number with value 124.</span>。大致意思就是:
                        不可用的prop,prop'strFail'的类型验证失败,使用字符串的"124"替代数值的124。
                    </p>
                    <p>
                        必填字符串(包含空字符串):{{ strRequire }}
                        如果不满足条件,比如是null,则一样会抛出类似警告:
                        <span class="font-red">
                        [Vue warn]: Invalid prop: type check failed for prop "strRequire".
                         Expected String with value "null", got Null.     
                        </span>
                    </p>
                    <p>字符串或数值类型验证成功:{{ numOrStrSuccess }}</p>
                    <p>
                        字符串或数值类型验证失败:{{ numOrStrFail }}
                        浏览器控制台会报如此的警告:<span class="font-red">Invalid prop: type check failed for prop "numOrStrFail". 
                            Expected Number, String, got Object </span>。大致意思就是:
                        不可用的prop,prop'numOrStrFail'的类型验证失败,使用字符串,数值替代对象。
                    </p>
                    <p>带有默认值的验证:{{ defaultNum }}</p>
                    <p>
                        对象或数组的默认值必须以一个函数返回:
                        <span>{{ defaultObj.name }}</span><br>
                        <span>{{ defaultObj.sex }}</span>
                    </p>
                    <p>自定义验证函数:{{ defineBySelf }}如果验证失败,则控制台会提示:
                        <span class="font-red">[Vue warn]: Invalid prop: custom validator check failed for prop "defineBySelf".</span>
                    </p>
                </div>
            `
        })
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    count1: 100,
                    count2: '123',
                    str1: "成功!",
                    str2: 124,
                    str3: '123',
                    numorstr1: '字符串',
                    numorstr2: {}
                };
            }
        });
    </script>
</body>

</html>

根据以上示例,我们可以得知一个prop可以验证js当中的基本数据类型,也可以验证对象或数组,可以指定必要值以及默认值,还可以自定义验证函数。你可以狠狠点击此处具体示例查看效果。

这里需要注意一点,就是prop会在一个组件实例创建之前进行验证,因此实例中的data,computed等属性在defaultvalidator函数中是不可用的。

前面提到prop可以通过一个type属性来进行验证,在这里,type的值可以是如下几个原生构造函数:

Number,String,Boolean,Object,Array,Function,Date,Symbol

额外的,type其实也可以是一个自定义的构造函数,并且通过instanceof来检查确认。比如以下一个示例:

<!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 data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>prop type</title>
</head>

<body>
    <div id="app">
        <name-component :name="fullName"></name-component>
    </div>
    <script>
        function FullName(firstName,lastName){
            this.firstName = firstName;
            this.lastName = lastName;
            this.fullName = firstName + ' ' + lastName;
        }
        var f = new FullName('夕','水');
        var nameComponent = {
            props:{
                name:{
                    type:FullName
                }
            },
            template:`
                <div class="name">
                    <p>
                        <span>姓:</span>
                        <span>{{ name.firstName }}</span>
                    </p>
                    <p>
                        <span>名:</span>
                        <span>{{ name.lastName }}</span>
                    </p>
                    <p>
                        <span>姓名:</span>
                        <span>{{ name.fullName }}</span>
                    </p>
                </div>
            `
        }
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    fullName:f
                }
            },
            components:{
                nameComponent:nameComponent
            }
        })
    </script>
</body>

</html>

在这里,只要name是自定义构造函数FullName的实例,那么数据就验证成功,如果不是,那么vue就会在浏览器控制台给出警告。你可以狠狠点击此处具体示例查看效果。

在前面,我们都是显式的定义prop,然而,当我们不显式的定义prop的时候,实际上,它也作为了一个特性,传给了组件,只不过它是一个非prop特性,因为该组件并没有显式的定义该prop特性,显式的定义prop固然适用于传向一个组件,但是组件库的作者并不能总是预见到prop特性会应用于什么样的场景。所以如果不显式的定义prop特性,那么该特性就会被添加到组件的根元素上。如下例:

<!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 data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>not prop</title>
</head>

<body>
    <div id="app">
        <name-component :name="nameValue"></name-component>
    </div>
    <script>
        var nameComponent = {
            template:`
                <div class="name">
                   <p>组件并没有显式的定义prop,所以特性被添加到了根元素上。</p>
                </div>
            `
        }
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    nameValue:"eveningwater"
                }
            },
            components:{
                nameComponent:nameComponent
            }
        })
    </script>
</body>

</html>

在浏览器控制台,我们可以看到,name特性被添加到class为name的根元素上,如下图所示:

clipboard.png

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

在这里,对于绝大多数的特性来说,外部提供的特性都会替换组件内部的特性,例如想象一下,如果组件内部是这样一个模板:

<input type="text" v-model="newValue" class="ew-input"/>

如果我们在组件外部提供一个type="number"的特性,一旦替换掉组件内部的特性,那么就改变了原有的DOM元素,这并不是我们所想要的。来看如下一个例子:

<!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 data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>replace prop</title>
</head>

<body>
    <div id="app">
        <ew-input :type="type"></ew-input>
    </div>
    <script>
        var ewInput = {
            template:`
                <input type="text" v-model="value" class="ew-input">
            `,
            data(){
                return{
                    value:"eveningwater"
                }
            }
        }
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    type:"number"
                }
            },
            components:{
                ewInput:ewInput
            }
        })
    </script>
</body>

</html>

显然,type="number"已经替换掉了原有的type="text"属性,这并不是我们所想要的。
你可以狠狠点击此处具体示例查看效果。

庆幸的是,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 data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>replace prop</title>
</head>

<body>
    <div id="app">
        <ew-input :class="className" :style="stylePadding"></ew-input>
    </div>
    <script>
        var ewInput = {
            template:`
                <input type="text" v-model="value" class="ew-input" style="border:2px solid #2396fe;">
            `,
            data(){
                return{
                    value:"eveningwater"
                }
            }
        }
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    className:"ew-success-input",
                    stylePadding:"padding:6px 7px;"
                }
            },
            components:{
                ewInput:ewInput
            }
        })
    </script>
</body>

</html>

现在,你在控制台中查看该组件的根元素,你会发现classstyle都已经被合并了。你可以狠狠点击此处具体示例查看效果。

虽然是如此,但我们有时候并不想要这样的效果,也就是说我们想要组件的根元素不能继承特性,那么我们可以在组件的选项中设置inheritAttrs:false。格式如下:

Vue.component('my-component',{
    inheritAttrs:false,
    ......
})

当然,这个属性的设置并不会影响styleclass的绑定。我们可以将这个属性与实例的$attrs属性一起结合使用,在构造基础组件的时候非常常用,$attrs属性也是一个对象,包含传递给一个组件的特性名和特性值。例如:

{
  placeholder:"请输入你的用户名",
  id:"#myInput"
}

这个模式更允许你像写原始的HTML元素一样写一个组件,而不需要担心组件的根元素到底是哪个元素。如下例:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>inherit prop</title>
</head>

<body>
    <div id="app">
        <ew-input v-model="name" label="姓名:" placeholder="请输入你的姓名" name="eveningwater"></ew-input>
    </div>
    <script>
        var ewInput = {
            inheritAttrs:false,
            props:['label','value'],
            template:`
                <label>
                    {{ label }}
                    <input type="text" v-bind="$attrs" :value="value" class="ew-input">
                </label>
            `
        }
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                   name:""
                }
            },
            components:{
                ewInput:ewInput
            }
        })
    </script>
</body>

</html>

这样,我们就已经完成了一个基本的表单输入框组件,再加点样式扩展,我们可以完美的构造一个媲美常用的UI框架的表单输入框组件。你可以狠狠的点击此处具体示例查看效果。

3.自定义事件

事件名不同于prop和组件名的注册,是不存在大小写的转换的。也就是说事件名注册的是什么,使用的时候就应该是什么。比如如果用camelCase来定义事件名的话,那么用kebab-case的事件名是无效的。如下例:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>emit</title>
  </head>
  <body>
    <div id="app">
        <p>{{ count }}</p>
        <emit-button @add-count="count+=1"></emit-button>
    </div>
    <script>
        Vue.component('emit-button',{
            template:`<button class="btn" @click="$emit('addCount')">click me</button>`
        })
      var vm = new Vue({
        el: "#app",
        data:{
            count:0
        }
      });
    </script>
  </body>
</html>

在这个示例中,无论我们怎么点击按钮,都不会触发自定义事件,因为我们事件定义的名称是camelCase,而在使用的时候则是kebab-case,这也说明了事件名是不存在大小写的转换的。你可以狠狠点击此处具体示例查看效果。

所以,推荐用kebab-case来定义事件名。

v-model指令默认是利用一个props和一个名为input的事件来完成的,但在像单选框,复选框,下拉框等类型的控件中可能会将value特性用于其它目的。所以vue提供了model选项来避免这样的冲突,来看一个自定义v-model的示例:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>自定义v-model</title>
</head>

<body>
    <div id="app">
        <div class="form">
            <base-check v-model="gameChecked">游戏</base-check>
            <div class="form-item">
                <p>是否选中:<span>{{ gameChecked }}</span></p>
            </div>
        </div>
    </div>
    <script>
        const baseCheck = {
            model: {
                prop: 'checked',
                event: "change"
            },
            props: {
                checked: Boolean
            },
            template: `
                <div class="checkbox">
                    <input type="checkbox" :value="checked" @change="onChecked">
                    <slot></slot>
                </div>
            `,
            methods:{
                onChecked(e){
                    this.$emit('change',e.target.checked)
                }
            }
        }
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    gameChecked:false
                }
            },
            components: {
                baseCheck: baseCheck
            }
        });
    </script>
</body>

</html>

这里的gameChecked的值会被传入名为checkedprop,同时当base-checkbox被更新的时候,这个作为属性的值也会发生相应的改变。你可以狠狠点击此处具体示例查看效果。

在这里你需要注意的就是,需要显式的声明checked这个prop

在某些时候,我们是想要直接在组件上监听一个元素的原生事件的,这个时候,我们可以直接使用.native事件修饰符。比如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>native</title>
</head>

<body>
    <div id="app">
        <base-input @on-focus="count+=1"></base-input>
        <p>{{ count }}</p>
    </div>
    <script>
        const baseInput = {
            template: `
                <input type="text" @focus.natve="$emit('on-focus',$event.target.value)">
            `
        }
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    count:0
                }
            },
            methods:{

            },
            components: {
                baseInput: baseInput
            }
        });
    </script>
</body>

</html>

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

在有的时候,这样确实不错,但事实上,在有些情况下,这样做却并不是一个好主意。比如在编写一个基本的base-input组件时,这个组件的根元素不一定是input元素,这个时候监听input的元素的原生事件时,则会无效,尽管浏览器控制台并不会报错。如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>base-input</title>
</head>

<body>
    <div id="app">
        <base-input @focus.native="msg='hello'" label="姓名"></base-input>
        <p>{{ msg }}</p>
    </div>
    <script>
        const baseInput = {
            props:['label','value'],
            template: `
               <div class="base-input-container">
                    <div class="label">{{ label }}</div>
                    <input type="text" v-bind="$attrs" :value="value" @input="$emit('on-input',$event.target.value)">
               </div>
            `
        }
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    msg:"base input component"
                }
            },
            methods:{

            },
            components: {
                baseInput: baseInput
            }
        });
    </script>
</body>

</html>

显然,这个事件的监听并没有产生效果,你可以狠狠点击此处具体示例查看效果。

为了解决这个问题,vue提供了一个$listeners属性,它是一个对象,里面就包含了这个组件的所有监听器。例如:

{
    focus:function(event){/*业务逻辑代码*/},
    input:function(event){/*业务逻辑代码*/}
    ...
}

有了这个属性,就可以配合v-on:$listeners将所有的事件监听器指向组件特定的子元素。对于类似input的你希望它也可以配合v-model工作的组件来说,使用计算属性来创建这些监听器是非常有用的。如下:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>base-input</title>
</head>

<body>
    <div id="app">
        <base-input @focus="msg='hello,vue.js!'" label="姓名" v-model="msg"></base-input>
        <p>{{ msg }}</p>
    </div>
    <script>
        const baseInput = {
            inheritAttrs:false,
            props:['label','value'],
            computed:{
                input$Listeners:function(){
                    let vm = this;
                    return Object.assign({},this.$listeners,{
                        focus(e){
                            vm.$emit('focus',e.target.value)
                        },
                        input(e){
                            vm.$emit('input',e.target.value)
                        }
                    })
                }
            },
            template: `
               <div class="base-input-container">
                    <div class="label">{{ label }}</div>
                    <input type="text" v-bind="$attrs" :value="value" v-on="input$Listeners">
               </div>
            `
        }
        var vm = new Vue({
            el: "#app",
            data() {
                return {
                    msg:"base input component"
                }
            },
            methods:{
                
            },
            components: {
                baseInput: baseInput
            }
        });
    </script>
</body>

</html>

现在,base-input已经完全和一个普通的input元素一摸一样了,你可以狠狠点击此处具体示例查看效果。

在前面,我们知道props是单向数据流,只能从父组件中修改,而不能从子组件中修改,这会让vue在浏览器中给出一个警告,可是在实际场景中,我们难免不会遇到想让props成为双向绑定的存在,这会比用vuex更为方便一些。当然,很不幸的是,这种愿望是不便实现的,因为双向绑定会带来维护上的问题。虽然子组件可以修改父组件,但父组件与子组件之间并没有明显的改动来源。

当然,这也并不是说我们就不能修改子组件从而触发父组件的改变,vue2.3.0推荐v-on:update:propName的模式来触发事件并修改prop的值。如下一个示例:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>sync修饰符</title>
  </head>
  <body>
    <div id="app">
        <text-document :title="title" v-on:update:title="title = $event"></text-document>
    </div>
    <script>
        Vue.component('text-document',{
            props:['title'],
            template:`<div class="text">
                <p>{{ title }}</p>
                <button type="button" @click="$emit('update:title',$event.target.textContent)">change props</button>
            </div>`
        })
      var vm = new Vue({
        el: "#app",
        data:{
            title:'标题'
        }
      });
    </script>
  </body>
</html>

在这里,定义了一个名为titleprop,然后绑定父组件实例data选项中的title数据,传入子组件,子组件定义了一个按钮,通过点击触发update:title的事件,将按钮的文本传递给父组件,然后父组件v-on:update:title监听事件,并修改data选项中的值,这样也算是真正的让prop实现了双向绑定吧。你可以狠狠点击此处具体示例查看该示例。

v-on:update:title这样看着总有些麻烦,为了方便起见,vue将这种模式缩写为v-bind:title.sync。需要注意的就是这种模式是不能和JavaScript表达式一起使用的,例如:v-bind:title.sync=myTitle + '!'这种写法就是无效的。也就是说,我们只能为其绑定一个属性名,类似v-model指令的功能。

当我们用一个对象设置多个prop的时候,我们还可以将这种模式简写为:v-bind.sync=object,这样会把object对象的每一个属性作为一个独立的prop传进去,然后通过v-on添加各自的事件监听器。例如:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>sync修饰符</title>
  </head>
  <body>
    <div id="app">
        <text-document  v-bind.sync="articleObj"></text-document>
    </div>
    <script>
        Vue.component('text-document',{
            props:['title','content'],
            template:`<div class="text">
                <p>{{ title }}</p>
                <article>{{ content }}</article>
                <button type="button" @click="$emit('update:title',$event.target.textContent)">出师表</button>
                <button type="button" @click="$emit('update:content',$event.target.textContent)">先帝创业未半而中道崩殂。。。</button>
            </div>`
        })
      var vm = new Vue({
        el: "#app",
        data:{
            articleObj:{
                title:"标题",
                content:"内容"
            }
        }
      });
    </script>
  </body>
</html>

在这里,我们绑定了一个articleObj对象,有文章标题title,文章内容content属性,然后在组件,分别接收它们,通过增加update事件模式,我们就可以修改数据了。你可以狠狠点击此处具体示例查看效果。

不过我们也要注意一点,就是v-bind.sync不能绑定一个字面量对象,例如v-bind.sync="{ title:"标题"}"是无法正常工作的,这是vue基于很多边缘情况考虑而决定的。

4.插槽

vue自定义组件当中,如果嵌套了内容,是会被抛弃的。也就是说,组件标签之间的内容是无法被渲染的。可有时候,我们又需要在组件当中渲染嵌套的内容,就像标签里嵌套标签一样。vue提供了<slot>元素,来作为内容分发的一个API。在组件当中,<slot>元素更像是一个分发内容的出口。如下例:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-nav v-bind="nav">
            navgation url
        </base-nav>
    </div>
    <script>
        Vue.component('base-nav', {
            props: ['url', 'target'],
            template: `
                <a :href="url" :target="target">
                    <slot></slot>
                </a>
            `
        })
        var vm = new Vue({
            el: "#app",
            data: {
                nav: {
                    url: "https://www.baidu.com/",
                    target: "_blank"
                }
            }
        });
    </script>
</body>

</html>

以上定义了一个base-nav组件,我们可以看到在组件标签之间,我们传入了navgation url作为分发的内容。不止是文本,我们还可以在里面嵌套HTML标签,甚至是另一组件,这都是可以的。你可以狠狠点击此处具体示例查看效果。

我们来看嵌入标签和组件的示例:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-nav v-bind="nav">
            navgation url
        </base-nav>
        <base-nav v-bind="nav">
            <p>这是一个p标签</p>
            简单的文本内容
            <base-li></base-li>
        </base-nav>
    </div>
    <script>
        Vue.component('base-nav', {
            props: ['url', 'target'],
            template: `
                <a :href="url" :target="target">
                    <slot></slot>
                </a>
            `
        })
        const baseLi = {
            template:`<li>这是一个li标签</li>`
        }
        var vm = new Vue({
            el: "#app",
            data: {
                nav: {
                    url: "https://www.baidu.com/",
                    target: "_blank"
                }
            },
            components:{
                baseLi:baseLi
            }
        });
    </script>
</body>

</html>

我们可以看到如下图所示:

clipboard.png

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

当我们在插槽中使用数据时,我们可以使用相同作用域下的实例中的数据,但我们不能使用不同作用域的数据。我们只需要记住一条规则:父模板的所有内容都是在父级作用域中编译的,子模版的所有内容都是在子作用域编译的。来看一个示例:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-nav v-bind="nav" title="导航">
            navgation url:{{ nav.url}}{{ title }}
        </base-nav>
    </div>
    <script>
        Vue.component('base-nav', {
            props: ['url', 'target','title'],
            template: `
                <a :href="url" :target="target">
                    <slot></slot>
                </a>
            `
        })
        var vm = new Vue({
            el: "#app",
            data: {
                nav: {
                    url: "https://www.baidu.com/",
                    target: "_blank"
                }
            }
        });
    </script>
</body>

</html>

如上例所示,我们可以看到在组件的插槽中,我们也可以访问vm实例中的数据对象nav对象,可是不同作用域的title,也就是组件上的数据,是无法被访问到了,所以浏览器控制台会报一个错误,如下图所示:

clipboard.png

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

有时候,我们需要为插槽提供默认的内容,vue也叫做后备内容。比如在封装一个提交按钮组件时,我们可以提供submit或提交作为后备内容。如下图所示:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot default content</title>
    <style>
        .ew-btn{
            display: inline-block;
            transition: all .2s linear;
            outline: none;
            border: 1px solid transparent;
            text-align: center;
            padding: 6px 7px;
            line-height: 1;
            cursor: pointer;
            border-radius: 4px;
        }
        .ew-primary-btn{
            background-color: #2396ef;
            color: #ffffff;
        }
        .ew-primary-btn:hover,
        .ew-primary-btn:active{
            background-color: #23f8ff;
            color: #535353;
        }
    
    </style>
</head>

<body>
    <div id="app">
        <base-button type="primary"></base-button>
        <base-button type="primary">提交</base-button>
    </div>
    <script>
        Vue.component('base-button', {
            props:['type'],
            template: `
                <button type="button" :class="'ew-'+ type +'-btn'" class="ew-btn">
                    <slot>submit</slot>
                </button>
            `
        })
        var vm = new Vue({
            el: "#app",
            data: {
                
            }
        });
    </script>
</body>

</html>

如上例所示,当我们在组件之间并没有提供内容的时候,就会将submit作为按钮文本渲染出来,而当我们提供了内容之后,就会渲染我们所提供的内容。你可以狠狠点击此处具体示例查看效果。

有时候,我们需要用到多个插槽,比如在一个页面布局当中,我们可能会有头部,内容,底部组件,网页DOM结构大概如下所示:

<div class="page">
    <header class="base-header">
        <!-- 头部 -->
    </header>
    <main class="base-main">
      <!-- 主体内容 -->
    </main>
    <footer class="base-footer">
      <!-- 底部 -->
    </footer>
</div>

这时候,头部,主体和底部都会用到插槽,我们可以为每个插槽指定一个属性:name。如果没有显示的指定这个属性的值,那么这个值会默认的指定为default。然后,我们在父组件中使用该组件并且提供插槽内容的时候,我们可以通过v-slot的参数形式结合template元素来指定添加到哪个插槽中。比如:

定义的组件:

<div class="page">
    <header class="base-header">
        <!-- 头部 -->
        <slot name="header"></slot>
    </header>
    <main class="base-main">
      <!-- 主体内容 -->
       <slot name="main"></slot>
    </main>
    <footer class="base-footer">
      <!-- 底部 -->
       <slot name="footer"></slot>
    </footer>
</div>

假定这个布局组件的组件名为base-layout,现在我们在使用这个组件:

<base-layout>
  <template v-slot:header>
      <h1>这是头部内容</h1>
  </template>
  <template v-slot:main>
    <p>这是主题内容</p>
  </template>
  <template v-slot:footer>
    <span>这是底部的内容</span>
  </template>
</base-layout>

当然主题内容,我们也可以不必指定v-slot,这样就是默认的default。这样我们就完成了一个基本的布局页面:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-layout>
            <template v-slot:header>
                <h1>这是头部内容</h1>
            </template>
            <template v-slot:main>
                <p>这是主题内容</p>
            </template>
            <template v-slot:footer>
                <span>这是底部的内容</span>
            </template>
        </base-layout>
    </div>
    <script>
        Vue.component('base-layout', {
            template: `
                <div class="page">
                    <header class="base-header">
                        <!-- 头部 -->
                        <slot name="header"></slot>
                    </header>
                    <main class="base-main">
                      <!-- 主体内容 -->
                       <slot name="main"></slot>
                    </main>
                    <footer class="base-footer">
                      <!-- 底部 -->
                       <slot name="footer"></slot>
                    </footer>
                </div>
            `
        })
        var vm = new Vue({
            el: "#app",
            data: {

            }
        });
    </script>
</body>

</html>

你可以狠狠点击具体示例查看效果。注意这里的v-slot只能添加到一个template元素上,只有一种例外,后续会赘述。

在有的时候,我们需要将插槽内容访问子组件的数据。比如有一个base-button组件如下:

<base-button>
  <span>{{ btn.text }}</span>
</base-button>

在子组件中,我们可能会有如下结构:

<button type="button">
  <slot>{{ btn.text }}</slot>
</button>

子组件的数据应该如下:

data(){
  return{
    btn:{
      text:"submit"
    }
  }
}

我们在子组件中是可以访问到btn.text数据的,但是在父组件中,我们是访问不到的,它们是不同的作用域,这时候我们可以使用v-bind特性将数据绑定到slot元素上,这种特性也被叫做插槽prop。如下:

<button type="button">
  <slot :btn="btn"></slot>
</button>

然后在父级组件中,我们可以使用v-slot带一个值来定义插槽prop的名字,结构如:v-slot="slotProps"v-slot:default="slotProps"。如下:

<base-button v-slot="slotprops">
  <span>{{ btn.text }}</span>
</base-button>

当然我们也可以在父级组件中使用template元素,将v-slot绑定到template元素上,例如:

<base-button>
  <template v-slot="slotProps">
    <span>{{ btn.text }}</span>
  </template>
</base-button>

然后父级中绑定的数据,我们应该修改成slotProps.btn.text。这样,我们就完成了插槽之间的数据传递。完整代码如下:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-button v-slot="slotProps">
            <template >
                <span>{{ slotProps.text }}</span>
            </template>
        </base-button>
    </div>
    <script>
        Vue.component('base-button', {
            template: `
                <button type="button" class="btn">
                    <slot :text="text"></slot>
                </button>
            `,
            data(){
                return{
                    text:"submit"
                }
            }
        })
        var vm = new Vue({
            el: "#app",
            data: {

            }
        });
    </script>
</body>

</html>

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

在这个例子中,我们将包含所有插槽prop的对象命名为slotProps,当然,也可以换其它的命名,这取决于每个人自己的想法。在这里,我们需要注意的就是,当被提供的插槽只有默认插槽的时候,组件的标签才可以被当做模板使用,我们才可以将v-slot绑定到组件标签上,如下:

<base-button v-slot:default="slotProps">
    <span>{{ slotProps.text }}</span>
</base-button>

当然,这种写法是还可以更简单的,vue把不带参数的v-slot指定为默认插槽。如下:

<base-button v-slot="slotProps">
    <span>{{ slotProps.text }}</span>
</base-button>

当有多个插槽,也就是有具名插槽的时候,以上的写法是不被允许的,因为这会导致作用域不明确。如以下示例:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-layout v-slot="footer">
            <template v-slot:header>
                <h1>这是头部内容</h1>
            </template>
        </base-layout>
    </div>
    <script>
        Vue.component('base-layout', {
            template: `
                <div class="page">
                    <header class="base-header">
                        <!-- 头部 -->
                        <slot name="header"></slot>
                    </header>
                    <main class="base-main">
                      <!-- 主体内容 -->
                       <slot name="main"></slot>
                    </main>
                    <footer class="base-footer">
                      <!-- 底部 -->
                       <slot name="footer"></slot>
                    </footer>
                </div>
            `
        })
        var vm = new Vue({
            el: "#app",
            data: {

            }
        });
    </script>
</body>

</html>

vue会在浏览器控制台给出一个提示,如下图:

clipboard.png

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

只要出现多个插槽,就应该将v-slot绑定到<template>元素上。如下:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-layout>
            <template v-slot:header>
                <h1>这是头部内容</h1>
            </template>
            <template v-slot:footer>
                <h1>这是底部内容</h1>
            </template>
        </base-layout>
    </div>
    <script>
        Vue.component('base-layout', {
            template: `
                <div class="page">
                    <header class="base-header">
                        <!-- 头部 -->
                        <slot name="header"></slot>
                    </header>
                    <main class="base-main">
                      <!-- 主体内容 -->
                       <slot name="main"></slot>
                    </main>
                    <footer class="base-footer">
                      <!-- 底部 -->
                       <slot name="footer"></slot>
                    </footer>
                </div>
            `
        })
        var vm = new Vue({
            el: "#app",
            data: {

            }
        });
    </script>
</body>

</html>

这样;vue就不会在浏览器中给出警告了。你可以狠狠点击此处具体示例查看效果。

作用域插槽的工作原理其实就是将插槽内容包括在一个封装的单个参数的函数里面,就像如下所示:

function slot(slotProps){
    //插槽内容
}

这也就意味着v-slot的值实际上就是任何可以用作函数参数的JavaScript表达式,因此,在浏览器支持或者单文件组件的情况下,我们也是可以使用es2015的解构赋值来作为传入的插槽。例如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-button>
            <template v-slot="{ user }">
                <span>{{ user.firstName }}</span>
            </template>
        </base-button>
    </div>
    <script>
        Vue.component('base-button', {
            template: `
                <button type="button">
                    <slot :user="user"></slot>
                </button>
            `,
            data(){
                return{
                    user:{
                        firstName:"evening",
                        lastName:"water"
                    }
                }
            }
        })
        var vm = new Vue({
            el: "#app",
            data: {
                
            }
        });
    </script>
</body>

</html>

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

你甚至还可以将插槽prop重命名,例如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-button>
            <template v-slot="{ user:person }">
                <span>{{ person.firstName }}</span>
            </template>
        </base-button>
    </div>
    <script>
        Vue.component('base-button', {
            template: `
                <button type="button">
                    <slot :user="user"></slot>
                </button>
            `,
            data(){
                return{
                    user:{
                        firstName:"evening",
                        lastName:"water"
                    }
                }
            }
        })
        var vm = new Vue({
            el: "#app",
            data: {
                
            }
        });
    </script>
</body>

</html>

如上,我们将user重命名为person,你可以狠狠点击此处具体示例查看效果。

我们甚至还可以定义后备内容,用于插槽propundefined的情况,比如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-button>
            <template v-slot="{ user = { firstName:'evening'} }">
                <span>{{ user.firstName }}</span>
            </template>
        </base-button>
    </div>
    <script>
        Vue.component('base-button', {
            template: `
                <button type="button">
                    <slot>{{ user.lastName }}</slot>
                </button>
            `,
            data(){
                return{
                    user:{
                       lastName:"water"
                    }
                }
            }
        })
        var vm = new Vue({
            el: "#app",
            data: {
                
            }
        });
    </script>
</body>

</html>

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

我们也可以将具名插槽进行缩写,也就是把v-slot:缩写为#。例如,将v-slot:footer缩写为#footer,值得注意的就是这种写法是不被允许的,#={user}。我们必须要指定插槽名,哪怕是默认插槽也一样。如#default={user}。如下例:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-layout>
            <template #default>
                <span>这是默认插槽的内容</span>
            </template>
            <template #header>
                <h1>这是头部内容</h1>
            </template>
            <template #main>
                <p>这是主题内容</p>
            </template>
            <template #footer>
                <span>这是底部的内容</span>
            </template>
        </base-layout>
    </div>
    <script>
        Vue.component('base-layout', {
            template: `
                <div class="page">
                    <slot></slot>
                    <header class="base-header">
                        <!-- 头部 -->
                        <slot name="header"></slot>
                    </header>
                    <main class="base-main">
                      <!-- 主体内容 -->
                       <slot name="main"></slot>
                    </main>
                    <footer class="base-footer">
                      <!-- 底部 -->
                       <slot name="footer"></slot>
                    </footer>
                </div>
            `
        })
        var vm = new Vue({
            el: "#app",
            data: {

            }
        });
    </script>
</body>

</html>

如果我们将#default中的default去掉,那么vue会在浏览器中给出一个警告,如下图所示:

clipboard.png

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

我们也可以绑定动态插槽,如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>slot</title>
</head>

<body>
    <div id="app">
        <base-layout>
            <template #default>
                <span>这是默认插槽的内容</span>
            </template>
            <template v-slot:[slotname]>
                <h1>这是头部内容</h1>
            </template>
            <template #main>
                <p>这是主题内容</p>
            </template>
            <template #footer>
                <span>这是底部的内容</span>
            </template>
        </base-layout>
    </div>
    <script>
        Vue.component('base-layout', {
            template: `
                <div class="page">
                    <slot></slot>
                    <header class="base-header">
                        <!-- 头部 -->
                        <slot name="header"></slot>
                    </header>
                    <main class="base-main">
                      <!-- 主体内容 -->
                       <slot name="main"></slot>
                    </main>
                    <footer class="base-footer">
                      <!-- 底部 -->
                       <slot name="footer"></slot>
                    </footer>
                </div>
            `
        })
        var vm = new Vue({
            el: "#app",
            data: {
                slotname:'header'
            }
        });
    </script>
</body>

</html>

动态插槽就是以v-slot:[slotName]的形式,但在这里需要注意一点,那就是动态插槽的插槽名不能包含大写字母,否则,vue会在浏览器中给出一个警告,如下图所示:

clipboard.png

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

关于插槽的基础就只有这些,但要深入的了解可以浏览这些诸如Vue Virtual Scroller,Vue Promised,Portal Vue 等库。

5.动态组件与异步组件

在前面,我们曾完成这样利用is特性的组件切换示例,is绑定组件名is绑定组件选项对象。现在我们来尝试修改一下示例的代码,如下:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!-- 引入vue.js开发版本 -->
    <script data-original="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>keep-alive</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .tab-component-demo {
            width: 900px;
            margin: 15px auto;
            padding: 15px 10px;
        }

        button {
            outline: none;
            border: 1px solid #ebebeb;
            background-color: transparent;
            display: inline-block;
            padding: 6px 7px;
            text-align: center;
            line-height: 1;
            transition: all 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
            cursor: pointer;
            font-size: 16px;
            border-radius: 4px;
        }

        button:hover,
        button:active {
            border-color: #58e9fc;
            background-color: #0abce9;
            color: #ffffff;
        }

        .ew-page {
            border: 1px solid #999;
            padding: 15px 8px;
        }
    </style>
</head>

<body>
    <div id="app">
        <div class="tab-component-demo">
            <button v-for="(tab,index) in tabs" :key="index" @click="currentTab = tab">
                {{ tab }}
            </button>
            <component :is="currentTabComponent"></component>
        </div>
    </div>
    <script>
        const childComponents = [
            {
                name: "tab-header",
                component: {
                    template: `<div class='home-content'>the header of home page!</div>`
                }
            },
            {
                name: "tab-content",
                component: {
                    template: `<div class='home-content'>the content of home page!</div>`
                }
            },
            {
                name: "tab-footer",
                component: {
                    template: `<div class='home-content'>the footer of home page!</div>`
                }
            }
        ];
        Vue.component("tab-home", {
            template: `
                <div class="ew-page">
                  <button
                      v-for="(childTab,index) in childTabs"
                      :key="index"
                      @click="currentChildTab = childTab"
                      >
                      {{ childTab.name.slice(4) }}
                   </button>
                   <component :is="currentChildTab.component"></component>
                </div>
            `,
            data() {
                return {
                    childTabs: childComponents,
                    currentChildTab: childComponents[0]
                }
            }
        });
        Vue.component("tab-list", {
            template: `
                <div class="ew-page">
                 list page!
                </div>
            `
        });
        Vue.component("tab-contact", {
            template: `
                <div class="ew-page">
                contact page!
                </div>
            `
        });
        var vm = new Vue({
            data: {
                currentTab: "home",
                tabs: ["home", "list", "contact"]
            },
            computed: {
                currentTabComponent: function () {
                    return "tab-" + this.currentTab.toLowerCase();
                }
            }
        }).$mount(document.getElementById("app"));
    </script>
</body>

</html>

你会注意到,当我们第一个导航(即home)所展示的页面中又继续包含3个子导航,我们可以再继续切换。但是当我们切换了子导航之后,再去切换父导航,最后切换回到父导航home,我们会发现之前切换过的子导航会回到初始状态(即header),这是因为每次切换,vue都新创建了一个组件实例。你可以狠狠点击此处具体实例查看效果。

尽管重新创建动态组件是非常有用的,但在这个例子中,我们可能更希望组件在第一次被创建的时候就缓存下来。为了解决这个问题,vue提供了<keep-alive>组件。我们只需要用这个组件包裹动态组件即可,如下:

  <keep-alive>
    <component :is="currentTabComponent"></component>
  </keep-alive>

现在我们再来测试一下这个修改后的示例具体示例。我们可以看到,现在组件已经保持了它的状态。

注意一点,<keep-alive>组件要求被切换的组件都有自己的名字,不论是通过组件的name选项还是局部或者全局注册。

你可以在keep-alive API文档中查看更多细节内容。

查看原文

赞 5 收藏 2 评论 0

该账号已被查封 赞了回答 · 7月10日

解决javascript如何获取当前脚本资源的地址

@airyland 的回复如果有js是异步加载的话会有问题,其实可以尝试使用下面的代码

function getCurrentScript() {
    if (document.currentScript) {
        return document.currentScript;
    }

    // For IE6-9 browsers, the script onload event may not fire right
    // after the script is evaluated. Kris Zyp found that it
    // could query the script nodes and the one that is in "interactive"
    // mode indicates the current script
    // ref: http://goo.gl/JHfFW
    var scripts = document.getElementsByTagName("script")

    for (var i = scripts.length - 1; i >= 0; i--) {
        var script = scripts[i]
        if (script.readyState === "interactive") {
            interactiveScript = script
            return interactiveScript
        }
    }
}

示例:http://binbinliao.com/demo/curjs/index.html

关注 1 回答 4

该账号已被查封 收藏了文章 · 7月3日

使用 ES6 的浏览器兼容性问题

以前对浏览器兼容性问题只是大概知道一些点,没想到这次真正着手去做的时候,还是碰到了很多问题。刚开始的时候一边解决问题,一边想着:用 IE8 的都是神经病,到后来,我发现完了,I LOVE IE。

0x00 起源

在这次做小蜜 PC 版的时候,由于早于 PC 版,无线版已经重新设计了全新版,做了很多架构上的优化调整。所以在做的时候把无线版的前端架构拿了过来,主要的考虑就是品牌和功能保持跟无线版统一的同时,技术上也可相互支持以及组件复用。

无线版技术上主要采用 ES6 + Webpack + Babel 的方式,由于项目的独特性和特殊需求,并没有使用任何框架,只引入 zepto 作为一个标准支撑库。

而 PC 版的架构跟无线版基本保持一致,主要是把 zepto 换成了 jQuery。

下面是一些基本的开发依赖:

{
  "devDependencies": {
    "babel-core": "~6.3.15",
    "babel-loader": "~6.2.0",
    "babel-preset-es2015": "~6.3.13",
    "babel-preset-stage-0": "~6.3.13",
    "babel-runtime": "~6.3.13",
    "extract-text-webpack-plugin": "~0.9.1",
    "less-loader": "~2.2.1",
    "nunjucks-loader": "~1.0.7",
    "style-loader": "~0.10.2",
    "webpack": "~1.12.9",
    "webpack-dev-server": "^1.10.1"
  }
}

0x01 polyfill

由于 Babel 默认只转换转各种 ES2015 语法,而不转换新的 API,比如 Promise,以及 Object.assign、Array.from 这些新方法,这时我们需要提供一些 ployfill 来模拟出这样一个提供原生支持功能的浏览器环境。

主要有两种方式:babel-runtimebabel-polyfill

babel-runtime

babel-runtime 的作用是模拟 ES2015 环境,包含各种分散的 polyfill 模块,我们可以在自己的模块里单独引入,比如 promise:

import 'babel-runtime/core-js/promise'

它们不会在全局环境添加未实现的方法,只是这样手动引用每个 polyfill 会非常低效,我们可以借助 Runtime transform 插件来自动化处理这一切。

首先使用 npm 安装:

npm install babel-plugin-transform-runtime --save-dev

然后在 webpack 配置文件的 babel-loader 增加选项:

loader: ["babel-loader"],
query: {
  plugins: [
    "transform-runtime"
  ],
  presets: ['es2015', 'stage-0']
}

babel-polyfill

babel-polyfill 是针对全局环境的,引入它浏览器就好像具备了规范里定义的完整的特性,一旦引入,就会跑一个 babel-polyfill 实例。用法如下:

1.安装 babel-polyfill

npm install babel-polyfill --save

2.在入口文件中引用:

import 'babel-polyfill'

小结

其实做到这些,在大部分浏览器就可以正常跑了,但我们做的是一个用户环境很不确定的产品,对一些年代久远但又不容忽视的运行环境,比如 IE8,我们做的还不够。

接下来将开始讲述我们在兼容性方面遇到的一些问题,和解决方法。

0x02 开始在 IE8 运行

最开始做的时候并没有针对 IE 做一些兼容性方面的处理,结果在 IE8 上一跑一堆问题。

第一步,我们把 jQuery 换成 1.12.1 ,因为 2.X 已经不再支持 IE8。

但并没有像我们想象中的那样,只是简单换一下 jQuery 版本就可以正常运行了。

0x03 default or catch

这是遇到的第一个问题。在兼容性测试过程中,对下面的代码:

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

或者这种:

module.exports = _main2.default;

在 IE8 下会直接报”缺少标识符、字符串或数字”的错。

我们得在对象的属性上加 '' 才可以。就像下面这样:

function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : { 'default': obj };
}

module.exports = _main2['default'];

至于原因,并不是 IE8 下对象的属性必须得加 '' 才行,而是 default 的问题,作为一个关键字,同样的问题还包括 catch

这两种情况,可以通过使用 transform-es3-property-literalstransform-es3-member-expression-literals 这两个插件搞定。

总之,在平时写代码的时候避免使用关键字,或者保留字作为对象的属性值,尤其是在习惯不加引号的情况下。相关讨论:Allow reserved words for properties

0x04 es5-shim、es5-sham

为了兼容像 IE8 这样的老版本浏览器,我们引入 es5-shim 作为 polyfill。

但在遇到 Object.defineProperty 仍提示 "对象不支持此操作"

As currently implemented, the Object.defineProperty shim will not install on IE8 because IE8 already has such a method. However, the built-in IE8 method only works when applied to DOM objects.

其实 es5-shim 明确说明,这个方法的 polyfill 在 IE8 会失败,因为 IE8 已经有个同名的方法,但只是用于 DOM 对象。

同样的问题还包括 Object.create,上述问题可以再引入 es5-sham 解决.

0x05 addEventListener

项目中有部分代码直接使用 addEventListener 这个 API,但在 IE8 下的事件绑定并不是这个方法。

这个问题很容易解决,也无需去写额外的 polyfill。我们已经把 jQuery 换成 1.x,所以只需把代码中 addEventListener 换成 jQuery 的写法就 Okay 了。

jQuery 其实为我们封装了很多 API,并做了很多兼容性的封装,类似的只要使用封装好的就可以了。

0x06 无法获取未定义或 null 引用的属性

这个问题是在特定场景下【转人工】出现的,出现问题的不是 IE8,而是 IE9 和 IE10。

原因是 ocs 实例创建失败,因为没有调用父类的构造函数。

通过安装 transform-es2015-classestransform-proto-to-assign 解决。

在配置项加上这两个插件的配置:

{
  "plugins": [
      ["transform-es2015-classes", { "loose": true }],
      "transform-proto-to-assign"

  ]
}

0x07 postMessage

虽然 postMessage 是 HTML5 的特性,但 IE8 和 Firefox3 很早就实现了这个 API,当然,跟后来的标准并不一致。这其实也不能怪 IE8。

The postMessage method is supported in Internet Explorer from version 8, Firefox from version 3 and Opera from version 9.5.

我们可能会这样去使用:

parent.postMessage({success: 'ok', name: ‘mirreal’}, ‘*’);

但是为了兼容 IE8,我们得转成字符串:

parent.postMessage(JSON.stringify({success: 'ok', name: "mirreal"}), ‘*’);

另外一个需要注意的点是:在 IE8 下 window.postMessage 是同步的。

window.postMessage is syncronouse in IE 8

var syncronouse = true;
window.onmessage = function () {
  console.log(syncronouse); // 在 IE8 下会在控制台打印 true
};
window.postMessage('test', '*');
syncronouse = false;

0x08 IE8/IE9 的控制台

遇到一个奇怪的问题,在刚开始遇到的时候(其实搞清楚原因,好像也挺正常的),小蜜在 IE8 IE9 无法加载。在 IE8 那个古老浏览器的左下角,好像也是唯一会在页面提示脚本错误的浏览器,提示 script error

第一反应就是应该又是某个函数在 IE 下不支持,准备打开控制台看看到底哪里报错,结果却什么事都没有了,页面竟然顺畅地加载出来了,这下该怎么调试好呢?

开始思考:什么东西是依赖控制台而存在的,到底会是什么呢。。。其实就是控制台本身。

原因就是我们在代码中添加了一些控制信息会打印在控制台,而 IE8/IE9 要开启 IE Dev Tools 才能使用 console 对象。

切忌把 IE8/9 想成 Chrome/Firefox,以为永远有 window.console 可用.终于,IE10 改邪归正,console 不再像段誉的六脉神剑时有时无。

console.log is there in IE8, but the console object isn't created until you open DevTools. Therefore, a call to console.log may result in an error, for example if it occurs on page load before you have a chance to open the dev tools.

但只要 IE8/9 还在一天,console 检查还是不能少的

事实上,IE8/9 从未死去,所以

就像这样:


if (window.console) {
  console.log('log here');
}

要是有一堆 console.log, console.count, console.error, console.time, console.profile,... 这样去写,那还不把人写到恶心死。

写个简单的 console polyfill 吧,检测是否存在 console,不存在可以常见一个同名的空方法达到不报错的目的。当然,生产环境的代码其实也不会有那么多奇奇怪怪的 console

0x09 定义文档兼容性

X-UA-Compatible 当初是针对 IE8 新加的一个配置。用于为 IE8 指定不同的页面渲染模式,比如使用 IE7 兼容模式,或者是采用最新的引擎。

现在基本也不需要前者的降级模式,更多的是写入 IE=edge 支持最新特性。而 chrome=1 则会激活 Google Chrome Frame,前提是你的 IE 安装过这个插件。

有什么用呢,当然有用,有些 API 是作为新特性存在于 IE8 中的,比如 JSON,不开启的话就用不了。

为什么要用 X-UA-Compatible?

在 IE8 刚推出的时候,很多网页由于重构的问题,无法适应较高级的浏览器,所以使用 X-UA-Compatible 强制 IE8 采用低版本方式渲染。

比如:使用下面这段代码后,开发者无需考虑网页是否兼容 IE8 浏览器,只要确保网页在 IE6、IE7 下的表现就可以了。

<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />

而这段代码:

<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />

IE=edge 告诉 IE 使用最新的引擎渲染网页,chrome=1 则可以激活 Chrome Frame[1]。

0x0a 条件注释 or 条件编译

最后说说 IE 的条件注释,用法如下:

!    [if !IE]    The NOT operator. This is placed immediately in front of the feature, operator, or subexpression to reverse the Boolean meaning of the expression.

lt    [if lt IE 5.5]    The less-than operator. Returns true if the first argument is less than the second argument.

lte    [if lte IE 6]    The less-than or equal operator. Returns true if the first argument is less than or equal to the second argument.

gt    [if gt IE 5]    The greater-than operator. Returns true if the first argument is greater than the second argument.

gte    [if gte IE 7]    The greater-than or equal operator. Returns true if the first argument is greater than or equal to the second argument.

( )    [if !(IE 7)]    Subexpression operators. Used in conjunction with boolean operators to create more complex expressions.

&    [if (gt IE 5)&(lt IE 7)]    The AND operator. Returns true if all subexpressions evaluate to true

|    [if (IE 6)|(IE 7)]    The OR operator. Returns true if any of the subexpressions evaluates to true.

另外一个类似的东西是在 Javascript 中的条件编译(conditional compilation)。我们可以使用这段简单的代码来做浏览器嗅探:

var isIE = /*@cc_on!@*/false

在其他浏览器中,false 前的被视为注释,而在 IE 中,/*@cc_on .... @*/ 之间的部分可以被 IE 识别并作为程序执行,同时启用 IE 的条件编译。

常用变量如下:

* @_win32 如果在 Win32 系统上运行,则为 true。
* @_win16 如果在 Win16 系统上运行,则为 true。
* @_mac 如果在 Apple Macintosh 系统上运行,则为 true。
* @_alpha 如果在 DEC Alpha 处理器上运行,则为 true。
* @_x86 如果在 Intel 处理器上运行,则为 true。
* @_mc680x0 如果在 Motorola 680x0 处理器上运行,则为 true。
* @_PowerPC 如果在 Motorola PowerPC 处理器上运行,则为 true。
* @_jscript 始终为 true。
* @_jscript_build 包含 JavaScript 脚本引擎的生成号。
* @_jscript_version 包含 major.minor 格式的 JavaScript 版本号。

Internet Explorer 11 之前的所有版本的 Internet Explorer 都支持条件编译。 从 Internet Explorer 11 标准模式开始,Windows 8.x 应用商店应用不支持条件编译。

之前一直在做移动端的开发,没想到做 PC 端也会遇到这么多的兼容性问题。不同于移动端设备的繁杂和不确定性,PC 版的兼容更侧重于对特定浏览器的特性的了解,相比而言更为明确,而非因为某一款手机的诡异表现。

参考文档

查看原文

该账号已被查封 赞了文章 · 7月3日

使用 ES6 的浏览器兼容性问题

以前对浏览器兼容性问题只是大概知道一些点,没想到这次真正着手去做的时候,还是碰到了很多问题。刚开始的时候一边解决问题,一边想着:用 IE8 的都是神经病,到后来,我发现完了,I LOVE IE。

0x00 起源

在这次做小蜜 PC 版的时候,由于早于 PC 版,无线版已经重新设计了全新版,做了很多架构上的优化调整。所以在做的时候把无线版的前端架构拿了过来,主要的考虑就是品牌和功能保持跟无线版统一的同时,技术上也可相互支持以及组件复用。

无线版技术上主要采用 ES6 + Webpack + Babel 的方式,由于项目的独特性和特殊需求,并没有使用任何框架,只引入 zepto 作为一个标准支撑库。

而 PC 版的架构跟无线版基本保持一致,主要是把 zepto 换成了 jQuery。

下面是一些基本的开发依赖:

{
  "devDependencies": {
    "babel-core": "~6.3.15",
    "babel-loader": "~6.2.0",
    "babel-preset-es2015": "~6.3.13",
    "babel-preset-stage-0": "~6.3.13",
    "babel-runtime": "~6.3.13",
    "extract-text-webpack-plugin": "~0.9.1",
    "less-loader": "~2.2.1",
    "nunjucks-loader": "~1.0.7",
    "style-loader": "~0.10.2",
    "webpack": "~1.12.9",
    "webpack-dev-server": "^1.10.1"
  }
}

0x01 polyfill

由于 Babel 默认只转换转各种 ES2015 语法,而不转换新的 API,比如 Promise,以及 Object.assign、Array.from 这些新方法,这时我们需要提供一些 ployfill 来模拟出这样一个提供原生支持功能的浏览器环境。

主要有两种方式:babel-runtimebabel-polyfill

babel-runtime

babel-runtime 的作用是模拟 ES2015 环境,包含各种分散的 polyfill 模块,我们可以在自己的模块里单独引入,比如 promise:

import 'babel-runtime/core-js/promise'

它们不会在全局环境添加未实现的方法,只是这样手动引用每个 polyfill 会非常低效,我们可以借助 Runtime transform 插件来自动化处理这一切。

首先使用 npm 安装:

npm install babel-plugin-transform-runtime --save-dev

然后在 webpack 配置文件的 babel-loader 增加选项:

loader: ["babel-loader"],
query: {
  plugins: [
    "transform-runtime"
  ],
  presets: ['es2015', 'stage-0']
}

babel-polyfill

babel-polyfill 是针对全局环境的,引入它浏览器就好像具备了规范里定义的完整的特性,一旦引入,就会跑一个 babel-polyfill 实例。用法如下:

1.安装 babel-polyfill

npm install babel-polyfill --save

2.在入口文件中引用:

import 'babel-polyfill'

小结

其实做到这些,在大部分浏览器就可以正常跑了,但我们做的是一个用户环境很不确定的产品,对一些年代久远但又不容忽视的运行环境,比如 IE8,我们做的还不够。

接下来将开始讲述我们在兼容性方面遇到的一些问题,和解决方法。

0x02 开始在 IE8 运行

最开始做的时候并没有针对 IE 做一些兼容性方面的处理,结果在 IE8 上一跑一堆问题。

第一步,我们把 jQuery 换成 1.12.1 ,因为 2.X 已经不再支持 IE8。

但并没有像我们想象中的那样,只是简单换一下 jQuery 版本就可以正常运行了。

0x03 default or catch

这是遇到的第一个问题。在兼容性测试过程中,对下面的代码:

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

或者这种:

module.exports = _main2.default;

在 IE8 下会直接报”缺少标识符、字符串或数字”的错。

我们得在对象的属性上加 '' 才可以。就像下面这样:

function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : { 'default': obj };
}

module.exports = _main2['default'];

至于原因,并不是 IE8 下对象的属性必须得加 '' 才行,而是 default 的问题,作为一个关键字,同样的问题还包括 catch

这两种情况,可以通过使用 transform-es3-property-literalstransform-es3-member-expression-literals 这两个插件搞定。

总之,在平时写代码的时候避免使用关键字,或者保留字作为对象的属性值,尤其是在习惯不加引号的情况下。相关讨论:Allow reserved words for properties

0x04 es5-shim、es5-sham

为了兼容像 IE8 这样的老版本浏览器,我们引入 es5-shim 作为 polyfill。

但在遇到 Object.defineProperty 仍提示 "对象不支持此操作"

As currently implemented, the Object.defineProperty shim will not install on IE8 because IE8 already has such a method. However, the built-in IE8 method only works when applied to DOM objects.

其实 es5-shim 明确说明,这个方法的 polyfill 在 IE8 会失败,因为 IE8 已经有个同名的方法,但只是用于 DOM 对象。

同样的问题还包括 Object.create,上述问题可以再引入 es5-sham 解决.

0x05 addEventListener

项目中有部分代码直接使用 addEventListener 这个 API,但在 IE8 下的事件绑定并不是这个方法。

这个问题很容易解决,也无需去写额外的 polyfill。我们已经把 jQuery 换成 1.x,所以只需把代码中 addEventListener 换成 jQuery 的写法就 Okay 了。

jQuery 其实为我们封装了很多 API,并做了很多兼容性的封装,类似的只要使用封装好的就可以了。

0x06 无法获取未定义或 null 引用的属性

这个问题是在特定场景下【转人工】出现的,出现问题的不是 IE8,而是 IE9 和 IE10。

原因是 ocs 实例创建失败,因为没有调用父类的构造函数。

通过安装 transform-es2015-classestransform-proto-to-assign 解决。

在配置项加上这两个插件的配置:

{
  "plugins": [
      ["transform-es2015-classes", { "loose": true }],
      "transform-proto-to-assign"

  ]
}

0x07 postMessage

虽然 postMessage 是 HTML5 的特性,但 IE8 和 Firefox3 很早就实现了这个 API,当然,跟后来的标准并不一致。这其实也不能怪 IE8。

The postMessage method is supported in Internet Explorer from version 8, Firefox from version 3 and Opera from version 9.5.

我们可能会这样去使用:

parent.postMessage({success: 'ok', name: ‘mirreal’}, ‘*’);

但是为了兼容 IE8,我们得转成字符串:

parent.postMessage(JSON.stringify({success: 'ok', name: "mirreal"}), ‘*’);

另外一个需要注意的点是:在 IE8 下 window.postMessage 是同步的。

window.postMessage is syncronouse in IE 8

var syncronouse = true;
window.onmessage = function () {
  console.log(syncronouse); // 在 IE8 下会在控制台打印 true
};
window.postMessage('test', '*');
syncronouse = false;

0x08 IE8/IE9 的控制台

遇到一个奇怪的问题,在刚开始遇到的时候(其实搞清楚原因,好像也挺正常的),小蜜在 IE8 IE9 无法加载。在 IE8 那个古老浏览器的左下角,好像也是唯一会在页面提示脚本错误的浏览器,提示 script error

第一反应就是应该又是某个函数在 IE 下不支持,准备打开控制台看看到底哪里报错,结果却什么事都没有了,页面竟然顺畅地加载出来了,这下该怎么调试好呢?

开始思考:什么东西是依赖控制台而存在的,到底会是什么呢。。。其实就是控制台本身。

原因就是我们在代码中添加了一些控制信息会打印在控制台,而 IE8/IE9 要开启 IE Dev Tools 才能使用 console 对象。

切忌把 IE8/9 想成 Chrome/Firefox,以为永远有 window.console 可用.终于,IE10 改邪归正,console 不再像段誉的六脉神剑时有时无。

console.log is there in IE8, but the console object isn't created until you open DevTools. Therefore, a call to console.log may result in an error, for example if it occurs on page load before you have a chance to open the dev tools.

但只要 IE8/9 还在一天,console 检查还是不能少的

事实上,IE8/9 从未死去,所以

就像这样:


if (window.console) {
  console.log('log here');
}

要是有一堆 console.log, console.count, console.error, console.time, console.profile,... 这样去写,那还不把人写到恶心死。

写个简单的 console polyfill 吧,检测是否存在 console,不存在可以常见一个同名的空方法达到不报错的目的。当然,生产环境的代码其实也不会有那么多奇奇怪怪的 console

0x09 定义文档兼容性

X-UA-Compatible 当初是针对 IE8 新加的一个配置。用于为 IE8 指定不同的页面渲染模式,比如使用 IE7 兼容模式,或者是采用最新的引擎。

现在基本也不需要前者的降级模式,更多的是写入 IE=edge 支持最新特性。而 chrome=1 则会激活 Google Chrome Frame,前提是你的 IE 安装过这个插件。

有什么用呢,当然有用,有些 API 是作为新特性存在于 IE8 中的,比如 JSON,不开启的话就用不了。

为什么要用 X-UA-Compatible?

在 IE8 刚推出的时候,很多网页由于重构的问题,无法适应较高级的浏览器,所以使用 X-UA-Compatible 强制 IE8 采用低版本方式渲染。

比如:使用下面这段代码后,开发者无需考虑网页是否兼容 IE8 浏览器,只要确保网页在 IE6、IE7 下的表现就可以了。

<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />

而这段代码:

<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />

IE=edge 告诉 IE 使用最新的引擎渲染网页,chrome=1 则可以激活 Chrome Frame[1]。

0x0a 条件注释 or 条件编译

最后说说 IE 的条件注释,用法如下:

!    [if !IE]    The NOT operator. This is placed immediately in front of the feature, operator, or subexpression to reverse the Boolean meaning of the expression.

lt    [if lt IE 5.5]    The less-than operator. Returns true if the first argument is less than the second argument.

lte    [if lte IE 6]    The less-than or equal operator. Returns true if the first argument is less than or equal to the second argument.

gt    [if gt IE 5]    The greater-than operator. Returns true if the first argument is greater than the second argument.

gte    [if gte IE 7]    The greater-than or equal operator. Returns true if the first argument is greater than or equal to the second argument.

( )    [if !(IE 7)]    Subexpression operators. Used in conjunction with boolean operators to create more complex expressions.

&    [if (gt IE 5)&(lt IE 7)]    The AND operator. Returns true if all subexpressions evaluate to true

|    [if (IE 6)|(IE 7)]    The OR operator. Returns true if any of the subexpressions evaluates to true.

另外一个类似的东西是在 Javascript 中的条件编译(conditional compilation)。我们可以使用这段简单的代码来做浏览器嗅探:

var isIE = /*@cc_on!@*/false

在其他浏览器中,false 前的被视为注释,而在 IE 中,/*@cc_on .... @*/ 之间的部分可以被 IE 识别并作为程序执行,同时启用 IE 的条件编译。

常用变量如下:

* @_win32 如果在 Win32 系统上运行,则为 true。
* @_win16 如果在 Win16 系统上运行,则为 true。
* @_mac 如果在 Apple Macintosh 系统上运行,则为 true。
* @_alpha 如果在 DEC Alpha 处理器上运行,则为 true。
* @_x86 如果在 Intel 处理器上运行,则为 true。
* @_mc680x0 如果在 Motorola 680x0 处理器上运行,则为 true。
* @_PowerPC 如果在 Motorola PowerPC 处理器上运行,则为 true。
* @_jscript 始终为 true。
* @_jscript_build 包含 JavaScript 脚本引擎的生成号。
* @_jscript_version 包含 major.minor 格式的 JavaScript 版本号。

Internet Explorer 11 之前的所有版本的 Internet Explorer 都支持条件编译。 从 Internet Explorer 11 标准模式开始,Windows 8.x 应用商店应用不支持条件编译。

之前一直在做移动端的开发,没想到做 PC 端也会遇到这么多的兼容性问题。不同于移动端设备的繁杂和不确定性,PC 版的兼容更侧重于对特定浏览器的特性的了解,相比而言更为明确,而非因为某一款手机的诡异表现。

参考文档

查看原文

赞 45 收藏 142 评论 6

该账号已被查封 赞了文章 · 5月28日

window.open 打开新窗口被拦截的解决方案

最近公司开发的一个项目,平凡用到下载各种类型的文件,但是例如.txt,.jpg,.pdf格式的文件呢浏览器会在当前窗口直接打开,影响用户体验,尝试各种方案和百度总结一下几点;


原理:

当window.open为用户触发事件内部或者加载时,不会被拦截,一旦将弹出代码移动到ajax或者一段异步代码内部,马上就出现被拦截的表现了(浏览器认为这可能是一个广告,不是一个用户希望看到的页面)

常用办法页面打开方式

  1. 超链接<a href="https://www.baidu.com" title="">Welcome</a>

    等效于js代码

    window.location.href="https://www.baidu.com"; //在同当前窗口中打开窗口

  2. 超链接<a href="https://www.baidu.com/" title=""target="_blank">Welcome</a>

    等效于js代码

    window.open("https://www.baidu.com/"); //在另外新建窗口中打开窗口

  3. 关闭新窗口:this.window.opener =null; window.close();

解决方案:

  • 使用a标签替代:

给出如下函数,将此函数绑定到click的事件回调中,就可以避免大部分浏览器对窗口弹出 的拦截:

function newWin(url, id) {  
    var a = document.createElement(‘a‘);  
    a.setAttribute(‘href‘, url);  
    a.setAttribute(‘target‘, ‘_blank‘);  
    a.setAttribute(‘id‘, id);  
    // 防止反复添加  
    if(!document.getElementById(id)) {                       
        document.body.appendChild(a);  
    }  
     a.click();  
}  

function openUrl(url) {
    var a = $('<a href="'+url+'" target="_blank"></a>')[0];
    var e = document.createEvent('MouseEvents');
    e.initEvent('click', true, true);
    a.dispatchEvent(e);
}

//调用方法newWin(url,'bbb') / openUrl(url)
//原理都是通过创建一个a标签对象,通过里面自带的target执行跳转

  • 在超链接里加入onclick事件,如:

//这样用户点击这个超链接,浏览器会认为它是打开一个新的链接,所以就不会拦 截。

<a href="javascript:void(0)" onclick="window.open()"></a>
  • 使用 setTimeout 包装一下,也可以防止被浏览器拦截。

//注意这里的超时时间不能太短,否则也会被拦截。

setTimeout('window.open(url);', 500);
  • 我们会遇到想要弹出一个窗口,可是却是在onckick事件执行后,才去弹出来的,这时就会被浏览器拦截,我们可以通过下面的方法来避免

//先用window.open打开一个窗口,然后修改地址。如:

var tempwindow=window.open('_blank');


呵呵哒,你以为这样就完事了?大错特错了,以上办法也就是在已声明url下有效,如果异步ajax请求获取下载路径呢?

解决1:

click: () => {
    var tempwindow=window.open();//先打开临时窗体,由于是点击事件内触发,不会被拦截 
    this.$http.get(url+id,
    {emulateJSON: true}
    ).then(response => {
        let resd = response.data;
        if(resd.code==0){
             tempwindow.location.href = resd.result//当回调的时候更改临时窗体的路径
        }
        else{
            tempwindow.close()//回调发现无需打开窗体时可以关闭之前的临时窗体
            this.$Message.error(resd.message)
        }
   }, response => {
        tempwindow.close()//回调发现无需打开窗体时可以关闭之前的临时窗体
        console.log('error:', response) //for debug
    });
}

解决2:

click: () => {
    var flag = false;   
    $.ajax({   
        'url': url+id,   
        'type': 'post',   
        'dataType': 'json',   
        'data': data,   
        'async':false,//同步请求   
        success: function (data) {   
           $("#a").attr("href","www.baidu.com");//当回调的时候更改页面上或创建的某个a标签的href   
           flag = true;//更改标志   
        },   
        error:function(){   
          
        }   
   });   
   if(flag){   
       $("#a")[0].click();//href属性更改后模拟点击   
   }  
}
查看原文

赞 6 收藏 6 评论 0

该账号已被查封 赞了文章 · 5月25日

karma入门学习整理

karma介绍

Karma是Testacular的新名字,在2012年google开源了Testacular,2013年Testacular改名为Karma。
Karma是一个基于Node.js的JavaScript测试执行过程管理工具(Test Runner)。该工具可用于测试所有主流Web浏览器,也可集成到CI(Continuous integration)工具,也可和其他代码编辑器一起使用。这个测试工具的一个强大特性就是,它可以监控(Watch)文件的变化,然后自行执行,通过console.log显示测试结果。

安装karma

Karma依赖NodeJs和NPM包管理工具,安装前首先要确认存在node和npm(安装这里就不介绍了)

首先安装karma的cli工具karma-cli,有了cli工具才可以在全局执行karma命令

npm install karma-cli -g        // 安装karma的cli工具

新建一个目录来执行整个过程

$ mkdir karma-test

$ cd karma-test

生成package.json

$ npm init -y

安装karma

$ npm install --save-dev karma

初始化karma

$ karma init
// 选择测试框架,这里我选择jasmine
Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> jasmine

// 是否引入Require.js,不需要
Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no 

// 选择使用的浏览器,可以使用无头浏览器PhantomJS,不过需要单独安装PhantomJS
// 这里也可以选择多个浏览器,测试用例将在多个浏览器里执行
// 这里我只选择了PhantomJS(键入空白执行下一步)
Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
> PhantomJS
>

// 告诉需要执行的测试用例的代码路径,支持正则,可以写多个(键入空白执行下一步)
What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
> src/**/*.js
> test/**/*.js
14 10 2016 10:49:43.958:WARN [init]: There is no file matching this pattern.

>

// 上面指定的路径中需要排除在外的文件
Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
>

// 是否观察文件的变化,自动测试
Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> yes


Config file generated at "E:\demo\karma-test\karma.conf.js".

命令运行完成后,我们可以看到在当前目录下生成了karma.conf.js文件。同时,根据我们的配置情况,package.json里也多了一些依赖项。如我的package.json里,就多了

"devDependencies": {
    "karma-jasmine": "^1.1.2",
    "karma-phantomjs-launcher": "^1.0.4"
}

因为我们选择的是使用jasmine框架和phantomjs,所以自动添加了这两个Karma依赖。

安装新增的依赖项

// 自动安装package.json新增的依赖项
$ npm install

// 安装jasmine框架
$ npm install jasmine-core --save-dev

编写第一个测试用例

创建一个 src 目录和一个 test 目录,在其中分别创建 index.jsindex.spec.js 文件。
我要做的测试内容比较简单,对 index.js 中的两个函数(一个加法函数,一个乘法函数)进行测试。
index.js 文件如下:

// 加法函数
function add(x){
    return function(y){
        return x + y;
    }
}

// 乘法函数
function multi(x){
    return function(y){
        return x * y + 1;
    }
}

index.spec.js 文件如下:

describe("运算功能单元测试",function(){
    it("加法函数测试",function(){
        var add5 = add(5)
        expect(add5(5)).toBe(10)
    });

    it("乘法函数测试",function(){
       var multi5 = multi(5)
        expect(multi5(5)).toBe(25)
    })
})

单测的代码写好后,就可以使用 karma start 来运行单元测试。由于我们的乘法代码中有错误,因此测试结果是这样的:

23 07 2018 15:28:06.122:INFO [watcher]: Changed file "E:/demo/karma-test/test/index.spec.js".
23 07 2018 15:28:06.334:INFO [watcher]: Changed file "E:/demo/karma-test/test/index.spec.js".
23 07 2018 15:28:06.570:INFO [watcher]: Changed file "E:/demo/karma-test/test/index.spec.js".
PhantomJS 2.1.1 (Windows 8 0.0.0) 运算功能单元测试 乘法函数测试 FAILED
        Expected 26 to be 25.
        <Jasmine>
        test/index.spec.js:9:31
        <Jasmine>
PhantomJS 2.1.1 (Windows 8 0.0.0): Executed 2 of 2 (1 FAILED) (0.004 secs / 0.003 secs)
TOTAL: 1 FAILED, 1 SUCCESS

将乘法函数的代码改为正常,再次启用 karma start 进行测试:

23 07 2018 15:30:39.779:INFO [watcher]: Changed file "D:/test/karma-test/test/index.spec.js".
PhantomJS 2.1.1 (Windows 8 0.0.0): Executed 2 of 2 SUCCESS (0.005 secs / 0.003 secs)
TOTAL: 2 SUCCESS

测试覆盖率

karma 的插件 karma-coverage 提供了测试代码覆盖率的支持。
首先你需要安装这个 Karma 插件,然后需要在配置文件的三个地方进行配置。

$ npm install karma-coverage --save-dev

修改karma.conf.js配置

// Karma configuration

module.exports = function(config) {
  config.set({
    ...
    // 这里配置哪些文件需要统计测试覆盖率,例如,如果你的所有代码文件都在 src文件夹中,你就需要如下配置。
    preprocessors: {
        'src/*.js': 'coverage'
    },
    // 新增 coverageReporter选项
    // 配置覆盖率报告的查看方式,type查看类型,可取值html、text等等,dir输出目录
    coverageReporter: {
        type: 'html',
        dir: 'coverage/'
    },

    // 添加配置报告选择
    reporters: ['progress','coverage'],
    
    ...
  })
}

再次执行karma start,我们能看到生成了coverage目录,在浏览器中打开目录中的index.html我们能看到覆盖率已经生成。

查看原文

赞 4 收藏 1 评论 0

该账号已被查封 赞了文章 · 4月29日

JavaScript 是如何运行的?

js

什么是JavaScript?

我们来确认一下JavaScript的定义:JavaScript 是一门解释型的动态语言。

解释型语言是相对于编译型语言存在的,源代码不是直接编译为目标代码,而是转成中间代码,再由解释器对中间代码进行解释运行。

主流编程语言有编译型(如 C++)、解释型(如 JavaScript)、和半解释半编译(如 Java)这几大类型。

代码是怎么运行的?

首先我们来了解一下代码是怎么运行的。

我们知道,代码是由CPU执行的,而目前的CPU并不能直接执行诸如if…else之类的语句,它只能执行二进制指令。但是二进制指令对人类实在是太不友好了:我们很难快速准确的判断一个二进制指令1000010010101001代表什么?所以科学家们发明汇编语言。

汇编语言

汇编语言实际上就是二进制指令的助记符。

假设10101010代表读取内存操作,内存地址是10101111,寄存器地址是11111010,那么完整的操作101010101010111111111010就代表读取某个内存地址的值并装载到寄存器,而汇编语言并没有改变这种操作方式,它只是二进制指令的映射:

LD:10101010 
id:10101111
R:11111010

这样上述指令就可以表达为LD id R ,大大增强了代码的可读性。

但是这样还不够友好,CPU只能执行三地址表达式,和人的思考方式、语言模式相距甚远。所以伟大的科学家们又发明了高级语言。

高级语言

“代码是写给人看的,不是写给机器看的,只是顺便计算机可以执行而已。”

高级语言之所以称之为“高级”,就是因为它更加符合我们的思维和阅读习惯。if…else这种语句看起来要比1010101010舒服的多了。但是计算机并不能直接执行高级语言,所以还需要把高级语言转化为汇编语言/机器指令才能执行。这个过程就是编译。

JavaScript 需要编译吗?

JavaScript毫无疑问是高级语言,所以它肯定是需要编译后才能执行。但为什么我们又称之为解释型语言呢?它和编译型语言、半解释半编译型语言又有什么区别呢?我们先从编译说起。

编译

之前我们已经了解编译的概念,下面我们来聊聊平台:同样一份C++代码在Windows上会编译成.obj文件,而在Linux上则生成.o文件,两者不能通用。这是因为一个可执行文件除了代码外还需要操作系统 API、内存、线程、进程等系统资源,而不同的操作系统其实现也不尽相同。比如我们熟悉的I/O多路复用(事件驱动的灵魂),在Windows上的实现方案是IOCP方案,在Linux上是epoll。所以针对不同的平台,编译型语言需要分别编译,甚至需要分别编写,而且生成的可执行文件其格式并不相同。

跨平台

Java在此之上更进一步,它通过引入字节码实现了跨平台运行:无论是在什么操作系统上.java文件编译出的都是.class文件(这就是字节码文件,一种中间形态的目标代码)。然后Java对不同的系统提供不同的Java虚拟机用于解释执行字节码文件。解释执行并不生成目标代码,但其最终还是要转为汇编/二进制指令来给计算机执行的。

假如我们自己完全独立的新写一个简单的操作系统,那么它能不能运行Java呢?很显然是不能的,因为并没有这个系统相应的JVM。所以Java的跨平台、任何其他语言的跨平台,都是有局限性的。

Java采用半解释半编译的好处就是大大提升了开发效率,然而相应的则降低了代码的执行效率,毕竟虚拟机是有性能损失的。

解释执行

JavaScript则更进一步。它是完全的解释执行,或者叫做即时编译。它不会有中间代码生成,也不会有目标代码生成。这个过程通常由宿主环境(如浏览器、Node.js)包办。

编译过程

现在我们确认了,即使是解释执行的语言,也是需要编译的。那么代码是如何编译的呢?我们来简单了解一下。

词法分析

词法分析会把语句分解成词法单元,即Token。

function square(n){
 return n*n;
}

这个函数会被词法分析器识别为function square(n){return,,n ,*n}并且给它们加上标注,代表这是一个变量还是一个操作。

语法分析

这个过程会把Token转化成抽象语法树(AST):

{
 type:'function',
    id:{
        type:'id'
        name:'square'
    },
    params:[
        {
            type:'id',
            name:'n'
        }
    ]
    ...
}

优化及代码生成

在这一步编译器会做一些优化工作,比如删除多余运算、删除未用赋值、合并部分变量等等操作,最后生成目标代码。

由于即时编译型语言的编译通常发生在运行前几微秒,所以编译器来不及做太多的优化工作。这也是相比编译型语言,早期JavaScript性能孱弱的原因之一。不过就现在而言,益于 V8 引擎(相比早期的JavaScript的引擎转换成字节码或解释执行,Node.js可以用 V8 提供的 JS2C 工具将 JavaScript 转译为 C++代码),JavaScript 和其他语言性能上的差距已经不足为道了。

链接及装载

目标代码基本不能独立运行。应用程序一般都会由多个部分(模块)组成 ,比如C++中一个简单的输出就要引入标准库 iostream

#include <iostream>
using namespace std;
int main(){    
    cout << "Happy Hacking!\n";    
    return 0;
}

编译器需要把多份目标代码(库)链接起来才能生成可执行文件。至此,我们简单的了解了编译过程。但实际上编译比我们所讲的要复杂得多,在此就不在展开了。

什么是动态语言,动态类型?

我们还知道,JavaScript是动态语言。那么什么是动态语言?

通常来说,这是指在运行时代码可以根据某些条件改变自身结构的语言。比如JavaScript在运行时新的函数、对象、甚至代码都可以被引进(eval);又比如Objective-C,它也可以在运行时修改对象,但它不能动态创建类,也没有 eval 方法。那Objective-C算是动态语言吗?所以我认为,动态语言是个程度的问题,我们不必在这个概念上太过纠结,可以更多的关注其应用。APP中常用的热更新功能就是基于动态语言特性而得以实现的。

JavaScript又是一门动态类型的语言,动态类型又是什么?动态类型的定义倒是很明确:数据类型不是在编译阶段确定,而是在运行时确定。

那么 TypeScript 是什么类型的语言呢?它有静态类型检查,它是静态语言吗?实际上它只是 JavaScript 的一个方言。TypeScript 最终还是要转译为 JavaScript 才能执行(tsc),就如同我们使用babel 把 ES6 代码转译为 ES5 一样。这个过程严格上来说不是编译。

TypeScript 最大的优势就是静态类型检查和类型推断,这是 JavaScript 严重缺失的能力。但实际上如果我们忽略IDE 给的报错提示强行运行 TS 代码,也还是有几率能够成功跑起来的。

错误

刚刚我们提到报错,不妨再扩展说一说错误。通常来说错误分为以下几种:

  • 编译时错误
  • 链接时错误
  • 运行时错误

是不是和编译过程能够严格对应起来?

编译时错误

编译时错误分为:

  • 语法错误

    var str ='s ;

    这就是典型的语法错误,这种代码无法生成AST,在词法分析阶段就会报错。通常我们这么写代码,IDE 就会报错。这是IDE的优化工作,和词法分析相关。

  • 类型错误

    编译器会检查我们声明的变量和函数的类型,JavaScript中我们非常熟悉的Type Error:undefined is not object就是此类错误。

链接时错误

在链接阶段发生的异常。这种情况 JavaScript 中比较少见,在编译型语言中比较常见。

运行时错误

这是最难排查的错误了,举例来说:

int divider(int a,int b){
    return a/b;
}

上面的代码在编辑编译、链接阶段都没问题,也能够正常的生成可执行文件。但是一旦如此使用divider(1,0)就会报错了,这就是典型的运行时错误。通常来说运行时错误都是程序不够健壮导致的。

JavaScript中最常见的十个错误:

下图是某错误处理平台收集统计的JavaScript Top10 错误,其中7个TypeError,1个 ReferenceError:

top10_javascript_error

显然这 8 种问题,我们都能用 TypeScript 在编码早期及时应对。

结语

现在我们已经了解JavaScript是如何运行的。但是了解这些能够帮我们写出更好的代码吗?

答案是肯定的。且不说TypeScript能够帮助我们完善类型检查和类型推断,JavaScript的作用域、this也是和编译过程强相关的;而目前主流的小程序框架都能够支持一套代码、多个平台,相信读完本文后,你大致也了解了这些技术背后的原理。
Happy Hacking!

顺便给大家推荐一下Fundebug,很好用的BUG监控工具~

qr.001.jpeg

查看原文

赞 11 收藏 4 评论 1

该账号已被查封 赞了文章 · 1月7日

前端工程师学Docker ? 看这篇就够了 【原创精读】

前端工程师,为什么要学习Docker ?

传统的虚拟机,非常耗费性能

Docker可以看成一个高性能的虚拟机,并且不会浪费资源,主要用于Linux环境的虚拟化,类似VBox这种虚拟机,不同的是Docker专门为了服务器虚拟化,并支持镜像分享等功能。前端工程师也可以用于构建代码等等

目前看,Dokcer不仅带火了GO语言,还会持续火下去

首先,我们看看传统的虚拟机和Docker的区别

传统的虚拟机:

Docker:

可以看到,传统的虚拟机是每开一个虚拟机,相当于运行一个系统,这种是非常占用系统资源的,但是Docker就不会。但是也做到了隔离的效果

Docker容器虚拟化的优点:

  1. 环境隔离

Docker实现了资源隔离,实现一台机器运行多个容器互不影响。

  1. 更快速的交付部署

使用Docker,开发人员可以利用镜像快速构建一套标准的研发环境,开发完成后,测试和运维人员可以直接通过使用相同的环境来部署代码。

  1. 更高效的资源利用

Docker容器的运行不需要额外的虚拟化管理程序的支持,它是内核级的虚拟化,可以实现更高的性能,同时对资源的额外需求很低。

  1. 更易迁移扩展

Docker容器几乎可以在任意的平台上运行,包括乌力吉、虚拟机、公有云、私有云、个人电脑、服务器等,这种兼容性让用户可以在不同平台之间轻松的迁移应用。

  1. 更简单的更新管理

使用Dockerfile,只需要小小的配置修改,就可以替代以往的大量的更新工作。并且所有修改都是以增量的方式进行分发和更新,从而实现自动化和高效的容器管理。

正式开始

本文撰写于2019年10月13日

电脑系统:Mac OS

使用最新版官网下载的Docker

以下代码均手写,可运行

下载官网的Docker安装包,然后直接安装

https://www.docker.com/

Docker官网下载地址
安装后直接打开

打开终端命令行,输入docker,会出现以下信息,那么说明安装成功

下载安装成功后,首先学习下Docker的两个核心知识点

container(容器)和image(镜像)

Docker的整个生命周期由三部分组成:镜像(image)+容器(container)+仓库(repository

思维导图如下:

该如何理解呢?

每台宿主机(电脑),他下载好了Docker后,可以生成多个镜像,每个镜像,可以创建多个容器。发布到仓库时,以镜像为单位。可以理解成:一个容器就是一个独立的虚拟操作系统,互不影响,而镜像就是这个操作系统的安装包。想要生成一个容器,就用安装包(镜像)生成一次

上面就是Docker的核心概念,下面开始正式操作

补充一点:如果想深入Docker , 还是要去认真学习下原理,今天我们主要讲应用层面的

首先,我们回到终端命令行操作

输入:

docker images

如果你的电脑上之前有创建过的镜像,会得到如下:

如果没有的话就是空~

我们首先创建一个自己的镜像

先编写一个Node.js服务

创建index.js

// index.js
const Koa = require('koa');
const app = new Koa();

app.use(async ctx => {
   ctx.body = 'Hello docker';
});

app.listen(3000);

然后配置package.json文件

{
    "name": "app",
    "version": "1.0.0",
    "private": true,
    "scripts": {
      "start": "node server.js"
    },
    "dependencies": {
      "koa": "^2.5.0"
    }
   }
   

正常情况下 使用

npm startnode index.js 就可以启动服务
可是我们这里需要打包进Docker中,这里就需要写一个配置文件dockerfile

vsCode有天然插件支持

在目录下新建文件dockerfile,加入如下配置

FROM  node 
ADD . /app/
EXPOSE 3000
WORKDIR /app
RUN npm install
CMD ["node","./index.js"]

解释一下,上面这些配置的作用

FROM 是设置基础镜像,我们这里需要Node

ADD是将当前文件夹下的哪些文件添加到镜像中 参数是 [src,target]

这里我们使用的 . 意思是所有文件,当然跟git一样,可以配置ignore文件

EXPOSE是向外暴露的端口号

WORKDIR是说工作目录,我们这里将文件添加到的是app目录,所以配置app目录为工作目录,
这样就不用在命令行前面加/app

RUN是先要执行的脚本命令

CMD是执行的cmd命令

可以想一想,我们打包好镜像后,然后启动镜像会发生什么?

文件编写完,使用命令打包镜像

使用命令打包已经好的文件目录


 docker image build ./ -t app

打包后出现提示:

此时我们查看Docker镜像,使用命令:

docker images

我们可以清楚看到,app镜像已经打包成功,下面我们启动它



docker run -p 8000:3000 app 

使用上面命令即可启动我们的镜像,这时我们在命令中输入

curl 127.0.0.1:8000

得到返回内容

Hello docker

浏览器输入: 127.0.0.1:8000 即可访问到页面~

以上说明,我们的第一个Docker镜像已经制作成功

有人可能会觉得到这里,镜像和容器有点混淆了,不是先有镜像再有容器吗?

其实是我们启动的镜像有脚本命令帮我们启动了服务,于是Docker帮我们自动创建了容器

查看Docker容器命令:

docker ps -a 列出所有容器
不加 -a 仅列出正在运行的,像退出了的或者仅仅只是创建了的就不列出来
docker container ls 列出当前运行的容器

输入上面 docker container ls

得到结果

原来Docker看我们启动了脚本服务,帮我们自动生成了容器?

下面我们来一个生成镜像,再生成容器,最后手动启动容器的例子

这次我们配置,加入Nginx反向代理服务器

首先,创建用户需要看到的html文件

这里我们给一个普通的 hello-world内容的index.html文件即可

然后创建dickerfile文件,配置如下,将index.html文件添加到对应的位置


FROM nginx

COPY ./index.html /usr/share/nginx/html/index.html

EXPOSE 80

对外暴露端口号80

这里特别提示:配置文件怎么写,根据你的基础镜像来,百度基本都能找到,不用纠结这个

此时的文件结构:

老规矩,开始打包

docker build ./ -t html

打印信息:

输入终端命令:

docker images

得到结果:

新的镜像html已经构建成功,但是此时查看容器,是没有正在运行的

输入命令:

docker container ls //查看正在运行的所有容器
docker container ls -a //查看所有容器
得到结果是:

可以确认的是,我们创建镜像不会自动生成和启动容器

我们手动生成容器

docker container create -p 8000:80 html

此时命令行返回 一段值

输入

docker container ls

没有显示有任何启动的容器,这时候我们手动启动

输入

docker container start ***(上面那段值)

再重复 docker container ls 命令

得到结果

此时访问localhost:8000即可正常访问页面~

至此,我们可以确定,创建镜像只要不启动,不会生成容器,更不会运行容器

那怎样将Docker用在前端的日常构建中呢?

我们使用gitHub+travis+docker来形成一套完整的自动化流水线

只要我们push新的代码到gitHub上,自动帮我们构建出新的代码,然后我们拉取新的镜像即可(gitLab也有对应的代码更新事件钩子,可以参考那位手动实现Jenkens的文章)

首先我们先进入 Travis CI 官网配置,注册绑定自己的gitHub账号

然后在左侧将自己需要git push后自动构建镜像的仓库加入

接着在项目根目录配置 .travis.yml 文件



language: node_js
node_js:
  - '12'
services:
  - docker

before_install:
  - npm install
  - npm install -g parcel-bundler

script:
  - parcel build ./index.js
  - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
  - docker build -t jinjietan/mini-react:latest .
  - docker push jinjietan/mini-react:latest

每次更新push代码,都会下载,然后执行打包命令,这样你下载的镜像就是有最新的代码。不再需要每个人下载打开镜像再去build

为了降低复杂度,这里使用了Parcel打包工具,零配置

更改dockerfile内容,将parcel打包后的内容COPY进容器

FROM nginx
COPY ./index.html /usr/share/nginx/html/
COPY ./dist /usr/share/nginx/html/dist
EXPOSE 80

添加好了你的库之后,选择这里的设置

然后添加两个环境变量:

DOCKER_USERNAME和DOCKER_PASSWORD

这里,我将我编写的mini-react框架源码,放入docker中,然后使用parcel打包工具打包,再用nginx反向代理~

特别提示:这里的Docker容器,想要后台运行,就必须有一个前台进程。容器运行的命令如果不是那些一直挂起的命令(比如tcp,ping),就是会自动退出的

通过 docker ps -a 可以看到容器关闭的原因

注意 :jinejietan/mini-react应该换成你的用户名/包名,再push代码

这是思维导图:

当配置成功,代码被推送到gitHub上后,travis-ci帮我们自动构建发布新镜像

一定要学会使用: docker ps -a 查看容器的状态

成功的提示:

至此,发布,自动构建镜像已经完成

正式开始拉取镜像,启动容器

我们刚才发布的镜像名称是:jinjietan/mini-react

先使用下面几条命令

docker中 启动所有的容器命令
 
docker start $(docker ps -a | awk '{ print $1}' | tail -n +2)
docker中 关闭所有的容器命令

docker stop $(docker ps -a | awk '{ print $1}' | tail -n +2)
docker中 删除所有的容器命令

docker rm $(docker ps -a | awk '{ print $1}' | tail -n +2)
docker中 删除所有的镜像

docker rmi $(docker images | awk '{print $3}' |tail -n +2)
tail -n +2 表示从第二行开始读取

清除当前宿主机上面所有的镜像,容器,依次执行

然后使用:

docker image pull jinjietan/mini-react:latest

拉取镜像,这时候需要下载

拉取完成后,使用

docker images

可以看到jinjietan/mini-react:latest镜像已经存在了

我们使用

docker container create -p 8000:80 jinjietan/mini-react:latest

创建这个镜像的容器,并且绑定在端口号8000

最后输入下面的命令,即可启动mini-react框架的容器

docker container start  ***(上面create的返回值)

浏览器输入 127.0.0.1:8000 发现,访问成功,框架生效。

Docker的使用,我们大致就到这里,个人认为,用Docker比不用好,这个技术已经快跟TypeScript一样,到不学不行的阶段了。

并不是说你非要用它,而是比如说,你如果不怎么懂TypeScript,你就没办法把如今那些优秀库的大部门的源码搞得那么清楚。

越来越多的技术在依赖Docker

当然,其实这个mini-react框架源码也是不错的,如果有兴趣可以了解以下,源码都在:

mini-react框架+镜像配置源码,记得切换到diff-async分支哦~

https://github.com/JinJieTan/...

如果觉得写得不错,可以右下角点个在看

关注一下我的微信公众号:前端巅峰 ~ 回复加群即可加入大前端交流群

主要注重技术点:即时通讯,跨平台重型应用开发,全栈工程师方向前沿技术

查看原文

赞 149 收藏 99 评论 11

该账号已被查封 赞了文章 · 2019-12-05

聊一聊 cookie

咱们不搞一开始就一大堆理论知识介绍,怕把人讲懵了...... 咱们换一个思维方式——"从现象看本质",先说说我们看到了什么,再从看到的现象中提出问题,最后深入寻找答案。

我们看到的 cookie

我自己创建了一个网站,网址为http://ppsc.sankuai.com。在这个网页中我设置了几个cookieJSSESSIONIDPA_VTIMEskmtutctest

在 chrome 浏览器中打开这个网站,进入开发者模式,点击Resources栏 -> 选择cookies,我们会看到如下图所示的界面:

图片描述

解释一下:左边栏Cookies下方会列举当前网页中设置过cookie的域都有哪些。上图中只有一个域,即“ppsc.sankuai.com”。而右侧区域显示的就是某个域下具体的 cookie 列表,对应上图就是“ppsc.sankuai.com”域下设置的4个cookie

在这个网页中我往http://ppsc.sankuai.com/getList接口发了一个 Ajax 请求,request header如下图所示:

图片描述

从上图中我们会看到request header中自动添加了Cookie字段(我并没有手动添加这个字段哦~),Cookie字段的值其实就是我设置的那4个 cookie。这个请求最终会发送到http://ppsc.sankuai.com这个服务器上,这个服务器就能从接收到的request header中提取那4个cookie

上面两张图展示了cookie的基本通信流程:设置cookie => cookie被自动添加到request header中 => 服务端接收到cookie。这个流程中有几个问题需要好好研究:

  1. 什么样的数据适合放在cookie中?

  2. cookie是怎么设置的?

  3. cookie为什么会自动加到request header中?

  4. cookie怎么增删查改?

我们要带着这几个问题继续往下阅读。

cookie 是怎么工作的?

首先必须明确一点,存储cookie是浏览器提供的功能。cookie 其实是存储在浏览器中的纯文本,浏览器的安装目录下会专门有一个 cookie 文件夹来存放各个域下设置的cookie

当网页要发http请求时,浏览器会先检查是否有相应的cookie,有则自动添加在request header中的cookie字段中。这些是浏览器自动帮我们做的,而且每一次http请求浏览器都会自动帮我们做。这个特点很重要,因为这关系到“什么样的数据适合存储在cookie中”。

存储在cookie中的数据,每次都会被浏览器自动放在http请求中,如果这些数据并不是每个请求都需要发给服务端的数据,浏览器这设置自动处理无疑增加了网络开销;但如果这些数据是每个请求都需要发给服务端的数据(比如身份认证信息),浏览器这设置自动处理就大大免去了重复添加操作。所以对于那设置“每次请求都要携带的信息(最典型的就是身份认证信息)”就特别适合放在cookie中,其他类型的数据就不适合了。

但在 localStorage 出现之前,cookie被滥用当做了存储工具。什么数据都放在cookie中,即使这些数据只在页面中使用而不需要随请求传送到服务端。当然cookie标准还是做了一些限制的:每个域名下的cookie 的大小最大为4KB,每个域名下的cookie数量最多为20个(但很多浏览器厂商在具体实现时支持大于20个)。

cookie 的格式

document.cookie

JS 原生的 API提供了获取cookie的方法:document.cookie(注意,这个方法只能获取非 HttpOnly 类型的cookie)。在 console 中执行这段代码可以看到结果如下图:

图片描述

打印出的结果是一个字符串类型,因为cookie本身就是存储在浏览器中的字符串。但这个字符串是有格式的,由键值对 key=value构成,键值对之间由一个分号和一个空格隔开。

cookie 的属性选项

每个cookie都有一定的属性,如什么时候失效,要发送到哪个域名,哪个路径等等。这些属性是通过cookie选项来设置的,cookie选项包括:expiresdomainpathsecureHttpOnly。在设置任一个cookie时都可以设置相关的这些属性,当然也可以不设置,这时会使用这些属性的默认值。在设置这些属性时,属性之间由一个分号和一个空格隔开。代码示例如下:

"key=name; expires=Thu, 25 Feb 2016 04:18:00 GMT; domain=ppsc.sankuai.com; path=/; secure; HttpOnly"

expires

expires选项用来设置“cookie 什么时间内有效”。expires其实是cookie失效日期,expires必须是 GMT 格式的时间(可以通过 new Date().toGMTString()或者 new Date().toUTCString() 来获得)。

expires=Thu, 25 Feb 2016 04:18:00 GMT表示cookie讲在2016年2月25日4:18分之后失效,对于失效的cookie浏览器会清空。如果没有设置该选项,则默认有效期为session,即会话cookie。这种cookie在浏览器关闭后就没有了。

expires 是 http/1.0协议中的选项,在新的http/1.1协议中expires已经由 max-age 选项代替,两者的作用都是限制cookie 的有效时间。expires的值是一个时间点(cookie失效时刻= expires),而max-age 的值是一个以为单位时间段(cookie失效时刻= 创建时刻+ max-age)。
另外,max-age 的默认值是 -1(即有效期为 session );若max-age有三种可能值:负数、0、正数。负数:有效期session0:删除cookie;正数:有效期为创建时刻+ max-age

domain 和 path

domain是域名,path是路径,两者加起来就构成了 URL,domainpath一起来限制 cookie 能被哪些 URL 访问。

一句话概括:某cookie的 domain为“baidu.com”, path为“/ ”,若请求的URL(URL 可以是js/html/img/css资源请求,但不包括 XHR 请求)的域名是“baidu.com”或其子域如“api.baidu.com”、“dev.api.baidu.com”,且 URL 的路径是“/ ”或子路径“/home”、“/home/login”,则浏览器会将此 cookie 添加到该请求的 cookie 头部中。

所以domainpath2个选项共同决定了cookie何时被浏览器自动添加到请求头部中发送出去。如果没有设置这两个选项,则会使用默认值。domain的默认值为设置该cookie的网页所在的域名,path默认值为设置该cookie的网页所在的目录。

特别说明1:
发生跨域xhr请求时,即使请求URL的域名和路径都满足 cookie 的 domain 和 path,默认情况下cookie也不会自动被添加到请求头部中。若想知道原因请阅读本文最后一节)

特别说明2:
domain是可以设置为页面本身的域名(本域),或页面本身域名的父域,但不能是公共后缀 public suffix。举例说明下:如果页面域名为 www.baidu.com, domain可以设置为“www.baidu.com”,也可以设置为“baidu.com”,但不能设置为“.com”或“com”。

secure

secure选项用来设置cookie只在确保安全的请求中才会发送。当请求是HTTPS或者其他安全协议时,包含 secure 选项的 cookie 才能被发送至服务器。

默认情况下,cookie不会带secure选项(即为空)。所以默认情况下,不管是HTTPS协议还是HTTP协议的请求,cookie 都会被发送至服务端。但要注意一点,secure选项只是限定了在安全情况下才可以传输给服务端,但并不代表你不能看到这个 cookie。

下面我们设置一个 secure类型的 cookie:

document.cookie = "name=huang; secure";

之后你就能在控制台中看到这个 cookie 了,如下图所示:

图片描述

这里有个坑需要注意下:
如果想在客户端即网页中通过 js 去设置secure类型的 cookie,必须保证网页是https协议的。在http协议的网页中是无法设置secure类型cookie的。

httpOnly

这个选项用来设置cookie是否能通过 js 去访问。默认情况下,cookie不会带httpOnly选项(即为空),所以默认情况下,客户端是可以通过js代码去访问(包括读取、修改、删除等)这个cookie的。当cookiehttpOnly选项时,客户端则无法通过js代码去访问(包括读取、修改、删除等)这个cookie

在客户端是不能通过js代码去设置一个httpOnly类型的cookie的,这种类型的cookie只能通过服务端来设置。

那我们在页面中怎么知道哪些cookiehttpOnly类型的呢?看下图:

图片描述

凡是httpOnly类型的cookie,其 HTTP 一列都会打上√,如上图中的PA_VTIME。你通过document.cookie是不能获取的,也不能修改PA_VTIME的。

——httpOnly与安全

从上面介绍中,大家是否会有这样的疑问:为什么我们要限制客户端去访问cookie?其实这样做是为了保障安全。

试想:如果任何 cookie 都能被客户端通过document.cookie获取会发生什么可怕的事情。当我们的网页遭受了 XSS 攻击,有一段恶意的script脚本插到了网页中。这段script脚本做的事情是:通过document.cookie读取了用户身份验证相关的 cookie,并将这些 cookie 发送到了攻击者的服务器。攻击者轻而易举就拿到了用户身份验证信息,于是就可以摇摇大摆地冒充此用户访问你的服务器了(因为攻击者有合法的用户身份验证信息,所以会通过你服务器的验证)。

如何设置 cookie?

知道了cookie的格式,cookie的属性选项,接下来我们就可以设置cookie了。首先得明确一点:cookie既可以由服务端来设置,也可以由客户端来设置。

服务端设置 cookie

不管你是请求一个资源文件(如 html/js/css/图片),还是发送一个ajax请求,服务端都会返回response。而response header中有一项叫set-cookie,是服务端专门用来设置cookie的。如下图所示,服务端返回的response header中有5个set-cookie字段,每个字段对应一个cookie(注意不能将多个cookie放在一个set-cookie字段中),set-cookie字段的值就是普通的字符串,每个cookie还设置了相关属性选项。

图片描述

注意:

  • 一个set-Cookie字段只能设置一个cookie,当你要想设置多个 cookie,需要添加同样多的set-Cookie字段。

  • 服务端可以设置cookie 的所有选项:expiresdomainpathsecureHttpOnly

客户端设置 cookie

在网页即客户端中我们也可以通过js代码来设置cookie。如我当前打开的网址为http://dxw.st.sankuai.com/mp/,在控制台中我们执行了下面代码:

document.cookie = "name=Jonh; ";

查看浏览器 cookie 面板如下图所示,cookie确实设置成功了,而且属性选项 domainpathexpires都用了默认值。

图片描述

再执行下面代码:

document.cookie="age=12; expires=Thu, 26 Feb 2116 11:50:25 GMT; domain=sankuai.com; path=/";

查看浏览器cookie 面板,如下图所示,新的cookie设置成功了,而且属性选项 domainpathexpires都变成了设定的值。

图片描述

注意:

  • 客户端可以设置cookie 的下列选项:expiresdomainpathsecure(有条件:只有在https协议的网页中,客户端设置secure类型的 cookie 才能成功),但无法设置HttpOnly选项。

用 js 如何设置多个 cookie

当要设置多个cookie时, js 代码很自然地我们会这么写:

document.cookie = "name=Jonh; age=12; class=111";

但你会发现这样写只是添加了第一个cookie“name=John”,后面的所有cookie都没有添加成功。所以最简单的设置多个cookie的方法就在重复执行document.cookie = "key=name",如下:

document.cookie = "name=Jonh";
document.cookie = "age=12";
document.cookie = "class=111";

如何修改、删除

修改 cookie

要想修改一个cookie,只需要重新赋值就行,旧的值会被新的值覆盖。但要注意一点,在设置新cookie时,path/domain这几个选项一定要旧cookie 保持一样。否则不会修改旧值,而是添加了一个新的 cookie。

删除 cookie

删除一个cookie 也挺简单,也是重新赋值,只要将这个新cookie的expires 选项设置为一个过去的时间点就行了。但同样要注意,path/domain/这几个选项一定要旧cookie 保持一样。

cookie 编码

cookie其实是个字符串,但这个字符串中逗号、分号、空格被当做了特殊符号。所以当cookie的 key 和 value 中含有这3个特殊字符时,需要对其进行额外编码,一般会用escape进行编码,读取时用unescape进行解码;当然也可以用encodeURIComponent/decodeURIComponent或者encodeURI/decodeURI三者的区别可以参考这篇文章)。

var key = escape("name;value");
var value = escape("this is a value contain , and ;");
document.cookie= key + "=" + value + "; expires=Thu, 26 Feb 2116 11:50:25 GMT; domain=sankuai.com; path=/";

跨域请求中 cookie

之前在介绍 XHR 的一篇文章里面提过:默认情况下,在发生跨域时,cookie 作为一种 credential 信息是不会被传送到服务端的。必须要进行额外设置才可以。具体原因和如何设置可以参考我的这篇文章:你真的会使用XMLHttpRequest吗?

另外,关于跨域资源共享 CORS极力推荐大家阅读阮一峰老师的这篇 跨域资源共享 CORS 详解

其他补充

  1. 什么时候 cookie 会被覆盖:name/domain/path 这3个字段都相同的时候;

  2. 关于domain的补充说明(参考1/参考2):

    1. 如果显式设置了 domain,则设置成什么,浏览器就存成什么;但如果没有显式设置,则浏览器会自动取 url 的 host 作为 domain 值;

    2. 新的规范中,显式设置 domain 时,如果 value 最前面带点,则浏览器处理时会将这个点去掉,所以最后浏览器存的就是没有点的(注意:但目前大多数浏览器并未全部这么实现)

    3. 前面带点‘.’和不带点‘.’有啥区别:

      • 带点:任何 subdomain 都可以访问,包括父 domain

      • 不带点:只有完全一样的域名才能访问,subdomain 不能(但在 IE 下比较特殊,它支持 subdomain 访问)

总结

咱们今天就聊到这里,若有不对之处欢迎各位指正~~
最后附上一些参考资料:

  1. http://www.quirksmode.org/js/...

  2. http://www.tutorialspoint.com...

  3. http://www.allaboutcookies.or...

  4. http://bubkoo.com/2014/04/21/...

查看原文

赞 332 收藏 535 评论 50

认证与成就

  • 获得 16 次点赞
  • 获得 7 枚徽章 获得 0 枚金徽章, 获得 3 枚银徽章, 获得 4 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-07-06
个人主页被 573 人浏览