标签:依赖 挂载 splay pac 比较 实例 属性 模式 false
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(); }, });
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(); } } }
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 通过 defineProperty
完成了 Data 中所有数据的代理,当数据触发 get 查询时,会将当前的 Watcher 对象加入到依赖收集池 Dep 中,当数据 Data 变化时,会触发 set 通知所有使用到这个 Data 的 Watcher 对象去 update 视图。
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,这里就不展开了。
Watcher 是在 Vue 初始化的阶段创建的,属于生命周期中 beforeMount 的位置创建的,创建 Watcher 时会执行 render 方法,最终将 Vue 代码渲染成真实的 DOM。
Vue初始化到渲染dom的过程:
当数据发生改变时,vue是怎样进行更新的?
由上图可见,在 Data 变化时,会调用 Dep.notify 方法,随即调用 Watcher 内部的 update 方法,此方法会将所有使用到这个 Data 的 Watcher 加入一个队列,并开启一个异步队列进行更新,最终执行_render
方法完成页面更新。
整体流程如下:
标签:依赖 挂载 splay pac 比较 实例 属性 模式 false
原文地址:https://www.cnblogs.com/pleaseAnswer/p/13564354.html