Vue学习笔记(二)

MVVM

MVVM是一种编程的模式,Vue 的设计正是基于 MVVM 模型的(虽然 Vue 也没有完全遵循 MVVM),MVVM 是四个字母,实际上是三个东西,即 M(Model)、V(View)、VM(ViewModel)

  • model:model 是什么呢,下面这张图写的是一般 JAVAScript 对象,我们可以理解成是,Vue 实例中 data 里的数据
  • view:图中说的是DOM,其实就是我们的页面,由于页面是从我们写的模板中渲染而来的,所以我们可以理解成是我们在容器中所写的模板
  • viewmodel:这个图中很明确了,就是Vue,准确的说是 Vue 的实例对象,因此我们也经常把 Vue 的实例对象叫做 vm

那么理解了MVVM的三个组成部分,这张图就很好理解了,我们将 data 中的数据绑定到模板里的元素上,当 data 里的数据,也就是 model 发生变化时,页面也就是 view 也会随之而改变。我们上一篇文章提到过事件绑定,也就是 v-on ,它会监听 DOM 的事件,然后通知 VM 来修改 data 里的数据,也就是 model

Vue 中的数据代理

​ ​ 了解了 MVVM 模型之后,我们要引入一个概念——数据代理,如果你在控制台输出过 Vue 的实例对象,后面我们就都统一称为 vm ,你就会发现,data 里的数据在 vm 上面有两份,一份是直接在 vm 身上,另一份在 vm 身上的 _data 里,为什么会有两份呢,很简单,这两份其实是一份,真正的东西是在 _data 之中,而直接挂在 vm 身上的那份就是通过数据代理的方式放到 vm 身上的

​ ​ 那么到底什么叫数据代理?打个比方,我有一堆手办,但我懒得管理,就由你来代理,有人来问你手办价格,你过来问我,我告诉你,有人要买手办,你到我这来拿手办给人家,这就是数据代理。换句话说,就是数据都在我这,但由你来帮忙管理,别人要用数据,你来我这找数据,别人要改数据,你来我这改数据,我不亲自管理

​ ​ 那么 Vue 是怎么实现数据代理的呢?主要是一个函数 Object.defineProperty() ,这个函数可以给一个对象添加一个属性,它的其他用法这里就略过了,我们只需要知道,这个函数可以给对象添加属性的时候,再加上两个回调函数,一个叫 get ,一个叫 set ,get 函数会在这个新加的属性被访问的时候调用,比如输出新属性的值,set 则是在新的属性被改变的时候调用

​ ​ 通过这两个函数我们就可以实现上面所说的数据代理了,只要把 data 里的所有属性,都通过 Object.defineProperty() 放到 vm 身上就可以了,只需要在 get 函数里,返回 data 中对应属性的值, set 函数里修改 data 中对应属性的值即可

计算属性

在上一篇文章中提到过,Vue 的模板语法中,会把字符串当作 JavaScript 的表达式来处理,表达式自然可以有简单的也有复杂的,比如下面这个

<div id="example">
  {{ message.split('').reverse().join('') }}
</div>

这个很明显是一个翻转字符串的表达式,如果只用这么一个地方,那么这么写没什么问题,但如果在一个页面上,我们要用十个、甚至一百个,那么这个值每当 message 改变就会被计算十遍、一百遍,这显然是个极大的性能浪费

那么如果是平常我们写代码遇到这种情况会怎么做呢,很简单,找个变量把这个值存起来,每次 message 改变的时候,就改一下这个变量,其他地方只需要调用这个变量就可以了

所以 Vue 也提供了这么一个变量,就是所谓的计算属性,顾名思义,这个属性是通过其他属性计算而来的,只有当它用到的属性的变化时,它才会重新计算,其他时候,不过是使用它一次,还是一百次,都是不会重新计算,它会将上次计算的结果缓存下来

<div id="example">
  <p>Original message: "{{ message }}"</p>
  <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: 'Hello'
  },
  computed: {
    // 计算属性的 getter
    reversedMessage: function () {
      // `this` 指向 vm 实例
      return this.message.split('').reverse().join('')
    }
  }
})

普通的属性,我们就放在 data 里,而计算属性,则放在 computed 里,里面的计算属性,我们有两种形式,一种是函数式,也就是上面那种,另一种就是对象式,不过这个我们等会儿再说,在此之前要先引入一个问题,Vue 是怎么监视数据的?

答案就是上面刚提到过,get 和 set ,data 里的每个属性都有 get 和 set,只要一被修改,set 函数就会被调用,Vue 自然就会知道数据变了

那么回到计算属性,其实就是通过类似的方法来实现的,函数式的计算属性,其实就相当于是这个属性的 get 函数,只不过 Vue 比 Object.defineProperty() 高级的地方就是缓存了上次计算的值,如果计算属性用到的 data 里的那几个属性没有改动,就不会重新计算

那么你是不是想到了什么,没错,有 get 自然也可以有 set ,函数式的计算属性其实就是只有 get 的对象式计算属性的简写形式,如果我们需要用到 set ,也就是需要主动的修改某个计算属性的值,那么就需要写成对象的形式,虽然这种情况一般不多

<div id="demo">{{ fullName }}</div>
// ...
computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
// ...// ...
computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
// ...

侦听器

Vue 还有个类似计算属性的东西,叫做侦听器,或者说侦听属性,其被作为一种更通用的方式来观察和响应 Vue 实例也就是 vm 上的数据变动

首先我们要明确一点,计算属性所能做到到,侦听器都能做到,只不过可能更麻烦一点罢了,所以,一般来说,能用计算属性来实现的,尽量不要使用侦听器

var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
    firstName: function (val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName: function (val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
})

很明显,对于这个例子中,用 watch (侦听器比较难听,后面就直接叫 watch了)来实现计算属性的功能,就需要先设置一个属性 fullName,然后通过监听另外两个属性来修改 fullName,这比直接用计算属性绕了一大圈

在这个例子中 watch 相当于是 data 里的属性的 set 函数,而 computed 则是新加属性的 get 函数(当然它也可以加上 set)

刚刚我们提到过,computed 能做到的, watch 都能做到,那么自然有 watch 能做到而 computed 做不到的东西,比如 watch 可以实现异步操作,而 computed 不行,因为 computed 主要是要靠 get 函数的返回值来获取值,而返回值是无法异步的,我们显然没法让它过 2s 以后再把返回值 return

那么我们到底什么时候用 watch ,官网上是这么说的

当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

但除了这种情况,还有其他使用 watch 的情况吗,那么回到 computed 和 watch 本身,通过上面的对比,我们可以发现 computed 是新增加一个属性来代替原本复杂的表达式,侧重点在于你需要一个属性来表示一个或多个 data 里的数据所计算的值,比如我们需要一个全名来代替原来姓和名的组合,我们需要一个 flag来表示 color 是红的、shape 是圆的且 size 是大的,再或者我们需要一个按某些条件筛选后的列表,这些都很适合使用computed。watch的侧重点显然在于监听 data 里的某个属性,当它的值改变时,进行一些逻辑操作