vue 源码分析

核心

Vue 响应式的核心是利用 Object.defineProperty()这个方法进行数据劫持和观察者模式进行数据响应式的。Object.defineProperty()这个方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。具体用法如下:

​ 它有三个参数,第一个是 object,它代表要定义属性的对象。;第二个是 prop,它代表目标对象的属性值;第三个参数是 descriptor,它代表要定义或修改的属性描述符。

​ 响应式用法:

1const person = {};
2Object.defineProperty(person, 'name', {
3  get: function () {
4    console.log('获取到name了');
5  },
6  set: function (val) {
7    console.log('设置了name为' + val);
8  },
9});
10person.name; //获取到name了
11person.name = 'h7ml'; //设置了name为h7ml

观察者模式

什么是观察者模式?它分为注册环节跟发布环节

比如我去买芝士蛋糕,但是店家还没有做出来。这时候我又不想在店外面傻傻等,我就需要隔一段时间来回来问问蛋糕做好没,对于我来说是很麻烦的事情,说不定我就懒得买了。

店家肯定想要做生意,不想流失我这个吃货客户。于是,在蛋糕没有做好的这段时间,有客户来,他们就让客户把自己的电话留下,这就是观察者模式中的注册环节。然后蛋糕做好之后,一次性通知所有记录了的客户,这就是观察者的发布环节

一个简单的观察者模式类:

1function Observer() {
2  this.dep = [];
3
4  register(fn) {
5    this.dep.push(fn)
6  }
7
8  notify() {
9    this.dep.forEach(item => item())
10  }
11}
12
13const wantCake = new Oberver();
14// 每来一个顾客就注册一个想执行的函数
15wantCake.register(() => {'console.log("call daisy")'})
16wantCake.register(() => {'console.log("call anny")'})
17wantCake.register(() => {'console.log("call sunny")'})
18
19// 最后蛋糕做好之后,通知所有的客户
20wantCake.notify()

初始化

vue 在初始化执行 initState 的时候,会对propsmethodsdatacomputedwathcer 等属性做初始化操作。

1export function initState(vm: Component) {
2  vm._watchers = [];
3  const opts = vm.$options;
4  if (opts.props) initProps(vm, opts.props);
5  if (opts.methods) initMethods(vm, opts.methods);
6  if (opts.data) {
7    initData(vm);
8  } else {
9    observe((vm._data = {}), true /* asRootData */);
10  }
11  if (opts.computed) initComputed(vm, opts.computed);
12  if (opts.watch && opts.watch !== nativeWatch) {
13    initWatch(vm, opts.watch);
14  }
15}

在初始化 data 的时候,一共做了两件事,一件是利用 proxy 将数据代理到整个 vue 实例上,另一件就是将 data 中的所有对象属性 reactive 化,变成响应式对象,为对象添加 getter 和 setter。

1/**
2 * Observer class that is attached to each observed
3 * object. Once attached, the observer converts the target
4 * object's property keys into getter/setters that
5 * collect dependencies and dispatch updates.
6 */
7export class Observer {
8  value: any;
9  dep: Dep;
10  vmCount: number; // number of vms that has this object as root $data
11
12  constructor(value: any) {
13    this.value = value;
14    this.dep = new Dep();
15    this.vmCount = 0;
16    def(value, '__ob__', this);
17    if (Array.isArray(value)) {
18      const augment = hasProto ? protoAugment : copyAugment;
19      augment(value, arrayMethods, arrayKeys);
20      this.observeArray(value);
21    } else {
22      this.walk(value);
23    }
24  }
25
26  /**
27   * Walk through each property and convert them into
28   * getter/setters. This method should only be called when
29   * value type is Object.
30   */
31
32  walk(obj: Object) {
33    const keys = Object.keys(obj);
34    for (let i = 0; i < keys.length; i++) {
35      defineReactive(obj, keys[i]);
36    }
37  }
38
39  /**
40   * Observe a list of Array items.
41   */
42  observeArray(items: Array<any>) {
43    for (let i = 0, l = items.length; i < l; i++) {
44      observe(items[i]);
45    }
46  }
47}

上面的这段代码,它将对象的所有属性进行遍历执行 defineReactive()方法进行响应式化

1walk (obj: Object) {
2    const keys = Object.keys(obj)
3    for (let i = 0; i < keys.length; i++) {
4      defineReactive(obj, keys[i])
5    }
6  }

在执行这个方法的过程中,会 new 一个 dep 实例,记得注意,后面会用到

1function defineReactive(obj: Object, key: string, ...) {
2    const dep = new Dep()
3
4    Object.defineProperty(obj, key, {
5      enumerable: true,
6      configurable: true,
7      get: function reactiveGetter () {
8        ....
9        dep.depend()
10        return value
11        ....
12      },
13      set: function reactiveSetter (newVal) {
14        ...
15        val = newVal
16        dep.notify()
17        ...
18      }
19    })
20  }
21
22  class Dep {
23      static target: ?Watcher;
24      subs: Array<Watcher>;
25
26      depend () {
27        if (Dep.target) {
28          Dep.target.addDep(this)
29        }
30      }
31
32      notify () {
33        const subs = this.subs.slice()
34        for (let i = 0, l = subs.length; i < l; i++) {
35          subs[i].update()
36        }
37      }

上述代码中的 Dep 类就是一个观察者类,每个对象属性都一个 dep 实例对象,在执行 get 的时候进行触发 depend 方法,触发 sit 的时候执行 notify 方法。

mount 阶段

在 vue 实例挂载阶段,会创建一个 Watcher 类的实例对象,这个 Watcher 实际上是连接 Vue 组件与 Dep(也就是视图更新环节)的桥梁。

1mountComponent(vm: Component, el: ?Element, ...) {
2    vm.$el = el
3
4    ...
5
6    updateComponent = () => {
7      vm._update(vm._render(), ...)
8    }
9
10    new Watcher(vm, updateComponent, ...)
11    ...
12}
13
14class Watcher {
15  getter: Function;
16
17  // 代码经过简化
18  constructor(vm: Component, expOrFn: string | Function, ...) {
19    ...
20    this.getter = expOrFn
21    Dep.target = this                      // 注意这里将当前的Watcher赋值给了Dep.target
22    this.value = this.getter.call(vm, vm)  // 调用组件的更新函数
23    ...
24  }
25}

在 rende()方法将模板渲染成虚拟 Vnode 的过程中会访问 data,从而触发属性的 getter,然后每个对象属性又有一个 dep 实例对象(上面提到的),然后再 getter 的逻辑中又会调用该 dep 的 depend 方法,将 watcher 实例 add 到 sub(存在 Dep 类中的存储 watcher 的数组)里面。在 depend 方法里面,Dep.target 就是 watcher 本身,在 Wacher 类中的构造函数会执行(上面代码有)。以上过程就叫依赖收集

派发更新

在对象属性的数据改变之后,会触发 sitter,从而执行 sitter 函数的逻辑,从而调用 dep 实例的 notify 方法,从而进行遍历调用 sub 中所有 watcher 的 upadte 方法进行视图更新。

总结

**第一步:**组件初始化的时候,先给每一个 Data 属性都注册 getter,setter,也就是 reactive 化。然后再 new 一个自己的 Watcher 对象,此时 watcher 会立即调用组件的 render 函数去生成虚拟 DOM。在调用 render 的时候,就会需要用到 data 的属性值,此时会触发 getter 函数,将当前的 Watcher 函数注册进 sub 里。

**第二步:**当 data 属性发生改变之后,会触发 sitter,遍历 sub 里所有的 watcher 对象,通知它们去重新渲染组件。

注意:vue 对数组响应式是通过变异方法完成的,而且如果要对响应式对象添加新的属性,必须要用$set 方法才能完成

参考文章:

vue 技术揭秘

最简化 VUE 的响应式原理