本文基于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 = nullconst 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.setVue.set(vm.items, indexOfItem, newValue)// Array.prototype.splicevm.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.jsconst arrayProto = Array.prototypeexport 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.jsexport 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.jsfunction 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.jsget() { //  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.jsdepend () {  if (Dep.target) {    Dep.target.addDep(this)  }}// watcher.jsaddDep(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.jsdepend() {    // this.deps是从cleanupDeps()中    // this.deps = this.newDeps来的    // this.newDeps是通过addDep()来的    let i = this.deps.length    while (i--) {        this.deps[i].depend()    }}// Dep.jsdepend() {    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.jsexport 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.jsget() {    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 技术揭秘