深入浅出 - vue之深入响应式原理
# 深入浅出 - vue之深入响应式原理

本文讲的内容是 vue 1.0 版本，同时为了阅读者的阅读心情，本文尽量做到不枯燥，特别适合那些想明白内部原理又讨厌看枯燥的源码的同学~

说到响应式原理其实就是`双向绑定`的实现，说到 `双向绑定` 其实有两个操作，数据变化修改dom，input等文本框修改值的时候修改数据

```
1. 数据变化 -> 修改dom
2. 通过表单修改value -> 修改数据
```

先说第一步，数据变化更改DOM的一个前提条件是能够知道数据什么时候变了，像这种需求如果不考虑兼容性的话，用屁股想都知道可以通过 `getter` 和 `setter` 来实现，每当触发 `setter` 的时候更新DOM

但这就引发了一个问题，我们怎么知道当 `setter` 触发的时候更新哪个DOM？

一个解决思路是，我们先知道哪些dom需要用到数据，当触发 `setter` 的时候把所有使用到该数据的dom更新

所以我们需要一个收集依赖关系的功能，每当触发 `getter` 的时候如果是 DOM 中触发的，我把这个 Key 和 DOM 记录起来，这样当这个 Key 触发 `setter` 的时候，我把这个 Key 所对应的所有 DOM都更新一遍，这样一个简单的单向绑定就实现了

下面说说第二步，其实第二步要比第一步简单的多，以input为例：

```javascript
<input v-model="name" />
```
很明显，我只需要使用 `getAttribute` 方法读取 `v-model` 拿到的值就是 Key，在通过 `input.value` 拿到 value，直接就可以用key和value把数据改了

<br />
<br />
<br />
<br />
<br />

一切看起来都是那么的美好...

<br />
<br />
<br />
<br />
<br />

可是...

<br />
<br />
<br />
<br />
<br />

如果像 vue 这样的一个能投入生产环境下使用，而非玩具的框架的实现要考虑的事情要比上面那个多的多，实现方式也要复杂的多。

<br />
<br />
<br />
<br />
<br />

下面看看vue的实现方式

## Data

<img src="http://p4.qhimg.com/t0197bc79fdc9fc04ef.png" width="100%"/>

上图是vue官方文档中的一张图片

可以看到最右侧绿色的圆代表数据，里面紫色的圆代表属性，属性被 `getter` 和 `setter` 拦截

有一条黑色虚线指向 `getter`，标注的英文是 Collect Dependencies，代表触发 `getter` 的时候收集依赖（其实就是把watcher实例推到依赖列表里）

`setter` 处有一条红线指向 Watcher，标注是 Notify，代表触发 `setter` 时，会发送消息到 Watcher

可以看到 `getter` 与 `setter` 的部分与我上面的猜测基本一致，但是多了个 `Watcher` 和 `Directive`，其实这就是我上面说到的，作为vue来讲，并不是简单的更新dom就可以了，vue中有很多指令，不同的指令有不同的更新DOM的方式而 `Directive` 就是用来处理这方面的事情用的

那中间那个 `Watcher` 是个什么鬼？

`Watcher` 可以先暂时理解为 `房产中介` 用户买房子找中介，中介帮忙找房主，房主卖房子找中介，中介帮房主把房子卖给用户。。。。。。。。。。。。。。

`setter` 触发消息到 `Watcher` watcher帮忙告诉 `Directive` 更新DOM，DOM中修改了数据也会通知给 `Watcher`，watcher 帮忙修改数据

关于Directive 和 Watcher 后面会细说

其实站在原理的角度上讲，上图中的内容是不全的，上图中是为了使用者更好的理解响应式画的图，而不是为了研究者画的图

<img src="https://dn-mhke0kuv.qbox.me/461c52afb53ae47b964a.jpg" width="100%">

这张图片是《Vue.js 权威指南》中源码篇的一个章节中画的图，专门画给研究者看的

可以看到 多了一个 `Dep` 和 `Observer`

下面我们就要说说 `Observer`、`Dep`、`Watcher`、`Directive` 他们之间的关系以及vue是如何通过他们实现的双向绑定

先说说 `Observer` 

`Observer`，正如它的名字，Observer就是观察者模式的实现，它用来观察数据的变化，触发消息。

`Observer`会观察两种类型的数据，`Object` 与 `Array`

对于Array类型的数据，会先重写操作数组的原型方法，重写后能达到两个目的，

1. 当数组发生变化时，触发 `notify`
2. 如果是 push，unshift，splice 这些添加新元素的操作，则会使用observer观察新添加的数据

重写完原型方法后，遍历拿到数组中的每个数据 使用observer观察它

而对于Object类型的数据，则遍历它的每个key，使用 defineProperty 设置 getter 和 setter，当触发getter的时候，observer则开始收集依赖，而触发setter的时候，observe则触发notify。

那怎么收集的依赖呢？

这个时候 `Dep` 改闪亮登场了。

**当数据的 getter 触发后，会收集依赖，但也不是所有的触发方式都会收集依赖，只有通过watcher 触发的 getter 会收集依赖，而所谓的被收集的依赖就是当前 watcher**

**这里需要特殊说一下，因为只有watcher触发的 getter 才会收集依赖，所以DOM中的数据必须通过watcher来绑定，就是说DOM中的数据必须通过watcher来读取！**

`Dep` 提供了一些方法，我先简单帖两个

```javascript
export default function Dep () {
  this.id = uid++
  this.subs = []
}

Dep.prototype.addSub = function (sub) {
  this.subs.push(sub)
}

Dep.prototype.notify = function () {
  // stablize the subscriber list first
  var subs = toArray(this.subs)
  for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
  }
}

...
```

可以看到其实挺简单的，当通过 watcher 触发 getter时，watcher会使用 `dep.addSub(this)` 把自己的实例推到 `subs` 中

当触发setter的时候，会触发notify，而notify则会把watcher的update方法执行一遍。

到这里 `observer` `dep` `watcher` 已经缕清了

那么watcher的update方法是如何配合Directive改变视图的呢？？

说到这里就要从编译模板的时候说起了。。。。。

## Directive

<img src="http://p2.qhimg.com/t01dbc007161ac62089.png" max-width="100%" />

看上图，我们上一节讲的是左边的那部分内容，这一节我们讲右边那部分内容

关于编译这块vue分了两种类型，一种是文本节点，一种是元素节点，如果以最简单的文本节点为例，首先需要知道什么是文本节点？

```javascript
hello {{name}}
```
这就是一个文本节点，它包含两部分，普通文本节点 `hello ` 和一个特殊的节点`{{name}}`


**第一步**

首先第一步vue会通过正则来解析文本节点，把普通文本节点和特殊节点区分开，解析后大概长下面这样

```javascript
[{
  value: 'hello '
}, {
  value: 'name',
  tag: true,
  html: false,
  oneTime: false
}]
```

**第二步**

第二步是遍历Array，将所有tag为true的添加**扩展对象**，**扩展属性包括指令方法**

像文本节点的特殊节点只有两种类型，text和html，所以简单判断html的值就可以知道，应该给扩展类型添加那种指令的接口

添加扩展对象后大概长成下面的样子

```javascript
[{
  value: 'hello '
}, {
  value: 'name',
  tag: true,
  html: false,
  oneTime: false,
  descriptor: {
    def: {
      update: function,
      bind: function
    },
    expression: xx,
    filters: xx,
    name: 'text'
  }
}]
```

<img src="http://p6.qhimg.com/t01e1764f5f6f1b7d34.jpg" width="250" />

可以看到vue内置了这么多的指令，这些指令都会抛出两个接口 bind 和 update，这两个接口的作用是，编译的最后一步是执行所有用到的指令的bind方法，而 update 方法则是当 watcher 触发 update 时，Directive会触发指令的update方法

**observe** -> 触发setter -> **watcher** -> 触发update -> **Directive** -> 触发update -> **指令**

**第三步**
第三步是将所有 `tag` 为 `true` 的数据中的扩展对象拿出来生成一个Directive实例并添加到 `_directives` 中（_directives是当前vm中存储所有directive实例的地方）。

```javascript
this._directives.push(
  new Directive(descriptor, this, node, host, scope, frag)
)
```

**第四步**

循环 `_directives` 执行所有 directive实例的 _bind 方法。

Directive 中 _bind 方法的作用有几点：

1. 调用所有已绑定的指令的 bind 方法
2. 实例化一个Watcher，将指令的update与watcher绑定在一起（**这样就实现了watcher接收到消息后触发的update方法，指令可以做出对应的更新视图操作**）
3. 调用指令的update，首次初始化视图

这里有一个点需要注意一下，实例化 `Watcher` 的时候，Watcher会将自己主动的推入Dep依赖中

好了，到这里整体的流程已经结束了，来一段总结吧

## 总结

响应式原理共有四个部分，`observe`、`Dep`、`watcher`、`Directive`

observer可以监听数据的变化

Dep 可以知道数据变化后通知给谁

Watcher 可以做到接收到通知后将执行指令的update操作

Directive 可以把 Watcher 和 指令 连在一起

不同的指令都会有update方法来使用自己的方式更新dom

必须使用watcher触发getter，Dep才会收集依赖

**执行流：**

当数据触发 setter 时，会发消息给所有watcher，watcher会跟执行指令的update方法来更新视图

当指令在页面上修改了数据会触发watcher的set方法来修改数据


您的赞助是我最大的动力~

<img src="http://p5.qhimg.com/t01ffe112b147b0dd0c.jpg" width="50%"><img src="http://p2.qhimg.com/t01aa1dd80487cdc962.jpg" width="50%">