假设代码是

1
2
3
<body>
<div id="app">{{sum}}</div>
</body>

1
2
3
4
5
6
7
8
9
10
11
const app = new Vue({
el:"#app",
data:{
num:1
},
computed:{
sum: function(){
return this.num+10
}
}
})

computed 有函数声明和对象声明,我采用的是函数声明

1
2
3
4
5
6
7
8
// 对象声明用法
computed:{
sum: {
get:function(){
return this.num+10
}
}
}

实例 Vue

  • 流程

    • 先是进入[instance/index] 触发 this._init
    • 再进入[instance/init] 触发了 initState
    • 接着进入到[instance/state],依次给 props、methods、data、computed、watch 进行 Object.definePrototype() 数据绑定。
    • initData、initComputed 都是给里面的 key 进行 Object.defineProperty() 数据订阅劫持(应该是这个名词,网上大家都这么说),但是细节有所不同。并且 data 里的 key 都会有依赖收集 Dep,computed 里的 key 都设定一个监控器 Watcher。
    • 再进行完以上步骤后,就会进行实例挂载了
  • 对比 data 与 computed
    其中的 Dep、Watcher 是相互作用的,我对它们的理解是:
    Dep:dependence 依赖收集,data 就有 Dep,而 computed 没有。它的代码在[observer/dep.js]
    Watcher:监控器,需要监控变化。computed 就有 Watcher,而 data 没有。它的代码在[observer/watcher.js]。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    // data的主要代码是这个
    export function defineReactive (
    obj: Object,
    key: string,
    val: any,
    customSetter?: ?Function,
    shallow?: boolean
    ) {
    const dep = new Dep()
    const property = Object.getOwnPropertyDescriptor(obj, key)
    if (property && property.configurable === false) {
    return
    }
    const getter = property && property.get
    const setter = property && property.set
    if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
    }
    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) {
    const value = getter ? getter.call(obj) : val
    if (newVal === value || (newVal !== newVal && value !== value)) {
    return
    }
    if (process.env.NODE_ENV !== 'production' && customSetter) {
    customSetter()
    }
    if (getter && !setter) return
    if (setter) {
    setter.call(obj, newVal)
    } else {
    val = newVal
    }
    childOb = !shallow && observe(newVal)
    dep.notify()
    }
    })
    }
    // initComputed 的主要代码是这个:
    function initComputed (vm: Component, computed: Object) {
    const watchers = vm._computedWatchers = Object.create(null)
    const isSSR = isServerRendering()
    for (const key in computed) {
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    if (!isSSR) {
    watchers[key] = new Watcher(
    vm,
    getter || noop,
    noop,
    computedWatcherOptions
    )
    }
    if (!(key in vm)) {
    defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
    if (key in vm.$data) {
    warn(`The computed property "${key}" is already defined in data.`, vm)
    } else if (vm.$options.props && key in vm.$options.props) {
    warn(`The computed property "${key}" is already defined as a prop.`, vm)
    }
    }
    }
    }
    export function defineComputed (
    target: any,
    key: string,
    userDef: Object | Function
    ) {
    const shouldCache = !isServerRendering()// 是否服务端渲染
    if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
    ? createComputedGetter(key)
    : createGetterInvoker(userDef)
    sharedPropertyDefinition.set = noop
    } else {
    sharedPropertyDefinition.get = userDef.get
    ? shouldCache && userDef.cache !== false
    ? createComputedGetter(key)
    : createGetterInvoker(userDef.get)
    : noop
    sharedPropertyDefinition.set = userDef.set || noop
    }
    if (process.env.NODE_ENV !== 'production' &&
    sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
    warn(
    `Computed property "${key}" was assigned to but it has no setter.`,
    this
    )
    }
    }
    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
    }
    }
    }

    看下列表格, computed 多了一个判断条件 watcher.dirty,dirty 是 computed 专属的参数,它的作用可以继续向下看下一步的分析。
    注意 dep.depend() 和 watcher.depend() 不一样,前者是 Dep,后者是 Watcher。

Object.defineProperty data computed
get function(){ function(){
if (Dep.target) { if (watcher) {
dep.depend() if (watcher.dirty) {
} watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
}
set function(){dep.notify()} 用户自定义的
  • 缓存
    先看看 Watcher 的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    export default class Watcher {
    vm: Component;
    expression: string;
    cb: Function;
    id: number;
    deep: boolean;
    user: boolean;
    lazy: boolean;
    sync: boolean;
    dirty: boolean;
    active: boolean;
    deps: Array<Dep>;
    newDeps: Array<Dep>;
    depIds: SimpleSet;
    newDepIds: SimpleSet;
    before: ?Function;
    getter: Function;
    value: any;

    constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
    ) {
    this.vm = vm
    if (isRenderWatcher) {
    vm._watcher = this
    }
    vm._watchers.push(this)
    // options
    if (options) {
    this.deep = !!options.deep
    this.user = !!options.user
    this.lazy = !!options.lazy
    this.sync = !!options.sync
    this.before = options.before
    } else {
    this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid
    this.active = true
    this.dirty = this.lazy
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
    ? expOrFn.toString()
    : ''
    // parse expression for getter
    if (typeof expOrFn === 'function') {
    this.getter = expOrFn
    } else {
    this.getter = parsePath(expOrFn)
    if (!this.getter) {
    this.getter = noop
    process.env.NODE_ENV !== 'production' && warn(
    `Failed watching path: "${expOrFn}" ` +
    'Watcher only accepts simple dot-delimited paths. ' +
    'For full control, use a function instead.',
    vm
    )
    }
    }
    this.value = this.lazy
    ? undefined
    : this.get()
    }
    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 {
    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) {
    try {
    this.cb.call(this.vm, value, oldValue)
    } catch (e) {
    handleError(e, this.vm, `callback for watcher "${this.expression}"`)
    }
    } else {
    this.cb.call(this.vm, value, oldValue)
    }
    }
    }
    }
    evaluate () {
    this.value = this.get()
    this.dirty = false
    }
    depend () {
    let i = this.deps.length
    while (i--) {
    this.deps[i].depend()
    }
    }
    teardown () {
    if (this.active) {
    if (!this.vm._isBeingDestroyed) {
    remove(this.vm._watchers, this)
    }
    let i = this.deps.length
    while (i--) {
    this.deps[i].removeSub(this)
    }
    this.active = false
    }
    }
    }

    computed 的 sum 实例化了一个 Watcher(叫它 Watcher2), new Watcher(vm,getter || noop,noop,computedWatcherOptions),其中 getter 是我们实例中的 function(){return this.num+10}
    根据 Watcher 的代码 this.value = this.lazy ? undefined : this.get(),因为 dirty:true,所以它啥都不做,此时 Watcher1.value = undefined

  • 实例挂载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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 (!config.async) {
    subs.sort((a, b) => a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
    }
    }
    }

    实例挂载时,也会设定一个监控器(叫它 Watcher1)new Watcher(vm, updateComponent, noop, {before () {if (vm._isMounted && !vm._isDestroyed) {callHook(vm, ‘beforeUpdate’)}}}, true),其中 updateComponent 先暂定为虚拟 DOM 的函数,返回空。
    根据 Watcher 的代码,因为 dirty:false,所以触发了 Watcher2.get(),其中 value = this.getter.call(vm, vm),this.getter 指向 updateComponent 函数,它会访问 sum 的值,就触发了 sum 的 get 方法。
    根据上面表格 computed 的 get 函数,会进行 watcher.dirty 判断。

    1
    2
    3
    if(watcher.dirty){
    watcher.evalute()
    }

    因为是刚开始的数据处理,所以 dirty:true,走到了 watcher.evaluate(),根据 Watcher 中 evaluate 的函数定义,它再走到 watcher.get(),其中 value = this.getter.call(vm, vm),它需要获取到 num 的值,就触发了 num 的 get。结果就是 num 的依赖收集器 Dep.subs 存入了 sum 的 Watcher 监控器,sum 的监控器 Watcher.deps 也存入了 num 这个依赖。于是 watcher.value 就获得值了,并且将 dirty: false。

    1
    2
    3
    if (Dep.target) {
    watcher.depend()
    }

    可以自己跟着逻辑走一步,发现 Dep.target 此时指向 Watcher1,但是触发的是 Watcher2 的 depend() 函数,它将自己的 Watcher 里的 deps 的依赖组一个个执行,将此前的 Watcher2 放进依赖收集器里了。
    所以最终 num 的依赖收集器 的 subs 有2个 Watcher。

  • 依赖更新
    假设此时的 num:2,就会触发 num 的 set 方法,开始执行 dep.notify(),注意一下为什么它会用到 sort 函数,因为 subs 里的 Watcher有先后顺序,我们需要先执行 computed 的 Watcher,再执行挂载时的 Watcher。
    Watcher2就跑到前面了, Watcher2.run()后只是将 dirty:true 了。
    接着执行 Watcher1,因为 sum 脏了,所以它又开始上面实例挂载的步骤了。

这是我画出一个流程图
流程图

我研究了好几天了,特别绕,一下 Watcher、一下 Dep。然后我就用 WPS 的流程图把整个流程滑下来,就特别的清晰了。也把 Watcher 与 Dep 的作用都了解清楚了(因为我前几天做了个小小小小 vue,只了解了其中一点点。目前我的 vue 还在继续完善)。