# 第3章 Vue 基础介绍

本章节相关代码存放在Github (opens new window)中。

本章我们主要介绍一些 Vue 里常用的概念、语法和 API。

其实 Vue 以入门门槛低、对初学者友好而收获了一致的好评,在这种情况下,其实最新、最完整的语法和 API,当然是从官网上搜索最合适。而本章内容则配合一些对使用的理解和生动的实例,来让大家对 Vue 有更深刻的认知。

# 3.1 Vue 实例

讲 Vue 的基础,当然得先从实例说起。要怎么理解实例两个字?在 Vue 里,可以理解为每一个通过用 Vue 函数创建的,都是一个 Vue 实例:

// new Vue返回一个Vue实例
var vm = new Vue({
  // 选项
});

Vue 实例和 Vue 应用是什么关系呢?官方介绍:一个 Vue 应用由一个通过new Vue()创建的根 Vue 实例,以及可选的嵌套的、可复用的组件树组成。所以 Vue 实例是属于 Vue 应用的一部分,与组件树组成了 Vue 应用:

// 一个Vue应用,由根实例+组件树组成
根实例
└─ 根组件 // 此行开始,为组件树
   ├─ 组件1
   │  ├─ 组件1-1
   │  └─ 组件1-2
   └─ 组件2
      ├─ 组件2-1
      └─ 组件2-2

那组件又是什么呢?关于组件的介绍和使用,会在下一章介绍。

# 3.1.1 生命周期钩子

生命周期要怎么理解呢?结合前言中浏览器页面渲染的机制,以及《第1章 Vue 框架介绍》中 Vue 模板的解析和渲染的过程,我们知道在 Vue 中要渲染一块页面内容的时候,会有这么几个过程:
(1) 解析语法生成 AST。
(2) 根据 AST 结果,完成 data 数据初始化。
(3) 根据 AST 结果和 data 数据绑定情况,生成虚拟 DOM。
(4) 将虚拟 DOM 生成真正的 DOM 插入到页面中,此时页面会被渲染。

当我们绑定的数据进行更新的时候,又会产生以下这些过程:
(5) 框架接收到数据变更的事件,根据数据生成新的虚拟 DOM 树。比较新旧两棵虚拟 DOM 树,得到差异。
(6) 把差异应用到真正的 DOM 树上,即根据差异来更新页面内容。

当我们清空页面内容时,还有:
(7) 注销实例,清空页面内容,移除绑定事件、监听器等。

所以在整个页面或是某块页面内容(组件)中,Vue 提供了以下的一些关键的生命周期钩子:

表 3-1 Vue 生命周期说明

生命周期钩子 说明 对应上述步骤
beforeCreate 初始化实例前,datamethods等不可获取 1 之后,2 之前
created 实例初始化完成,此时可获取data里数据和methods事件,无法获取 DOM 2 之后,3 之前
beforeMount 虚拟 DOM 创建完成,此时未挂载到页面中,vm.$el可获取未挂载模板 3 之后,4 之前
mounted 数据绑定完成,真实 DOM 已挂载到页面,vm.$el可获取真实 DOM 4 之后
beforeUpdate 数据更新,DOM Diff 得到差异,未更新到页面 5 之后,6 之前
updated 数据更新,页面也已更新 6 之后
beforeDestroy 实例销毁前 7 之前
destroyed 实例销毁完成 7 之后

我们可以更新官方的生命周期图如下:
image
图 3-1 Vue 生命周期说明补充前半部分
image
图 3-2 Vue 生命周期说明补充后半部分

生命周期钩子的使用方式也很简单,我们可以根据需要在特定的生命周期钩子里进行一些处理:

<div id="app"></div>
<script>
  new Vue({
    el: "#app",
    template: "<div>{{ message }}</div>",
    data() {
      return {
        message: "欢迎来到Vue的世界"
      };
    },
    methods: {
      test() {}
    },
    beforeCreate: function() {
      // 在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用
      console.log("beforeCreate", this.message, this.test, this.$el);
    },
    created: function() {
      // 在实例创建完成后被立即调用
      // 在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调
      // 挂载阶段还没开始,$el 属性目前不可见
      console.log("created", this.message, this.test, this.$el);
    },
    beforeMount: function() {
      // 在挂载开始之前被调用:相关的 render 函数首次被调用
      console.log("beforeMount", this.message, this.test, this.$el);
    },
    mounted: function() {
      // el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子
      // 如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内
      // mounted 不会承诺所有的子组件也都一起被挂载
      // 如果希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted
      console.log("mounted", this.message, this.test, this.$el);
      this.$nextTick(function() {
        // 此处整个视图已渲染完毕
      });
    },
    beforeUpdate: function() {
      // 数据更新时调用,发生在虚拟 DOM 打补丁之前
      // 这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器
    },
    updated: function() {
      // 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子
      // 当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作
    },
    beforeDestroy: function() {
      // 实例销毁之前调用。在这一步,实例仍然完全可用
    },
    destroyed: function() {
      // Vue 实例销毁后调用
      // 调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁
    }
  });
</script>

页面中我们可以看到输出结果,验证了几个生命周期的datamethods、DOM 挂载等情况:
image
图 3-3 生命周期测试输出

点击此处查看页面效果 (opens new window) 点击此处查看源码 (opens new window)

# 3.1.2 Vue 实例基本选项说明

Vue 实例提供了非常丰富的选项(new Vue()时传入的选项),除了上面介绍的生命周期之外,最常见的大概是这几个了:

表 3-2 Vue 实例常用选项

选项名 说明 类型
el 通过 CSS 选择器或者 HTMLElement 实例的方式,提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标 string/Element
template 字符串模板,将会替换挂载的元素 string
render 字符串模板的代替方案,该渲染函数接收一个createElement方法作为第一个参数用来创建 VNode (createElement: () => VNode) => VNode
data Vue 实例的数据对象,用于数据绑定 Object/Function
组件只支持Function
props 用于接收来自父组件的数据 Array<string>/Object
methods Vue 实例的事件,可用于事件绑定 { [key: string]: Function }
computed 计算属性,用于简化模板的复杂数据计算 { [key: string]: Function or { get: Function, set: Function } }
watch 观察 Vue 实例变化的一个表达式或计算属性函数 { [key: string]: string or Function or Object or Array }
directives 自定义指令 Object
filters 过滤器 Object
components 组件 Object

Vue 实例中的这些选项,大多数都可以作为全局实例属性来获取或者访问:

const vm = new Vue({
  // ...一些选项
});

vm.$data; // 获取 data
vm.$props; // 获取 props
vm.$el; // 获取挂载元素
vm.$options; // 获取 Vue 实例的初始选项
vm.$parent; // 获取父实例
vm.$root; // 获取根实例
vm.$children; // 获取当前实例的直接子组件
vm.$refs; // 获取持有注册过 ref 特性 的所有 DOM 元素和组件实例

vm.$watch; // 观察 Vue 实例变化的一个表达式或计算属性函数
vm.$set; // 向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新
vm.$delete; // 删除对象的属性。如果对象是响应式的,确保删除能触发更新视图

这些内容,我们将在本章以及后面的几章里一一讲述。更完整的选项内容和 API,各位可以去官网查询。

# 3.1.3 DOM 相关选项

既然要讲 Vue 实例,那就从最基本的 DOM 渲染相关开始讲起。

# el

Vue 实例中,el选项提供一个页面中已存在的 DOM 元素作为实例的挂载目标。挂载的意思是,在选中的该元素所在的位置进行页面渲染,该元素会被替换成需要渲染的页面内容。我们可以传入一个 CSS 选择器,也可以传入一个 DOM 元素。例如,页面中有一个 id 为#app 的元素,如果我们希望以<div id="app"></div>该元素作为 Vue 实例的挂载目标,以下方式都是可以的:

new Vue({
  // 1. 传入 Element 元素方式一
  el: document.getElementById("app"),

  // 2. 传入 Element 元素方式二
  el: document.getElementsByTagName("div")[0],

  // 3. 传入 CSS 选择器方式一
  el: "#app",

  // 4. 传入 CSS 选择器方式二
  // 最好选择唯一的元素,不推荐该方式
  el: "div"
});

所谓挂载元素,在实例挂载之后,元素可以用vm.$el访问。当然,前面生命周期中我们讲了,需要在mounted之后才能获取到:

new Vue({
  el: "#app",
  template: "<div>{{ message }}</div>",
  data() {
    return {
      message: "欢迎来到Vue的世界"
    };
  },
  mounted() {
    console.log(this.$el);
  }
});

如果在实例化时存在这个选项,实例将立即进入编译过程,否则,需要显式调用vm.$mount()手动开启编译,也就是这样:

const vm = new Vue({
  template: "<div>{{ message }}</div>",
  data() {
    return {
      message: "欢迎来到Vue的世界"
    };
  },
  mounted() {
    console.log(this.$el);
  }
});

// 需要的时候使用
vm.$mount("#app");

点击此处查看页面效果 (opens new window) 点击此处查看源码 (opens new window)

如果 render 函数和 template 属性都不存在,挂载 DOM 元素的 HTML 会被提取出来用作模板,此时,必须使用 Runtime + Compiler 构建的 Vue 库。我们来理解下这句话:

  • 编译器(Compiler): 我们可以理解为用来将模板字符串编译成为 JavaScript 渲染函数的代码,也就是第1章内容中的 AST 解析部分
  • 运行时(Runtime): 用来创建 Vue 实例、渲染并处理虚拟 DOM 等的代码,也就是第1章中 AST 以外的部分

一般来说,我们在部署生产代码的时候,已经将需要的代码解析编译好,这个时候我们只需要运行时的 Vue 库。在特殊情况下,有需要运行时解析编译的情况,如在线平台编写代码的时候,我们需要引入编译器的 Vue 库,该部分占完整版(Runtime + Compiler)的 30%。上面说到,如果我们需要使用挂载 DOM 元素的 HTML 作为模板,则需要运行时编译,也就需要编译器了。

那关于 render 函数和 template 属性的选项,我们来看一下。

# template

给 Vue 实例提供字符串模板,该模板将会替换挂载的元素,我们来看一个简单的代码片段:

<body>
  <div id="app"></div>
  <script>
    new Vue({
      el: "#app",
      template: "<p>{{ message }}</p>",
      data() {
        return {
          message: "欢迎来到Vue的世界"
        };
      },
      beforeMount() {
        console.log("beforeMount", this.$el);
      },
      mounted: function() {
        console.log("mounted", this.$el);
      }
    });
  </script>
</body>

这里挂载的元素指的是<div id="app"></div>,当我们使用了 template 选项之后,我们在页面中可以看到最终页面中的内容是 template 中的内容,此时<div id="app"></div>已经被替换成 template 中的<p></p>,并将 message 中的内容替换成绑定的数据了:
image
图 3-4 挂载元素最终页面效果

我们也能看到,在beforeMount生命周期中,vm.$el获取的是挂载的元素模板,而在mounted生命周期后则变成了 template 中的真实 DOM 元素:
image
图 3-5 挂载元素不同生命周期效果

点击此处查看页面效果 (opens new window) 点击此处查看源码 (opens new window)

如果 Vue 选项中包含 render 渲染函数,则 template 将被忽略,我们来看看渲染函数。

# render

字符串模板 template 的代替方案,该渲染函数接收一个createElement方法作为第一个参数用来创建 VNode。第1章中我们有讲到 Vue 里使用了虚拟 DOM,而createElement创建的便是虚拟 DOM,在 Vue 里称为 VNode。要怎么用呢,例如我们可以实现一个v-if的能力:

new Vue({
  // 该段实现:
  // <p v-if="condition">condition work!</p>
  // <p v-else>condition not work!</p>
  render: function (createElement) {
    if (this.condition) {
      return createElement('p', "condition work!")
    } else {
      return createElement('p', 'condition not work!')
    }
  }
  // 该段实现:
  // <ul><li v-for="item in items">{{item}}</li></ul>
  render: function (createElement) {
    return createElement('ul', this.items.map(function (item) {
      return createElement('li', item)
    }))
  }
});

一般来说,我们可以结合 JSX 来使用(需要添加 Babel 插件噢):

import MyComponent from "./MyComponent.vue";

new Vue({
  render: function(h) {
    return (
      <MyComponent>
        <p>Hello world!</p>
      </MyComponent>
    );
  }
});

# 3.2 常用模板语法

关于 Vue 实例的选项,上面讲了最基本的生命周期钩子,以及 DOM 渲染相关的。而数据相关的,或许要结合模板语法来一起讲,会更好理解。

关于数据绑定、事件绑定,其实也是一种 Vue 提供的方便开发者使用的模板语法。在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。下面我们会结合对语法的理解,来介绍它们的使用。

# 3.2.1 数据绑定

数据绑定在 Vue 里有最基础的几种方式:

表 3-3 Vue 中数据绑定的常用方式

语法 说明
插值语法{{}} 文本插值,可配合过 Javascript 表达式和过滤器使用
v-once 一次性插值,数据改变时插值处的内容不会更新
v-html 可输出真正的 HTML,不会被转义为普通文本
v-bind:(简写: 可用于绑定 DOM 属性、或一个组件 prop 到表达式

# 插值

我们来简单看看插值相关的绑定语法:

<template>
  <div>{{ message }}</div>
  <div v-once>{{ message }}</div>
  <div v-html="message"></div>

  <div>{{ msgHtml }}</div>
  <div v-html="msgHtml"></div>
</template>

<script>
  export default {
    data() {
      return {
        message: "欢迎来到Vue的世界",
        msgHtml: "<p style='color: red'>欢迎来到红色Vue的世界</p>"
      };
    },
    created() {
      this.message = "啦啦啦啦啦啦";
    },
    mounted() {
      this.message = "略略略略略";
    }
  };
</script>

虽然比较简单,但有意思的地方在于,你能猜到最终的真实 DOM 渲染是这样的吗:
image
图 3-5 Vue 中数据绑定渲染结果

点击此处查看页面效果 (opens new window) 点击此处查看源码 (opens new window)

我们知道,v-once只渲染元素和组件一次,后面的所有重新渲染过程,被绑定的该元素/组件及其所有的子节点将被视为静态内容并跳过。但在这里,最终呈现的并不是我们初始data中的“欢迎来到 Vue 的世界”,而是created周期中设定的“啦啦啦啦啦啦”。

前面讲 Vue 生命周期钩子的时候我们知道,真实 DOM 挂载发生在beforeMount之后、mounted之前,也就是说,我们在mounted之前的生命周期中更改data中 message 的值,都是有效的。

所以关于插值,需要注意以下两点:
(1) v-oncemounted生命周期之后,不可再更改。
(2) v-html请只对可信内容使用 HTML 插值,绝不要对用户提供的内容使用插值,因为它很容易导致 XSS 攻击(该内容在第1章有阐述)。

# Javascript 表达式

Vue 中支持完全的 Javascript 表达式(请注意不是语句噢),因为我们可以直接在模板中做很多数据的处理和逻辑判断:

{{ price * 100 + 2000 }} {{ message.split('').reverse().join('') }} {{ type ===
'group' ? '团队' : '个人' }}

<div v-bind:class="isActived ? 'actived' : ''"></div>

<div v-bind:index="'item-' + index"></div>

<!-- 以下是语句,不是表达式 -->
{{ var message = "123" }} {{ if(type === 'group') { return '团队' } }}

# v-bind

其实v-bind也是插值的一种,只不过它的使用方式更加灵活和多变,所以这里单独进行介绍,我们来看看以下的用法:

<!-- 绑定一个属性 -->
<img v-bind:src="imageSrc" />
<!-- 缩写 -->
<img :src="imageSrc" />
<!-- 最终会生成 `<img src="${imageSrc}">` 这样的模板 -->

<!-- 动态特性名 (2.6.0+) -->
<button v-bind:[key]="value"></button>
<!-- 动态特性名缩写 (2.6.0+) -->
<button :[key]="value"></button>
<!-- 最终会生成 `<button ${key}="${value}">` 这样的模板 -->

<!-- 内联字符串拼接 -->
<img :src="'/path/to/images/' + fileName" />

<!-- class 绑定 -->
<div :class="{ red: isRed }"></div>
<div :class="[classA, classB]"></div>
<div :class="[classA, { classB: isB, classC: isC }]">
  <!-- style 绑定 -->
  <div :style="{ fontSize: size + 'px' }"></div>
  <div :style="[styleObjectA, styleObjectB]"></div>

  <!-- 绑定一个有属性的对象 -->
  <div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>
</div>

除了 style 绑定、class 绑定以及一些常用属性 src 等绑定在日常开发中会使用较多外,还有一个重要的 prop 绑定,用于向子组件传递数据,例如我们有这么一个组件:

Vue.component("my-text", {
  props: ["text"],
  template: "<p>{{ text }}</p>"
});

则可以通过<my-text :text="myText"></my-text>的方式使用。有关组件、子组件的内容,会在后面章节详细讲述,这里就不多再说了。

# data

data是 Vue 实例的数据对象,是上述所有数据绑定的数据来源。

var vm = new Vue({
  // 1. 接受返回对象的函数
  data() {
    return {
      message: "欢迎来到Vue的世界"
    };
  },
  // 2. 也可以直接棒对象
  data: {
    message: "欢迎来到Vue的世界"
  }
});

我们已经知道,Vue 里数据的变更检测是来自于 getter/setter,从而让data的属性能够响应数据变化。前面我们也讲到,Vue 将遍历 data 选项的 JavaScript 对象所有的属性,并使用Object.defineProperty把这些属性全部转为 getter/setter:

// 响应式的变更检测
Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  // getter
  get: function reactiveGetter() {
    const value = getter ? getter.call(obj) : val;
    if (Dep.target) {
      // 依赖检测
      dep.depend();
      if (childOb) {
        childOb.dep.depend();
        if (Array.isArray(value)) {
          dependArray(value);
        }
      }
    }
    return value;
  },
  // setter,最终更新后会通知噢
  set: function reactiveSetter(newVal) {
    const value = getter ? getter.call(obj) : val;
    if (newVal === value || (newVal !== newVal && value !== value)) {
      return;
    }
    if (process.env.NODE_ENV !== "production" && customSetter) {
      customSetter();
    }
    if (getter && !setter) return;
    if (setter) {
      setter.call(obj, newVal);
    } else {
      val = newVal;
    }
    childOb = !shallow && observe(newVal);
    // 会通知噢
    dep.notify();
  }
});

由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。换句话说,只有当实例被创建时就已经存在于data中的属性才是响应式的(新增的属性等都不会触发视图的更新)。问题也是很显然,Vue 无法检测到对象属性的添加或删除,也无法检测一些特殊的数组变动:

// 只有这些操作会通知变更噢
const methodsToPatch = [
  "push",
  "pop",
  "shift",
  "unshift",
  "splice",
  "sort",
  "reverse"
];

// 拦截上述这些操作方法,然后通知变更
methodsToPatch.forEach(function(method) {
  // cache original method
  const original = arrayProto[method];
  def(arrayMethods, method, function mutator(...args) {
    const result = original.apply(this, args);
    const ob = this.__ob__;
    let inserted;
    switch (method) {
      case "push":
      case "unshift":
        inserted = args;
        break;
      case "splice":
        inserted = args.slice(2);
        break;
    }
    if (inserted) ob.observeArray(inserted);
    // notify change
    ob.dep.notify();
    return result;
  });
});

所以在更新data中绑定的对象或者数组的时候需要注意,除了使用可触发变更检测的特殊方法之外,也可以使用vm.$set(Vue.set)实例方法。vm.$set(Vue.set)用于向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新:

// 更新数组
vm.$set(vm.items, indexOfItem, newValue);

// 更新对象
vm.$set(vm.someObject, keyOfObject, newValue);

# 3.2.2 过滤器

Vue 中可以自定义过滤器,可被用于一些常见的文本格式化,支持全局定义和组件中定义。

// 全局定义
// 千分位处理
Vue.filter('thousandth', function (value) {
  if (!value) return '';
  return value && value.toString().replace(/^(-?\d+?)((?:\d{3})+)(?=\.\d+$|$)/, function (all, pre, groupOf3Digital) {
    return pre + groupOf3Digital.replace(/\d{3}/g, ',$&')
  });
})

// 局部定义
// 乘以倍数
filters: {
  multiply: function (value, times) {
    if (!value) return '';
    return value * ( parseInt(times) || 1);
  }
}

过滤器可以用在两个地方:双花括号插值和v-bind表达式。

<!-- 在双花括号中 -->
{{ number | thousandth }}

<!-- 在 `v-bind` 中 -->
<div v-bind:text="number | thousandth"></div>

<!-- 支持串联,可接受传参 -->
{{ number | multiply(1000) | thousandth }}

# 3.2.3 事件绑定

可以用v-on指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码,可用@缩写。

<template>
  <button v-on:click="addCounter">Add 1</button>
  <!-- 以下为缩写 -->
  <button @click="addCounter">Add 1</button>
  <p>The button above has been clicked {{ counter }} times.</p>
</template>

<script>
  export default {
    data() {
      return {
        counter: 0
      };
    },
    methods: {
      addCounter() {
        this.counter += 1;
      }
    }
  };
</script>

# 事件修饰符

Vue 为v-on提供了事件修饰符:

表 3-4 v-on事件修饰符

修饰符 说明
.stop event.stopPropagation(),阻止事件继续传播
.prevent event.preventDefault(),阻止默认事件
.capture 添加事件监听器时使用事件捕获模式
.once 只绑定一次
.enter/.tab/.esc/.space/.ctrl/.[keyCode] 按键修饰符

使用方式很简单,在绑定事件后面加上修饰符就可以:

<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- Alt + C -->
<input @keyup.alt.67="clear" />

# 3.3 计算属性和侦听器

computedwatch大概是 Vue 里除了datamethodsprops之外,用的最多的选项了。前面我们也讲了很多次,Vue 中数据变更之所以能更新到页面里,因为对data执行 getter/setter 转化,然后进行侦听。而computedwatch属性都是基于此,我们看看一个Watcher是怎样的:

export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    // 初始化
  }

  // 计算getter,重新收集依赖
  get () { //... }

  // 添加依赖
  addDep (dep: Dep) { //... }

  // 清理依赖集合
  cleanupDeps () { //... }

  // 订阅接口,将在依赖项更新时调用
  update () { //... }

  // 调用接口,将被调配者调用
  run () { //... }

  // 该watcher的所有依赖
  depend () { //... }

  // 从所有依赖项的订户列表中删除自己
  teardown () { //... }
}

计算属性和侦听器都离不开数据监听。

# 3.3.1 computed

有时候我们需要对绑定的数据进行一些处理,通常使用前面说过的插值的 Javascript 表达式、过滤器都可以处理完毕,但有些时候需要稍微复杂一点的逻辑计算,这种情况我们可以使用计算属性。计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算,计算属性支持读取设置:

var vm = new Vue({
  data: { a: 1 },
  computed: {
    // 仅读取
    aDouble: function() {
      return this.a * 2;
    },
    // 读取和设置
    aPlus: {
      get: function() {
        return this.a + 1;
      },
      set: function(v) {
        this.a = v - 1;
      }
    }
  }
});
vm.aPlus; // => 2
vm.aPlus = 3;
vm.a; // => 2
vm.aDouble; // => 4

# 3.3.2 watch

侦听器用来监听某些数据变化,观察 Vue 实例变化的一个表达式或计算属性函数。Vue 实例将会在实例化时调用$watch(),遍历 watch 对象的每一个属性。watch支持的方式有好几种,回调函数得到的参数为新值和旧值:

var vm = new Vue({
  data: {
    a: 1,
    b: 2,
    c: 3,
    d: 4,
    e: {
      f: {
        g: 5
      }
    }
  },
  watch: {
    a: function(val, oldVal) {
      console.log("new: %s, old: %s", val, oldVal);
    },
    // 方法名
    b: "someMethod",
    // 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深
    c: {
      handler: function(val, oldVal) {
        /* ... */
      },
      deep: true
    },
    // 该回调将会在侦听开始之后被立即调用
    d: {
      handler: "someMethod",
      immediate: true
    },
    e: [
      "handle1",
      function handle2(val, oldVal) {
        /* ... */
      },
      {
        handler: function handle3(val, oldVal) {
          /* ... */
        }
        /* ... */
      }
    ],
    // watch vm.e.f's value: {g: 5}
    "e.f": function(val, oldVal) {
      /* ... */
    }
  },
  methods: {
    someMethod() {}
  }
});
vm.a = 2; // => new: 2, old: 1

本章内容主要介绍了 Vue 实例,以及一些常用的选项和模板语法,相信大家现在已经能理解一个 Vue 实例的生命周期,以及理解一些基本语法。后面我们会基于这些基础内容,来慢慢介绍组件、指令、动画相关,以及搭配一些其他开源库来完善项目,一步步走进 Vue 的世界里。