剖析源码响应式变化原理 (三)

一.数据劫持

在上节中我们已经知道,vue在哪里做了状态的初始化(initState

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这里又进行了细化和拆分,对不同的属性做了不同的初始化操作,原来我们常用的api都在这里做的初始化~

1.数据的初始化

这里我们先关心数据是如何进行初始化操作的

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  // 1.数据不是对象则发生异常
  if (!isPlainObject(data)) {  
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  // 2.校验数据是否在method中已经声明过
  while (i--) {               
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    // 3.校验数据是否在属性中已经声明过
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
      // 4.将_data代理到实例上
      proxy(vm, `_data`, key) 
    }
  }
  // 5.观测数据
  observe(data, true /* asRootData */)
}
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

这里主要是检测属性是否被重复声明,并对属性进行观测

2.观测数据

export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 1.如果不是对象直接return
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  // 2.如果已经观测过则直接返回上次观测的实例
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { // 
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
  // 3.如果可以观测就进行观测
    ob = new Observer(value)
  }
  // 4.如果是根数据  vmCount标注为1
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}
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

只观测对象数据类型,已经观测的不在进行观测,不能扩展的属性不进行观测。

export class Observer {
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    // 1.数组的话重写数组原型方法 
    if (Array.isArray(value)) { 
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 2.观测数组中是对象类型的数据
      this.observeArray(value) 
    } else { 
      // 3.对象的话使用defineProperty重新定义属性
      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])
    }
  }
}
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

这里要区分对象和数组,如果是数组不能使用Object.defineProperty会造成性能浪费,所以采用重写可以更改数组本身的方法的方式。

3.对象的观测

对象的观测就是将所有属性使用defineProperty进行重新定义

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 1.如果对象不可配置则直接退出
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // 2.获取getter和setter
  const getter = property && property.get
  const setter = property && property.set
  
  // 3.重新定义set和get方法
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
    }
  })
}
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

对象的属性劫持已经烂大街了,非常简单就是通过defineProperty来实现的,如果你还不会那得好好反思一下了。这里提一下:想减少观测可以使用Object.freeze冻结对象

4.数组的观测

数组的观测就是通过重写原型方法来实现的

export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
methodsToPatch.forEach(function (method) {
  // cache original 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)
    return result
  })
})
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

这里我们所谓的数据观测就是当数据变化时我们可以知道,像对象更改时可以出发set方法,像数组调用push方法可以触发我们自己写的push

二.依赖收集

这里我们要回想一下vue的渲染过程是通过渲染watcher来实现的

let updateComponent = updateComponent = () => {
      vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {}, true /* isRenderWatcher */)
1
2
3
4

在我们创建watcher时,会对变量进行取值

2.1 对象依赖收集

对于对象而言,取值就会触发get方法,我们可以在defineProperty的get中进行依赖收集,在set中通知watcher进行更新操作

class Watcher {
  constructor (
    vm: Component,
    expOrFn: string | Function, // updateComponent
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // 将updateComponent 放到this.getter上
    this.getter = expOrFn
    this.value = this.lazy
      ? undefined
      : this.get() // 执行get方法
  }
  get () {
    pushTarget(this)  // Dep.target = 渲染watcher
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm) // 开始取值 那么在get方法中就可以获取到这个全局变量Dep.target        
    } 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
  }
}
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

渲染watcher,默认会调用get方法也就是我们传入的updateComponent方法,在调用此方法前先将watcher存到全局中,这样再取值时可以获取到这个watcher。

const dep = new Dep()
get: function reactiveGetter () {
    const value = getter ? getter.call(obj) : val
    if (Dep.target) { // 如果有watcher 将dep和watcher对应起来
        dep.depend()
    }
    return value
}
set: function reactiveSetter (newVal) {
    dep.notify();    // 当属性更新时通知dep中的所有watcher进行更新操作
}
1
2
3
4
5
6
7
8
9
10
11

2.2 数组的依赖收集

let childOb = !shallow && observe(val)
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的更新操作
methodsToPatch.forEach(function (method) {
    ob.dep.notify()
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

这里的watcher和dep的关系是多对多的关系,一个属性一个dep,每个dep里存放着多个watcher,同时watcher也会记住对应的dep。

export default class Dep {
  constructor () {
    this.id = uid++
    this.subs = []
  }
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  depend () {
    if (Dep.target) { 
      Dep.target.addDep(this) // 让watcher记住自己
    }
  }

  notify () {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update() // 让存储的watcher依次调用更新方法
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Watcher {
  constructor (
    vm: Component,
    expOrFn: string | Function, // updateComponent
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // 1.将updateComponent 放到this.getter上
    this.getter = expOrFn
    this.value = this.lazy
      ? undefined
    // 2.执行get方法
      : this.get()
  }
  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)) {
                // 3.让dep记录watcher
                dep.addSub(this) 
            }
        }
  }
  update () {
      queueWatcher(this)
  }
}
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

watcher中会进行虑重操作,实现watcher和dep互相记忆

三.异步更新

为了防止多次更改同一个属性或者多次修改不同属性(他们依赖的watcher相同) 会导致频繁更新渲染

export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  // 1.判断watcher是否已经存放了
  if (has[id] == null) {
    has[id] = true
  // 2.将watcher存放到队列中
    queue.push(watcher)
    // queue the flush
    if (!waiting) {
      waiting = true
      nextTick(flushSchedulerQueue) // 在下一队列中清空queue
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

对相同watcher进行过滤操作,当同步的更改状态完毕时再去更新watcher