Vue 2.6.13 源码解析 ObserverDepWatcher与订阅

Posted 白瑕

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue 2.6.13 源码解析 ObserverDepWatcher与订阅相关的知识,希望对你有一定的参考价值。

文章目录


前言

我还是感觉看了个寂寞, 一直分析代码执行, 但是忽略了比较宏观的一些事, 比如Dep如何将变化通知到Watcher, Watcher是怎么订阅Dep的, 缺乏这种意识.


一、由initData整理思路

1.1 initData()到observe()

vm提取data数据对象, 传给observe

function initData (vm: Component) 
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
  ? getData(data, vm)
  : data || 
  
  observe(data, true)


1.2 observe()

检查该data数据对象是否已受观察, 即检查该数据对象内是否具备__ob__属性, 若有则说明已受观察不做处理, 若无则设置观察者new Observer(value).
可能因为是初始化, 所以只对没有被观察的设置观察.

export function observe (value: any, asRootData: ?boolean): Observer | void 
  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
  ) 
    ob = new Observer(value)
  
  if (asRootData && ob) 
    ob.vmCount++
  
  return ob


1.3 new Observer()

Dep, 数据对象, vmConunt传入def()

export class Observer 
  value: any;
  dep: Dep;
  vmCount: number;

  constructor (value: any) 
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this) // 数据对象, Dep, vmCount

    this.walk(value)

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

  


1.4 def()

核心defineProperty()为数据对象value设置__ob__属性, 至此该对象被Dep劫持
Object.defineProperty(value, '__ob__', Dep, 数据对象, vmCount )

export function def (obj: Object, key: string, val: any, enumerable?: boolean) 
  Object.defineProperty(obj, key, 
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  )

完成后value上具备__ob__属性.

// value

  xxx: 'xxx',
  x: 'xxx',
  __ob__: 
    dep: 
      ...
      subs: [
        订阅者0,
        订阅者1,
        ...
      ]
    
  

此时Observer又调用了walk(value)


1.5 walk()

def完成后value会带着Dep来执行walk()
提取数据对象的key构成数组, 遍历数组对其内部元素defineReactive(数据对象, 属性名)将数据对象中每个属性都执行defineReactive().

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


1.6 defineReactive()

defineReactive(数据对象, 属性名), 内部Object.property(数据对象, 属性名, ..., get, set)为数据对象设置getset方法, 在初始化的时候会触发get, 后期更新数据会触发set.

get即初始化时检查Dep.target, 这个变量就是订阅该Dep一个Watcher, new Watcher()时会触发Watcher类的get()执行deppushTarget(this), 这个this指向一个Watcher, 随后pushTarget()Dep.target = 这个Watcher, 从而检索成功放行.

放行后执行dep.depend()调用该depdepend()方法进而将这个dep加入到Dep.Target对应的WatchernewDeps数组内, 表示这是该Watcher订阅的Dep, 同时Dep也会调用addSub()将这个Watcher加入自己的Subs数组表示这是自己的订阅者.
每个属性对应一个负责劫持的Observer, 每个Observer通知一个Dep, 一个Dep可以由多个Watcher订阅, Dep也通知变化到多个Watcher.

数据更新时会触发set进而调用Depnotify()通知函数, 通知订阅该Dep的所有Watcher执行update(), 下面会说到.

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) 
  const dep = new Dep()

  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) 
      const value = getter ? getter.call(obj) : val
      if (setter) 
        setter.call(obj, newVal)
       else 
        val = newVal
      
      dep.notify()
    
  )
  

1.7 new Dep() && depend()

defineReactive()defineProperty()定义的get()内部由Observer实例调用Depdepend(), 在depend()会调用Watcher类的addDep()方法, Watcher借此拿到Dep.
addDep还调用DepaddSub(), Dep借此拿到Watcher, 此时双方订阅与被订阅关系构成.
DepSubs数组用来存放所有订阅者Watcher.

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)
  

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

  notify () 
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) 
      subs[i].update()
    
  
  

1.7.1 pushTarget()

Dep.target = null // 全局
const targetStack = [] // 全局

export function pushTarget (target: ?Watcher) 
  targetStack.push(target)
  Dep.target = target


1.8 Watcher

Watcher实例化先调用get()里的pushTarget()(这个函数也在dep.js中但不属于Dep中)传自己过去, 然后pushTargetWatcher赋值给Dep.target, 此时defineReactive()里的dep.depend()能够执行.
直到newDep()发生, Depdepend(this)执行Dep.target.addDep()(addDep()Watcher的方法, 两者借此建立联系), 双方各自把对方加进自己的数组newDepSub, 订阅算是完成.

export default class Watcher 
  get () 
    pushTarget(this) // dep里的pushTarget
  

  addDep (dep: Dep) 
    const id = dep.id
    if (!this.newDepIds.has(id)) 
      this.newDepIds.add(id)
      this.newDeps.push(dep) // Watcher拿到Dep
      if (!this.depIds.has(id)) 
        dep.addSub(this) // Dep拿到Watcher
      
    
  
  
  update () 
    /* istanbul ignore else */
    if (this.lazy) 
      this.dirty = true
     else if (this.sync) 
      this.run()
     else 
      queueWatcher(this)
    
  
  
  run () 
    if (this.active) 
      const value = this.get()
    
  
  


1.9 initData图示


二、由initComputed整理思路

2.1 由initComputed()到defineComputed()

整个主体在一个loop完成, 为每个计算属性实例化Watcher, vm._watchers.push(this)将该Watcher存入vm的诸多Watcher中.
然后对每个计算属性执行defineComputed(vm, 计算属性名, 计算属性值)

const sharedPropertyDefinition =  // 全局
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop


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)
    
  


2.2 defineComputed()

针对computed属性值的不同书写方式组成sharedPropertyDefinition作为该计算属性的描述符参数.

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
    
  
  Object.defineProperty(target, key, sharedPropertyDefinition)


2.3 createComputedGetter()

在上一步为每个计算属性实例化了Watcher, 拿到当前计算属性的Watcher, 执行Watcherevaluate()调用Watcherget()和Dep的pushTarget, 导致Dep.target = 当前Watcher
Dep.target存在后由Watcher调用Dep的depend(), 然后就回到1.8的addDep()订阅了.

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
    
  


2.4 initComputed图示


总结

每次到addDep(), 也就是每次Watcher调用Depdepend(), 都会进行双向数据收集, Watcher和Dep互相同步数据, Dep的数据更新后通知订阅自己的每个Watcher调用notify()进而调用update()更新数据, computed计算属性在接受defineProperty()之后会拥有set()get()方法, 更新调set()之后Dep`通知.

以上是关于Vue 2.6.13 源码解析 ObserverDepWatcher与订阅的主要内容,如果未能解决你的问题,请参考以下文章

Vue 2.6.13 源码解析

Vue 2.6.13 源码解析

Vue 2.6.13 源码解析 ObserverDepWatcher与订阅

Vue 2.6.13 源码解析 感知变化以发起通知

Vue 2.6.13 源码解析 initLifeCycle && initEvents

Vue 2.6.13 源码分析