关于前端:Vue2源码响应式原理浅析

5次阅读

共计 22787 个字符,预计需要花费 57 分钟才能阅读完成。

本文基于 Vue 2.6.14 进行源码剖析
为了减少可读性,会对源码进行删减、调整程序、扭转的操作,文中所有源码均可视作为伪代码

文章内容

  1. 响应式原理相干 function 和 class 的解说
  2. Object 数据类型的响应式初始化和非凡更新模式
  3. Array 数据类型的响应式初始化和非凡更新模式
  4. 渲染 Watcher 的依赖收集和派发更新剖析:流程图
  5. computed 类型的依赖收集和派发更新剖析:流程图和源码剖析
  6. watch 类型的的依赖收集和派发更新剖析:源码剖析

响应式原理初始化

响应式数据设置代理

  • 拜访 props 的 item 对应的 key 时,应用 this.[key] 会主动代理到vm._props.[key]
  • 拜访 data 的 item 对应的 key1 时,应用 this.[key1] 会主动代理到vm._data.[key1]
function initProps(vm: Component, propsOptions: Object) {for (const key in propsOptions) {if (!(key in vm)) {proxy(vm, `_props`, key)
        }
    }
}
function initData(vm: Component) {
    let data = vm.$options.data
    data = vm._data = typeof data === 'function'
        ? getData(data, vm)
        : data || {};
    const keys = Object.keys(data)
    const props = vm.$options.props
    const methods = vm.$options.methods
    let i = keys.length
    while (i--) {const key = keys[i]
        // 监测 props 是否曾经有这个 key 了,有的话弹出正告
        proxy(vm, `_data`, key)
    }
}
export function proxy(target: Object, sourceKey: string, key: string) {sharedPropertyDefinition.get = function proxyGetter() {return this[sourceKey][key]
    }
    sharedPropertyDefinition.set = function proxySetter(val) {this[sourceKey][key] = val
    }
    Object.defineProperty(target, key, sharedPropertyDefinition)
}

Vue.props 响应式数据设置

在合并配置 mergeOptions()中,会调用 normalizeProps()对 props 的数据进行整顿,最终确保 initPros 调用时 props 曾经是一个对象,因而不须要 Observer 判断是否是数组,间接对 key 进行 defineReactive 即可

function initProps(vm: Component, propsOptions: Object) {const propsData = vm.$options.propsData || {}
    const props = vm._props = {}
    const keys = vm.$options._propKeys = []

    for (const key in propsOptions) {keys.push(key)
        const value = validateProp(key, propsOptions, propsData, vm)
        defineReactive(props, key, value)
    }
}

Vue.data 响应式数据设置

  • data 建设一个 Observer,次要性能是依据 value 类型判断,是数组则递归调用observe,为每一个 item 都创立一个Observer 对象,如果是对象,则遍历 key,为每一个key 都创立响应式监听
function initData(vm: Component) {
    let data = vm.$options.data
    data = vm._data = typeof data === 'function'
        ? getData(data, vm)
        : data || {}
    // observe data
    observe(data, true /* asRootData */)
}

export function observe(value: any, asRootData: ?boolean): Observer | void {if (!isObject(value) || value instanceof VNode) {return}
    // ... 判断数据 value 是否曾经设置响应式过
    let ob = new Observer(value)
    return ob
}
export class Observer {
    value: any;
    dep: Dep;
    vmCount: number; // number of vms that have this object as root $data

    constructor(value: any) {
        this.value = value
        this.dep = new Dep()
        if (Array.isArray(value)) {this.observeArray(value)
        } else {this.walk(value)
        }
    }

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

    observeArray(items: Array<any>) {for (let i = 0, l = items.length; i < l; i++) {observe(items[i])
        }
    }
}

Object.defineProperty 响应式根底办法

  • get:返回对应 key 的数据 + 依赖收集
  • set:设置对应 key 的数据 + 派发更新
export function defineReactive(obj: Object, key: string, val: any, ...args) {const dep = new Dep()
    let childOb = !shallow && observe(val) // 如果 val 也是 object

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {const value = getter ? getter.call(obj) : val
            if (Dep.target) {dep.depend()
                if (childOb) {
                    // key 对应的 val 是 Object, 当 val 外面的 key 产生扭转时
                    // 即 obj[key][key1]=xxx
                    // 也会告诉目前 obj[key]收集的 Watcher 的更新
                    childOb.dep.depend()
                    if (Array.isArray(value)) {dependArray(value)
                    }
                }
            }
            return value
        },
        set: function reactiveSetter(newVal) {const value = getter ? getter.call(obj) : val
            if (newVal === value || (newVal !== newVal && value !== value)) {return}
            if (setter) {setter.call(obj, newVal)
            } else {val = newVal}
            childOb = !shallow && observe(newVal)
            dep.notify()}
    })
}

Dep 响应式依赖治理类

  • 每一个 key 都有一个 Dep 治理类
  • Dep具备 addSub,即关联Watcher(渲染 Watcher 或者其它) 的能力
  • Dep具备 depend(),被Watcher 显式关联,能够被 Watcher 触发 dep.notify() 告诉它关联 Watcher 更新的能力
Dep.target = null
const targetStack = []
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []}

  addSub (sub: Watcher) {this.subs.push(sub)
  }

  removeSub (sub: Watcher) {remove(this.subs, sub)
  }

  depend () {if (Dep.target) {Dep.target.addDep(this)
    }
  }

  notify () {const subs = this.subs.slice()
    if (process.env.NODE_ENV !== 'production' && !config.async) {subs.sort((a, b) => a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {subs[i].update()}
  }
}

Watcher 响应式依赖收集和派发更新执行类

  • get()办法进行 pushTarget(this),触发对应的getter 回调,开始收集,而后 popTarget(this),进行收集,最初触发cleanupDeps() 进行依赖的更新
  • update()将更新内容压入队列中,而后依据顺序调用 Watcher.run(),也就是回调constructor() 传进来的 this.cb 办法
export default class Watcher {constructor(...args) {
        this.vm = vm
        if (isRenderWatcher) {vm._watcher = this}
        vm._watchers.push(this)
        this.cb = cb; // 触发更新时调用的办法
        this.deps = []
        this.newDeps = []
        this.depIds = new Set()
        this.newDepIds = new Set()
        this.value = this.lazy
            ? undefined
            : this.get()}

    get() {pushTarget(this)
        let value
        const vm = this.vm
        value = this.getter.call(vm, vm)
        if (this.deep) {traverse(value)
        }
        popTarget()
        this.cleanupDeps()
        return value
    }

    addDep(dep: Dep) {
        const id = dep.id
        if (!this.newDepIds.has(id)) {this.newDepIds.add(id)
            this.newDeps.push(dep)
            if (!this.depIds.has(id)) {dep.addSub(this)
            }
        }
    }

    cleanupDeps() {
        let i = this.deps.length
        while (i--) {const dep = this.deps[i]
            if (!this.newDepIds.has(dep.id)) {dep.removeSub(this)
            }
        }
        let tmp = this.depIds
        this.depIds = this.newDepIds
        this.newDepIds = tmp
        this.newDepIds.clear()
        tmp = this.deps
        this.deps = this.newDeps
        this.newDeps = tmp
        this.newDeps.length = 0
    }


    update() {if (this.lazy) {this.dirty = true} else if (this.sync) {this.run()
        } else {queueWatcher(this)
        }
    }

   
    run() {if (this.active) {const value = this.get()
            if (value !== this.value || isObject(value) || this.deep) {
                const oldValue = this.value
                this.value = value
                if (this.user) {const info = `callback for watcher "${this.expression}"`
                    invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
                } else {this.cb.call(this.vm, value, oldValue)
                }
            }
        }
    }

    depend() {
        let i = this.deps.length
        while (i--) {this.deps[i].depend()}
    }
}

Object 数据类型响应式

最外一层 key 的响应式设置

应用 observe() 对每一个 Objectkey都进行 Object.defineProperty() 劫持

function observe(value, asRootData) {ob = new Observer(value);
    return ob
}

var Observer = function Observer(value) {
    this.value = value;
    this.dep = new Dep();
    this.vmCount = 0;
    def(value, '__ob__', this);
    this.walk(value);
};
walk (obj: Object) {const keys = Object.keys(obj)
  for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i])
  }
}
export function defineReactive(obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean) {if ((!getter || setter) && arguments.length === 2) {val = obj[key]
    }
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {const value = getter ? getter.call(obj) : val
            if (Dep.target) {dep.depend()
            }
            return value
        },
        set: function reactiveSetter(newVal) {if (setter) {setter.call(obj, newVal)
            } else {val = newVal}
            dep.notify()}
    })
}

深度 key 的响应式设置

export function defineReactive(obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean) {const dep = new Dep()
    let childOb = !shallow && observe(val)
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {const value = getter ? getter.call(obj) : val
            if (Dep.target) {dep.depend()
                if (childOb) {childOb.dep.depend()
                    if (Array.isArray(value)) {dependArray(value)
                    }
                }
            }
            return value
        },
        set: function reactiveSetter(newVal) {if (setter) {setter.call(obj, newVal)
            } else {val = newVal}
            childOb = !shallow && observe(newVal)
            dep.notify()}
    })
}
  • 由上面对 observe() 办法的剖析,它会遍历 Object 的每一个 key,进行Object.defineProperty 申明
  • 对于 Object 每一个 key 对应的 value,如果childOb = !shallow && observe(val) 不为空,那么它会遍历 value 对应的每一个 key,如果value[key] 也是一个 Object,那么会再次走到childOb = !shallow && observe(val),直到所有Object 都为响应式数据为止
  • 对于 obj[key] 来说,会调用 dep.depend(),如果obj[key] 自身也是一个对象,即 childOb 不为空,那么它就会调用 childOb.dep.depend(),因而当obj[key][key1]=xx 时,也会触发 dep.depend() 收集的 Watcher 产生更新,例如
data: {
  parent: {children: {test: "111"}
  }
}


<div>{{parent.children}}</div>

由下面的剖析能够晓得,当 this.parent.children.test 发生变化时,会触发 this.parent.children 收集的 渲染 Watcher发生变化,从而触发界面从新渲染

额定增加 key

因为 Object.defineProperty() 的限度,无奈实现对 Object 新增 key 的响应式监听,因而当咱们想要为 Object 设置新的 key 的时候,须要调用 Vue.set 办法

export function set(target: Array<any> | Object, key: any, val: any): any {if (key in target && !(key in Object.prototype)) {target[key] = val;
        return val;
    }
    const ob = (target: any).__ob__;
    if (!ob) {target[key] = val;
        return val;
    }
    defineReactive(ob.value, key, val);
    ob.dep.notify();
    return val;
}

Vue.set()的流程能够总结为:

  • Object 减少对应的 keyvalue数据
  • 将新增的 key 退出响应式监听中,如果 key 对应的 value 也是 Object,依照下面深度 key 的监听设置剖析,会递归调用observe 进行深度 key 的响应式设置
  • 手动触发 Object 收集的 Watcher 的刷新操作

    实质上,下面的三步流程除了第二步有稍微差异之外,其它局部跟 defineReactive 中的 set()办法流程统一

删除 key

删除 key 也无奈触发响应式的变动,须要手动调用 Vue.del() 办法:

  • 删除 Object 指定的key
  • 手动触发 Object 收集的 Watcher 的刷新操作
function del(target: Array<any> | Object, key: any) {if (Array.isArray(target) && isValidArrayIndex(key)) {target.splice(key, 1)
        return
    }
    const ob = (target: any).__ob__
    if (!hasOwn(target, key)) {return}
    delete target[key]
    if (!ob) {return}
    ob.dep.notify()}

Array 数据类型响应式

前置阐明

依据官网文档阐明,Vue 不能检测以下数组的变动

  • 当你利用索引间接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  • 当你批改数组的长度时,例如:vm.items.length = newLength

举个例子:

var vm = new Vue({
  data: {items: ['a', 'b', 'c']
  }
})
vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的

为了解决第一类问题,以下两种形式都能够实现和 vm.items[indexOfItem] = newValue 雷同的成果,同时也将在响应式零碎内触发状态更新

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)

// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

为了解决第二类问题,你能够应用 splice:

vm.items.splice(newLength)

对 Array[index]数据的响应式监听

如果 item=Array[index]Object数据,应用 observe()Array的每一个 item 都进行响应式的申明

function observe(value, asRootData) {ob = new Observer(value);
    return ob
}

var Observer = function Observer(value) {
    this.value = value;
    this.dep = new Dep();
    this.vmCount = 0;
    def(value, '__ob__', this);
    if (Array.isArray(value)) {if (hasProto) {protoAugment(value, arrayMethods)
        } else {copyAugment(value, arrayMethods, arrayKeys)
        }
        this.observeArray(value)
    }
};

observeArray(items: Array < any >) {for (let i = 0, l = items.length; i < l; i++) {observe(items[i])
    }
}

Vue.set 更新 Array-item

从上面代码能够看出,Vue.set()更新数组的 item 实质上也是调用 Array.splice() 办法

export function set(target: Array<any> | Object, key: any, val: any): any {if (Array.isArray(target) && isValidArrayIndex(key)) {target.length = Math.max(target.length, key)
        target.splice(key, 1, val)
        return val
    }
}

Array.splice 更新 Array-item

从下面的剖析能够晓得,一开始会触发 new Observer(value) 的初始化
从上面代码能够晓得,大部分浏览器会触发 protoAugment() 办法,也就是扭转Array.__proto__

var Observer = function Observer(value) {
    this.value = value;
    this.dep = new Dep();
    this.vmCount = 0;
    def(value, '__ob__', this);
    if (Array.isArray(value)) {if (hasProto) {protoAugment(value, arrayMethods)
        } else {copyAugment(value, arrayMethods, arrayKeys)
        }
        this.observeArray(value)
    }
};

function protoAugment (target, src: Object) {target.__proto__ = src}
// node_modules/vue/src/core/observer/array.js
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

而扭转了 Array.__proto__ 多少办法呢?

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
methodsToPatch.forEach(function (method) {const original = arrayProto[method]
    def(arrayMethods, method, function mutator(...args) {const result = original.apply(this, args)
        const ob = this.__ob__
        let inserted
        switch (method) {
            case 'push':
            case 'unshift':
                inserted = args
                break
            case 'splice':
                inserted = args.slice(2)
                break
        }
        if (inserted) ob.observeArray(inserted)
        // notify change
        ob.dep.notify()
        return result
    })
})
// node_modules/vue/src/core/util/lang.js
export function def(obj: Object, key: string, val: any, enumerable?: boolean) {
    Object.defineProperty(obj, key, {
        value: val,
        enumerable: !!enumerable,
        writable: true,
        configurable: true
    })
}

从下面代码剖析能够晓得,Vue劫持了 Array'push','pop','shift', 'unshift', 'splice', 'sort','reverse'办法,一旦运行了这些办法,会被动触发:

  • 调用 Array 原来的办法进行调用,而后返回 Array 原来的办法的返回值,如 Array.push 调用后的返回值
  • 进行 observeArray 的响应式设置,更新新设置的item(可能为Object,须要设置响应式)
  • 手动触发 ob.dep.notify(),触发对应的Watcher 更新,达到响应式自动更新的目标

渲染 Watcher 依赖收集流程剖析

仅仅剖析最简略的渲染 Watcher 依赖收集的流程,实际上并不是只有渲染 Watcher 一种

渲染 Watcher 派发更新流程剖析

computed 依赖收集和派发更新剖析

测试代码

<div>{{myName}}</div>

// {[key: string]: Function | {get: Function, set: Function} }
computed: {myName: function() {// 没有 set()办法,只有 get()办法
    return this.firstName + this.lastName;
  }
}

依赖收集流程图剖析

依赖收集代码剖析

computedWatcher 初始化

Vue.prototype._init初始化时,会调用 initState()->initComputed(),从而进行computed 数据的初始化

// node_modules/vue/src/core/instance/state.js
function initComputed(vm: Component, computed: Object) {const watchers = vm._computedWatchers = Object.create(null)

    for (const key in computed) {const userDef = computed[key];
        const getter = typeof userDef === 'function' ? userDef : userDef.get;
        watchers[key] = new Watcher(
            vm,
            getter || noop,
            noop,
            computedWatcherOptions //{lazy: true}
        )
        defineComputed(vm, key, userDef);
    }
}

从下面代码能够晓得,最终为每一个 computed 监听的数据建设一个 Watcher,一个数据对应一个computed Watcher,传入{lazy: true},而后调用defineComputed() 办法

export function defineComputed(target: any, key: string, userDef: Object | Function) {
    // 为了缩小分支判断,不便了解,对立假如 userDef 传入 Function
    sharedPropertyDefinition.get = createComputedGetter(key);
    sharedPropertyDefinition.set = noop;
    Object.defineProperty(target, key, sharedPropertyDefinition)
}

function createComputedGetter(key) {return function computedGetter() {const watcher = this._computedWatchers && this._computedWatchers[key]
        if (watcher) {if (watcher.dirty) {watcher.evaluate()
            }
            if (Dep.target) {watcher.depend()
            }
            return watcher.value
        }
    }
}

从下面代码能够晓得,最终 defineComputed 是进行了 Object.defineProperty 的数据劫持,个别在 computed 中都只写 get() 办法,即

computed: {myName: function() {// 没有 set()办法,只有 get()办法
    return this.firstName + this.lastName;
  }
}

而回到下面代码的剖析,defineComputed劫持了 computedget()办法,最终返回watcher.value

渲染 Watcher 触发 ComputedWatcher 的 get()办法执行

当界面上 <template>{myName}</template> 渲染 myName 的时候,会触发 myNameget()办法,因为 Object.defineProperty 的数据劫持,会先调用

  • watcher.evaluate()->watcher.get()(从上面的代码能够得出这样的推导关系)
  • watcher.depend()

    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {if (watcher.dirty) {// evaluate () {//     this.value = this.get()
          //     this.dirty = false
          // }
          watcher.evaluate()}
      if (Dep.target) {// depend() {
          //     let i = this.deps.length
          //     while (i--) {//         this.deps[i].depend()
          //     }
          // }
          watcher.depend()}
      return watcher.value
    }
    // watcher.js
    get() {//  function pushTarget (target: ?Watcher) {//    targetStack.push(target)
     //    Dep.target = target
          // }
      pushTarget(this);
      let value;
      const vm = this.vm;
      try {
          // this.getter = return this.firstName + this.lastName;
          value = this.getter.call(vm, vm);
      } catch (e) {} 
      finally {if (this.deep) { // watch 类型的 watcher 能力配置这个参数
              traverse(value);
          }
          popTarget();
          this.cleanupDeps();}
      return value;
    }

    从下面的代码能够晓得,当调用 watcher.evaluate()->watcher.get() 的时候,会调用:

  • pushTarget(this):将目前的 Dep.target 切换到Computed Watcher
  • this.getter.call(vm, vm):触发 this.firstName 对应的 get() 办法和 this.lastName 对应的 get() 办法。由上面的依赖收集代码能够晓得,此时 this.firstNamethis.lastName持有的 Dep 会进行dep.addSub(this),收集该Computed Watcher
  • popTarget():将目前的 Dep.target 复原到上一个状态
  • cleanupDeps():更新 Computed Watcher 的所有依赖关系,将有效的依赖关系删除 (比方v-if 造成的依赖关系不必再依赖)
  • 最终返回myName= return this.firstName + this.lastName;

    watcher.evaluate():求值 + 更新依赖 + 将波及到的响应式对象 firstName 和 lastName 关联到 Computed Watcher

export function defineReactive(obj: Object, key: string, val: any, ...args) {const dep = new Dep()
    let childOb = !shallow && observe(val)

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {const value = getter ? getter.call(obj) : val
            if (Dep.target) {dep.depend()
                if (childOb) {childOb.dep.depend()
                    if (Array.isArray(value)) {dependArray(value)
                    }
                }
            }
            return value
        }
    })
}

// Dep.js
depend () {if (Dep.target) {Dep.target.addDep(this)
  }
}

// watcher.js
addDep(dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {this.newDepIds.add(id)
        this.newDeps.push(dep)
        if (!this.depIds.has(id)) {dep.addSub(this)
        }
    }
}

回到 myNameget()办法,即上面的代码,咱们刚刚剖析了 watcher.evaluate(),那么咱们接下来还调用了myNamewatcher.depend()
咱们从下面的代码晓得,这个办法次要是用来收集依赖的,此时的 Dep.target渲染 Watchercomputed Watcher会进行本身的 depend(),实质是拿出本人所有记录的Dep(为了不便了解,咱们了解 Dep 就是一个响应式对象的代理)computed Watcher 拿出本人记录的所有的 deps[i],而后调用它们的depend() 办法,从而实现这些响应式对象 (firstNamelastName)与 渲染 Watcher的关联,最初返回watcher.value

const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {if (watcher.dirty) {// 下面剖析触发了 watcher.get()办法
        // 失去对应的 watcher.value
        // 收集了 firstName+lastName 和 computerWatcher 的绑定
        watcher.evaluate();
        // 将目前的 Dep.target 切换到渲染 Watcher
    }
    if (Dep.target) {// depend() {
        //     let i = this.deps.length
        //     while (i--) {//         this.deps[i].depend()
        //     }
        // }
        watcher.depend()}
    return watcher.value
}

// watcher.js
depend() {// this.deps 是从 cleanupDeps()中
    // this.deps = this.newDeps 来的
    // this.newDeps 是通过 addDep()来的
    let i = this.deps.length
    while (i--) {this.deps[i].depend()}
}

// Dep.js
depend() {if (Dep.target) {Dep.target.addDep(this)
    }
}

派发更新流程图剖析

派发更新代码剖析

computed: {myName: function() {// 没有 set()办法,只有 get()办法
    return this.firstName + this.lastName;
  }
}

this.firstName 产生扭转时,会触发 this.firstName.dep.subs.notify() 性能,也就是触发刚刚注册的两个 Watcher: 渲染 WatcherComputed Watcher,首先触发的是Computed Watchernotify()办法,由上面的代码能够晓得,只执行this.dirty=true

  update () {
    // Computed Watcher 的 this.lazy 都为 true
    if (this.lazy) {this.dirty = true} else if (this.sync) {this.run()
    } else {queueWatcher(this)
    }
  }

而后触发 渲染 Watcher,触发整个界面进行渲染,从而触发该 computed[key]get()办法执行,也就是 myNameget()办法执行,由依赖收集的代码能够晓得,最终执行为

const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {if (watcher.dirty) {// 下面剖析触发了 watcher.get()办法
        // 失去对应的 watcher.value
        watcher.evaluate();}
    if (Dep.target) {// depend() {
        //     let i = this.deps.length
        //     while (i--) {//         this.deps[i].depend()
        //     }
        // }
        watcher.depend()}
    return watcher.value
}

从下面的剖析能够晓得,computed[key]get() 先收集了一波依赖:

  • watcher.evaluate():求值 watcher.value + 更新依赖 + 将波及到的响应式对象关联到Computed Watcher
  • watcher.depend():将波及到的响应式对象关联到以后的 Dep.target,即 渲染 Watcher

而后返回了对应的值watcher.value

computedWatcher 个别无 set 办法,因而触发派发更新就是触发渲染 Watcher/ 其它 Watcher 持有 computed 进行从新渲染,从而触发 computed 的 get 办法,收集最新依赖以及获取最新值

watch 依赖收集和派发更新剖析

watch 流程图跟 computed 流程大同小异,因而 watch 只做源码剖析

测试代码

watch反对多种模式的监听形式,比方传入一个回调函数,比方传入一个办法名称,比方传入一个Object,配置参数

// {[key: string]: string | Function | Object | Array }
watch: {a: function (val, oldVal) {},
    b: 'someMethod', // 办法名
    c: {handler: function (val, oldVal) {}, // 值扭转时的回调办法
      deep: true, // 深度遍历
      immediate: true // 马上回调一次
    },
    // 你能够传入回调数组,它们会被逐个调用
    e: [
      'handle1', // 形式 1
      function handle2 (val, oldVal) {}, // 形式 2
            { // 形式 3
        handler: function (val, oldVal) {},
        deep: true,
        immediate: true
        },
    ],
    // watch vm.e.f's value: {g: 5}'e.f': function (val, oldVal) {}}

初始化 watch

export function initState(vm: Component) {if (opts.watch && opts.watch !== nativeWatch) {initWatch(vm, opts.watch);
    }
}
function initWatch(vm: Component, watch: Object) {for (const key in watch) {const handler = watch[key];
        // 解决 watch:{b: [三种模式都容许]}的模式
        if (Array.isArray(handler)) {for (let i = 0; i < handler.length; i++) {createWatcher(vm, key, handler[i]);
            }
        } else {createWatcher(vm, key, handler);
        }
    }
}
function createWatcher(vm: Component, expOrFn: string | Function, handler: any, options?: Object) {if (isPlainObject(handler)) {// 解决 watch:{b: {handler: 处理函数, deep: true, immediate: true}}的模式
        options = handler
        handler = handler.handler
    }
    if (typeof handler === 'string') {// 解决 watch: {b: 'someMethod'}的模式
        handler = vm[handler]
    }
    return vm.$watch(expOrFn, handler, options)
}

从下面的代码能够看出,初始化时,会进行 watch 中各种参数的解决,将 3 种不同类型的 watch 回调模式整顿成为标准的模式,最终调用 Vue.prototype.$watch 进行 new Watcher 的构建

Vue.prototype.$watch = function (expOrFn: string | Function, cb: any, options?: Object): Function {
    const vm: Component = this
    // cb 是回调办法,如果还是对象,则应用 createWatcher 拆出来外面的对象
    if (isPlainObject(cb)) {return createWatcher(vm, expOrFn, cb, options)
    }
    options.user = true
    // 建设一个 watch 类型的 Watcher
    // expOrFn: getter
    // cb: 注册的回调
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {// options={immediate:true}的分支逻辑
        pushTarget()
        invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
        popTarget()}
    return function unwatchFn() {watcher.teardown()
    }
}

依赖收集代码剖析

新建 Watcher 的时候,在 constructor() 中会触发

class watcher {constructor() {
    // watch 的 key
    this.getter = parsePath(expOrFn);
    this.value = this.lazy?undefined:this.get();}

const bailRE = new RegExp(`[^${unicodeRegExp.source}.$_\\d]`)
export function parsePath (path: string): any {if (bailRE.test(path)) {return}
  const segments = path.split('.')
  return function (obj) {for (let i = 0; i < segments.length; i++) {if (!obj) return
      obj = obj[segments[i]]
    }
    return obj
  }
}

从下面的代码能够晓得,最终 this.getter 调用的还是传入的 obj[key],从上面的get() 办法能够晓得,赋值 this.getter 后,会触发 get() 办法,从而触发 this.getter.call(vm, vm),因而最终this.getter 失去的就是vm[key]

get() {pushTarget(this)
    let value
    const vm = this.vm
    value = this.getter.call(vm, vm)
    if (this.deep) {traverse(value); // 深度遍历数组 / 对象,实现
    }
    popTarget()
    this.cleanupDeps()
    return value
}

// traverse.js
export function traverse (val: any) {_traverse(val, seenObjects)
  seenObjects.clear()}

function _traverse (val: any, seen: SimpleSet) {
  let i, keys
  const isA = Array.isArray(val)
  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {return}
  if (val.__ob__) {
    const depId = val.__ob__.dep.id
    if (seen.has(depId)) {return}
    seen.add(depId)
  }
  if (isA) {
    i = val.length
    while (i--) _traverse(val[i], seen)
  } else {keys = Object.keys(val)
    i = keys.length
    while (i--) _traverse(val[keys[i]], seen)
  }
}

下面代码的步骤能够概括为

  • pushTarget:修复以后的 Dep.target 为以后的watch 类型的 Watcher
  • this.getter:返回以后的 vm[key],同时触发vm[key] 的响应式劫持 get() 办法,从而触发 vm[key] 持有的 Dep 对象启动 dep.depend() 进行依赖收集 (如上面代码所示),vm[key] 持有的 Dep 对象将以后的 watch 类型的 Watcher 收集到 vm[key] 中,下次 vm[key] 发生变化时,会触发 watch 类型的 Watcher 进行callback 的回调
  • traverse(value):深度遍历,会拜访每一个 Object 的 key,因为每一个 Object 的 key 之前在 initState()的时候曾经应用 Object.defineProperty() 进行 get 办法的劫持,因而触发它们对应的 getter 办法,进行 dep.depend() 收集以后的 watch 类型的 Watcher,从而实现扭转 Object 外部深层的某一个 key 的时候会回调watch 类型的 Watcher。没有加deep=true 的时候,watch 类型的 Watcher只能监听 Object 的扭转,比方 watch:{curData: function(){}},只有 this.curData=xxx,才会触发 watch,this.curData.children=xxx 是不会触发的
  • popTarget:复原 Dep.target 为上一个状态
  • cleanupDeps:更新依赖关系
  • 返回值 value,依赖收集完结,watch 类型的 Watcher初始化完结
Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {const value = getter ? getter.call(obj) : val
        if (Dep.target) {dep.depend()
            if (childOb) {childOb.dep.depend()
                if (Array.isArray(value)) {dependArray(value)
                }
            }
        }
        return value
    }
})

派发更新代码剖析

watcher 的值产生扭转时,会触发 dep.subs.notify() 办法,从下面的剖析能够晓得,最终会调用 watcher.run() 办法

run() {if (this.active) {const value = this.get()
        if (
            value !== this.value ||
            isObject(value) ||
            this.deep
        ) {
            // set new value
            const oldValue = this.value
            this.value = value
            if (this.user) {const info = `callback for watcher "${this.expression}"`
                invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
            } else {this.cb.call(this.vm, value, oldValue)
            }
        }
    }
}

因为 watch 类型的 Watcher 传入了this.user=true,因而会触发invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info),将新值和旧值一起回调,比方

watch: {myObject: function(value, oldValue) {// 新值和旧值}
}

watchOptions 几种模式分析

deep=true

// watcher.js
get() {pushTarget(this)
    let value
    const vm = this.vm
    try {value = this.getter.call(vm, vm)
    } catch (e) {if (this.user) {handleError(e, vm, `getter for watcher "${this.expression}"`)
        } else {throw e}
    } finally {
        // "touch" every property so they are all tracked as
        // dependencies for deep watching
        if (this.deep) {traverse(value)
        }
        popTarget()
        this.cleanupDeps()}
    return value
}

get() 办法中进行对象的深度 key 的遍历,触发它们的 getter() 办法,进行依赖的收集,能够实现

watch: {
  myObject: {
    deep: true,
    handler: function(value, oldValue) {// 新值和旧值}
  }
}

this.myObject.a = 2;

尽管下面的例子只是监听了 myObject,然而因为退出deep=true,因而this.myObject.a 也会触发watcher.run(),如上面代码所示,因为this.deep=true,因而会回调cb(value, oldValue)

run() {if (this.active) {const value = this.get()
        if (
            value !== this.value ||
            isObject(value) ||
            this.deep
        ) {
            // set new value
            const oldValue = this.value
            this.value = value
            if (this.user) {const info = `callback for watcher "${this.expression}"`
                invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
            } else {this.cb.call(this.vm, value, oldValue)
            }
        }
    }
}

immediate=true

从上面代码能够晓得,当申明 immediate=true 的时候,初始化 Watcher,会马上调用invokeWithErrorHandling(cb, vm, [watcher.value], vm, info),即cb 的回调

Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {const info = `callback for immediate watcher "${watcher.expression}"`
        pushTarget()
        invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
        popTarget()}
    return function unwatchFn() {watcher.teardown()
    }
}

watch: {
    myObject:
    {
        immediate: true,
          handler: function() {... 初始化马上触发一次}
    }
}

sync=true

如果申明了 sync=true,在dep.sub.notify() 中,会马上执行,如果没有申明 sync=true,会推入队列中,等到下一个nextTick 周期才会执行

update() {
    /* istanbul ignore else */
    if (this.lazy) {this.dirty = true} else if (this.sync) {this.run()
    } else {queueWatcher(this)
    }
}

export function queueWatcher(watcher: Watcher) {
    const id = watcher.id
    if (has[id] == null) {has[id] = true
        if (!flushing) {queue.push(watcher)
        } else {
            // if already flushing, splice the watcher based on its id
            // if already past its id, it will be run next immediately.
            let i = queue.length - 1
            while (i > index && queue[i].id > watcher.id) {i--}
            queue.splice(i + 1, 0, watcher)
        }
        // queue the flush
        if (!waiting) {
            waiting = true

            if (process.env.NODE_ENV !== 'production' && !config.async) {flushSchedulerQueue()
                return
            }
            nextTick(flushSchedulerQueue)
        }
    }
}

参考文章

  1. Vue.js 技术揭秘
正文完
 0