vue响应式原理

vue的响应式是如何实现的?

Watcher ----- Dep ---- walk + defineProperty

1  vue 初始化 -- 进行数据的set、get绑定,并创建了一个Dep对象

// src > core > observer > index.js
// 执行 new Vue 时会依次执行以下方法 // 1. Vue.prototype._init(option) // 2. initState(vm) // 3. observe(vm._data) // 4. new Observer(data) // 5. 调用 walk 方法,遍历 data 中的每一个属性,监听数据的变化。
//
遍历所有属性并将它们转换为getter/setter。此方法仅在值类型为Object时调用。

function walk(obj: Object) {
 const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
    defineReactive(obj, keys[i]);
  }
}

// 6. 执行 defineProperty 监听数据读取和设置。
// 定义对象上的响应式属性
function defineReactive(obj, key, val) { // 为每个属性创建 Dep(依赖搜集的容器) const dep = new Dep(); // 绑定 get、set Object.defineProperty(obj, key, { get() { const value = val; // 如果有 target 标识,则进行依赖搜集 if (Dep.target) { dep.depend(); } return value; }, set(newVal) { val = newVal; // 修改数据时,通知页面重新渲染 dep.notify(); }, });

vue响应式原理

 Dep对象是什么?

1.2  Dep对象 -- 用于依赖收集,它实现了一个发布订阅模式,完成了数据Data和渲染视图 Watcher 的订阅

// src > core > observer > dep.js
class Dep {
// Dep.target 是一个 Watcher 类型。 static target: ?Watcher; // subs 存放搜集到的 Watcher 对象集合 subs: Array<Watcher>; constructor() { this.subs = []; } addSub(sub: Watcher) { // 搜集所有使用到这个 data 的 Watcher 对象。 this.subs.push(sub); } depend() { if (Dep.target) { // 搜集依赖,最终会调用上面的 addSub 方法 Dep.target.addDep(this); } } notify() { const subs = this.subs.slice(); for (let i = 0, l = subs.length; i < l; i++) { // 调用对应的 Watcher,更新视图 subs[i].update(); } } }

vue响应式原理

  Watch的功能是什么?

 1.3 Watch 观察者 -- 实现了渲染方法 _render 和 Dep 的关联, 初始化 Watcher 的时候,打上 Dep.target 标识,然后调用 get 方法进行页面渲染。

// src > core > observer > watcher.js
class Watcher { constructor(vm: Component, expOrFn: string
| Function) { // 将 vm._render 方法赋值给 getter。 // 这里的 expOrFn 其实就是 vm._render。 this.getter = expOrFn; this.value = this.get(); } get() { // 给 Dep.target 赋值为当前 Watcher 对象 Dep.target = this; // this.getter 其实就是 vm._render // vm._render 用来生成虚拟 dom、执行 dom-diff、更新真实 dom。 const value = this.getter.call(this.vm, this.vm); return value; } addDep(dep: Dep) { // 将当前的 Watcher 添加到 Dep 收集池中 dep.addSub(this); } update() { // 开启异步队列,批量更新 Watcher queueWatcher(this); } run() { // 和初始化一样,会调用 get 方法,更新视图 const value = this.get(); } }

vue响应式原理

 小结:Vue 通过 defineProperty 完成了 Data 中所有数据的代理,当数据触发 get 查询时,会将当前的 Watcher 对象加入到依赖收集池 Dep 中,当数据 Data 变化时,会触发 set 通知所有使用到这个 Data 的 Watcher 对象去 update 视图。

vue响应式原理

Watcher 是什么时候创建的?

 2 模板渲染

2.1 new Vue 执行流程

// src > core > instance > lifecycle.js
// 1. Vue.prototype._init(option)
// 2. vm.$mount(vm.$options.el) // 3. render = compileToFunctions(template) ,编译 Vue 中的 template 模板,生成 render 方法。 // 4. Vue.prototype.$mount 调用上面的 render 方法挂载 dom。 // 5. mountComponent // 6. 创建 Watcher 实例 const updateComponent = () => { vm._update(vm._render()); }; // 结合上文,我们就能得出,updateComponent 就是传入 Watcher 内部的 getter 方法。 new Watcher(vm, updateComponent); // 7. new Watcher 会执行 Watcher.get 方法 // 8. Watcher.get 会执行 this.getter.call(vm, vm) ,也就是执行 updateComponent 方法 // 9. updateComponent 会执行 vm._update(vm._render()) // 10. 调用 vm._render 生成虚拟 dom Vue.prototype._render = function (): VNode { const vm: Component = this; const { render } = vm.$options; let vnode = render.call(vm._renderProxy, vm.$createElement); return vnode; }; // 11. 调用 vm._update(vnode) 渲染虚拟 dom Vue.prototype._update = function (vnode: VNode) { const vm: Component = this; if (!prevVnode) { // 初次渲染 vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false); } else { // 更新 vm.$el = vm.__patch__(prevVnode, vnode); } }; // 12. vm.__patch__ 方法就是做的 dom diff 比较,然后更新 dom,这里就不展开了。

vue响应式原理

 Watcher 是在 Vue 初始化的阶段创建的,属于生命周期中 beforeMount 的位置创建的,创建 Watcher 时会执行 render 方法,最终将 Vue 代码渲染成真实的 DOM。

Vue初始化到渲染dom的过程:

vue响应式原理

 当数据发生改变时,vue是怎样进行更新的?

由上图可见,在 Data 变化时,会调用 Dep.notify 方法,随即调用 Watcher 内部的 update 方法,此方法会将所有使用到这个 Data 的 Watcher 加入一个队列,并开启一个异步队列进行更新,最终执行 _render 方法完成页面更新。

整体流程如下:

vue响应式原理

 总结:Vue的响应式原理

  1. 从 new Vue 开始,首先通过 get、set 监听 Data 中的数据变化,同时创建 Dep 用来搜集使用该 Data 的 Watcher。
  2. 编译模板,创建 Watcher,并将 Dep.target 标识为当前 Watcher。
  3. 编译模板时,如果使用到了 Data 中的数据,就会触发 Data 的 get 方法,然后调用 Dep.addSub 将 Watcher 搜集起来。
  4. 数据更新时,会触发 Data 的 set 方法,然后调用 Dep.notify 通知所有使用到该 Data 的 Watcher 去更新 DOM。

vue响应式原理