阅读 382

Vue MVVM源码解析

1. 发布订阅模式与观察者模式

1.1 发布订阅

  • 订阅者
  • 发布者
  • 信号中心

我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布" (publish) 一个信号,其他任务可以向信号中心"订阅" (subscribe) 这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式" (publish-subscribe pattern)

1.1.1 发布订阅模式应用

  1. Vue自定义事件
let vm = new Vue()
// { 'click': [fn1, fn2], 'change': [fn] }

// 注册事件(订阅消息)
vm.$on('dataChange', () => {
  console.log('dataChange')
})

vm.$on('dataChange', () => {
  console.log('dataChange1')
})
// 触发事件(发布消息)
vm.$emit('dataChange')
复制代码
  1. eventBus
//事件中心
let eventHub = new Vue()

// ComponentA. vue
// 发布者
addTodo: function () {
  //发布消息(事件)
  eventHub.$emit('add-todo', { text: this.newTodoText })
  this.newTodoText = ''
}

// ComponentB.vue
// 订阅者
created: function () {
  // 订阅消息(事件)
  eventHub.$on('add-todo', this.addTodo)
}
复制代码

1.1.2 发布订阅实现

class EventEmitter { 
  constructor () { 
    // { eventType: [ handler1, handler2 ] } 
    this.subs = Object.create(null)
  }
  // 订阅通知 
  $on (eventType, handler) { 
    this.subs[eventType] = this.subs[eventType] || []
    this.subs[eventType].push(handler) 
  }
  // 发布通知 
  $emit (eventType) { 
    if (this.subs[eventType]) { 
      this.subs[eventType].forEach(handler => { 
        handler() 
      }) 
    } 
  } 
}
  
// 测试 
var bus = new EventEmitter() 

// 注册事件 
bus.$on('click', function () { 
  console.log('click') 
})

bus.$on('click', function () { 
  console.log('click1') 
})

// 触发事件 
bus.$emit('click')
复制代码

1.2 观察者模式

观察者模式符合的设计原则:主题和观察者分离,不是主动触发而是被动监听,两者解耦

// 主题,接收状态变化,触发每个观察者
class Subject {
  constructor() {
    this.state = 0
    this.observers = []
  }
  getState() {
    return this.state
  }
  setState(state) {
    this.state = state
    this.notifyAllObservers()
  }
  // 订阅通知
  attach(observer) {
    this.observers.push(observer)
  }
  // 执行通知
  notifyAllObservers() {
    this.observers.forEach(observer => {
      observer.update()
    })
  }
}

// 观察者,等待被触发
class Observer {
  constructor(name, subject) {
    this.name = name
    this.subject = subject
    this.subject.attach(this)
  }
  update() {
    console.log(`${this.name} update, state: ${this.subject.getState()}`)
  }
}

// 测试代码
let s = new Subject()
let o1 = new Observer('o1', s)
let o2 = new Observer('o2', s)
let o3 = new Observer('o3', s)

s.setState(1)
s.setState(2)
s.setState(3)
复制代码

1.3 发布订阅与观察者模式区别

总结:

  • 观察者模式是由具体目标调度,比如当事件触发,Dep 就会去调用观察者的方法,所以观察者模式的订阅者与发布者之间是存在依赖的。
  • 发布/订阅模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在。

1.4 Vue中的观察者模式

// 目标(发布者) 
// Dependency 
class Dep { 
  constructor () { 
    // 存储所有的观察者 
    this.subs = [] 
  }
  // 添加观察者 
  addSub (sub) { 
    if (sub && sub.update) {
      this.subs.push(sub) 
    } 
  }
  // 通知所有观察者 
  notify () { 
    this.subs.forEach(sub => { 
	  sub.update() 
    })
  }
}
 
// 观察者(订阅者) 
class Watcher { 
  update () { 
	console.log('update') 
  }
} 

// 测试 
let dep = new Dep() 
let watcher = new Watcher() 
dep.addSub(watcher) 
dep.notify()
复制代码
  • 观察者(订阅者) - Watcher
    • update(): 当事件发生时,具体要做的事情
  • 目标(发布者) -- Dep
    • subs数组:存储所有的观察者
    • addSub():添加观察者
    • notify(): 当事件发生,调用所有观察者的update()方法
  • 没有事件中心

2. Vue MVVM简单实现

2.1 Vue

  • 功能
    • 负责接收初始化的参数(选项)
    • 负责把 data 中的属性注入到 Vue 实例,转换成 getter/setter
    • 负责调用 observer 监听 data 中所有属性的变化
    • 负责调用 compiler 解析指令/差值表达式
  • 结构

这里我们把 data 属性挂载到 Vue 实例上其实是为了方便我们去使用数据,不用 this.data.,直接 this. 即可

class Vue {
  constructor (options) {
    // 1. 通过属性保存选项的数据
    this.$options = options || {}
    this.$data = options.data || {}
    this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el

    // 2. 把data中的成员转换成getter和setter,注入到vue实例中
    this._proxyData(this.$data)
    // 3. 调用observer对象,监听数据的变化
    new Observer(this.$data)
    // 4. 调用compiler对象,解析指令和差值表达式
    new Compiler(this)
  }

  _proxyData(data) {
    Object.keys(data).forEach(key => {
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get() {
          return data[key]
        },
        set(newValue) {
          if(newValue !== data[key]) {
            data[key] = newValue
          }
        }
      })
    })
  }
}
复制代码

2.2 Observer

  • 功能
    • 负责把 data 选项中的属性转换成响应式数据
    • data 中的某个属性也是对象,把该属性转换成响应式数据
    • 数据变化发送通知
  • 实现

这里我们需要注意 get 方法中我们返回的是 val 而不是 data[key],因为使用 data[key] 又会触发 getter,就变成了死循环。

另外我们在 set 中去修改 val,get 中返回 val,val 其实被锁在 defineReactive 函数里,我们 getset 操作都去修改 val 即可

class Observer {
  constructor (data) {
    this.walk(data)
  }

  walk(data) {
    // 1. 判断data是否是对象
    if (!data || typeof data !== 'object') {
      return
    }
    Object.keys(data).forEach(key => {
      this.defineReactive(data, key, data[key])
    })
  }

  defineReactive(data, key, val) {
    let _self = this
    let dep = new Dep()
    this.walk(val)
    Object.defineProperty(data, key, {
      enumerable: true,
      configurable: true,
      get() {
        Dep.target && dep.addSub(Dep.target) // 观察者模式添加观察者
        return val
      },
      set(newValue) {
        if(newValue !== val) {
          val = newValue
          _self.walk(newValue)
          dep.notify() // 观察者模式发布通知
        }
      }
    })
  }
}
复制代码

3.3 Dep

  • 功能
    • 收集依赖,添加观察者(watcher)
    • 通知所有观察者
  • 实现

class Dep {
  constructor() {
    this.subs = []
  }

  addSub(sub) {
    if(sub && sub.update) {
      this.subs.push(sub)
    }
  }

  notify() {
    this.subs.forEach(sub => {
      sub.update()
    })
  }
  
  remove() {
    if(arr.length) {
      const index = arr.indexOf(item)
      if(index > -1) {
        return arr.splice(index, 1)
      }
    }
  }
}
复制代码

3.4 watcher

  • 功能
    • 当数据变化触发依赖,dep 通知所有的 Watcher 实例更新视图
    • 自身实例化的时候往 dep 对象中添加自己
  • 实现

我们设置 Dep.target = this,然后调用 this.oldValue = vm[key],这时会触发一次getter,即 defineReactiveget 方法执行 Dep.target && dep.addSub(Dep.target) 添加一个观察者,接着我们再清空target Dep.target = null

class Watcher { 
  constructor (vm, key, cb) { 
    this.vm = vm 
    // data 中的属性名称 
    this.key = key 
    // 当数据变化的时候,调用 cb 更新视图 
    this.cb = cb 
    // 在 Dep 的静态属性上记录当前 watcher 对象,当访问数据的时候把 watcher 添加到 dep 的 subs 中 
    Dep.target = this 
    // 触发一次 getter,让 dep 为当前 key 记录 watcher
    this.oldValue = vm[key] 
    // 清空 target 
    Dep.target = null 
  }
  update () { 
    const newValue = this.vm[this.key] 
    if (this.oldValue === newValue) { 
      return 
    }
    this.cb(newValue) 
  } 
}
复制代码

3.5 调用 watcher

在 compiler.js 中为每一个指令/插值表达式创建 watcher 对象,监视数据的变化

// 因为在 textUpdater等中要使用 
this updaterFn && updaterFn.call(this, node, this.vm[key], key) 

// v-text 指令的更新方法 
textUpdater (node, value, key) { 
  node.textContent = value // 每一个指令中创建一个 watcher,观察数据的变化 
  new Watcher(this.vm, key, value => { 
    node.textContent = value 
  }) 
}
复制代码

视图变化更新数据

// v-model 指令的更新方法 
modelUpdater (node, value, key) { 
  node.value = value // 每一个指令中创建一个 watcher,观察数据的变化 
  new Watcher(this.vm, key, value => { 
  	node.value = value 
  })// 监听视图的变化 
  node.addEventListener('input', () => { 
  	this.vm[key] = node.value 
  }) 
}
复制代码

3.6 批处理

功能:

  • 异步处理视图渲染,这样我们多次修改data时,只渲染最后一次
  • 由于异步渲染,这里我们拿到的 data 肯定是最后一次的 data。
  • 我们可以得出结论,修改数据是同步的,渲染页面是异步的
class Batcher {
  constructor () {
    this.has = {}
    this.queue = []
    this.waiting = false
  }

  push(job) {
    let id = job.id
    // 如果没有该id,才继续走下一步
    if (!this.has[id]) {
      this.queue.push(job)
      //设置元素的ID
      this.has[id] = true
      if (!this.waiting) {
        this.waiting = true
        if ("Promise" in window) {
          Promise.resolve().then( ()=> {
            this.flush()
          })
        } else {
          setTimeout(() => {
            this.flush()
          }, 0)
        }
      }
    }
  }

  flush() {
    this.queue.forEach((job) => {
      job.cb(job.newValue)
    })
    this.reset()
  }

  reset() {
    this.has = {}
    this.queue = []
    this.waiting = false
  }
}
复制代码

3.7 总体流程

源码:gitee.com/zxhnext/fed…

4. Vue源码中的MVVM

我们通过源码来找以下几个问题的答案

  • vm.msg = { count: 0 } ,重新给属性赋值,是否是响应式的?
  • vm.arr[0] = 4 ,给数组元素赋值,视图是否会更新
  • vm.arr.length = 0 ,修改数组的 length,视图是否会更新
  • vm.arr.push(4) ,视图是否会更新

4.1 initState

在 Vue 的初始化阶段,_init 方法执行的时候,会执行 initState(vm) 方法,它的定义在 src/core/instance/state.js 中 这里会判断data是否是 函数,是否与 methods/props/ 等中有重名,判断完后,调用 observer() 方法

  • 执行顺序:
    • props
    • methods
    • data
    • computed
    • watch
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
  // 判断data是否是函数,是否与methods/props/等中有重名,最后再调用observer()
  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)
}
复制代码

initState 方法主要是对 propsmethodsdatacomputedwathcer 等属性做了初始化操作

这里主要做了两件事:一个是对定义 data 函数返回对象的遍历,通过 proxy 把每一个值 vm._data.xxx 都代理到 vm.xxx 上;
另一个是调用 observe 方法观测整个 data 的变化,把 data 也变成响应式

4.2 响应式对象

1. observer

observer在 /src/core/observer/index.js

  • 判断 value 是否是对象或vnode实例,是直接返回
  • 如果 value 有 __ob__ (observer对象) 属性,说明已有该对象,直接返回
  • 创建一个 Observer 对象
export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 判断 value 是否是对象或vnode实例
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  // 如果 value 有 __ob__(observer对象) 属性,说明已有该对象,结束
  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 // 不能是vue实例
  ) {
    // 创建一个 Observer 对象
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}
复制代码

2. Observer

  • src\core\observer\index.js
    • 通过执行 def 函数把自身实例添加到数据对象 value 的 __ob__ 属性
    • 对对象做响应化处理
    • 对数组做响应化处理(下面单独作一个章节,这里先略过)
    • walk(obj) 
      • 遍历 obj 的所有属性,为每一个属性调用 defineReactive() 方法,设置 getter/setter
export class Observer {
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    // 初始化实例的 vmCount 为0
    this.vmCount = 0
    // 将实例挂载到观察对象的 __ob__ 属性
    def(value, '__ob__', this)
    // 数组的响应式处理
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 为数组中的每一个对象创建一个 observer 实例
      this.observeArray(value)
    } else {
      // 遍历对象中的每一个属性,转换成 setter/getter
      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])
    }
  }
}
复制代码

3. defineReactive()

  • src\core\observer\index.js
  • defineReactive(obj, key, val, customSetter, shallow)
    • 为一个对象定义一个响应式的属性,每一个属性对应一个 dep 对象
    • 如果对象不可配置,什么都不做
    • 如果该属性的值是对象,继续调用 observe
    • 如果给属性赋新值,继续调用 observe
    • get中建立依赖
    • set如果数据更新发送通知
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 创建依赖对象实例
  const dep = new Dep()
  // 获取 obj 的属性描述符对象,如果该对象不可配置,直接返回
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }
  // 判断是否递归观察子对象,并将子对象属性都转换成 getter/setter,返回子观察对象
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      // 如果预定义的 getter 存在则 value 等于getter 调用的返回值
      // 否则直接赋予属性值
      const value = getter ? getter.call(obj) : val
      // 如果存在当前依赖目标,即 watcher 对象,则建立依赖
      if (Dep.target) {
        dep.depend()
        // 如果子观察目标存在,建立子对象的依赖关系
        if (childOb) {
          childOb.dep.depend()
          // 如果属性是数组,则特殊处理收集数组对象依赖
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      // 返回属性值
      return value
    },
    set: function reactiveSetter (newVal) {
      // 如果预定义的 getter 存在则 value 等于getter 调用的返回值
      // 否则直接赋予属性值
      const value = getter ? getter.call(obj) : val
      // 如果新值等于旧值或者新值旧值为NaN则不执行
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // 如果没有 setter 直接返回
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      // 如果预定义setter存在则调用,否则直接更新新值
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // 如果新值是对象,观察子对象并返回 子的 observer 对象
      childOb = !shallow && observe(newVal)
      // 派发更新(发布更改通知)
      dep.notify()
    }
  })
}
复制代码

4.3 收集依赖

1. dep

  • Dep 实际上就是对 Watcher 的一种管理
  • addSub
  • notify
  • Dep.target 用来存放目前正在使用的 watcher, 全局唯一,并且一次也只能有一个 watcher 被使用
export default class Dep {
  // 静态属性,watcher 对象
  static target: ?Watcher;
  // dep 实例 Id
  id: number;
  // dep 实例对应的 watcher 对象/订阅者数组
  subs: Array<Watcher>;

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

  // 添加新的订阅者 watcher 对象
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }


  // 将观察对象和 watcher 建立依赖
  depend () {
    if (Dep.target) {
      // 如果 target 存在,把 dep 对象添加到 watcher 的依赖中
      Dep.target.addDep(this)
    }
  }

  // 发布通知
  notify () {
    const subs = this.subs.slice()
    // 调用每个订阅者的update方法实现更新
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// Dep.target 用来存放目前正在使用的watcher
// 全局唯一,并且一次也只能有一个watcher被使用
Dep.target = null
const targetStack = []
// 入栈并将当前 watcher 赋值给 Dep.target
// 父子组件嵌套的时候先把父组件对应的 watcher 入栈,
// 再去处理子组件的 watcher,子组件的处理完毕后,再把父组件对应的 watcher 出栈,继续操作
export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}

export function popTarget () {
  // 出栈操作
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}
复制代码

2. watcher

  • get
    • pushTarget 入栈并将当前 watcher 赋值给 Dep.target
    • value = this.getter.call(vm, vm) // 读取值,触发get
    • popTarget 出栈操作, Dep.target 赋值为下个target
  • update
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
}

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

3. 注册 watcher

updateComponent = () => {
  vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
  before () {
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate')
    }
  }
}, true /* isRenderWatcher */)
复制代码

4.4 数组处理

1. 获取数组原型 arrayMethods

创建变量 arrayMethods,它继承自 Array.prototype,具备其所有功能。未来,我们要使用 arrayMethods 去覆盖Array.prototype

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
;[
  'push', 
  'pop', 
  'shift', 
  'unshift', 
  'splice', 
  'sort', 
  'reverse'
].forEach(function(method) {
  // 缓存原始方法
  const original = arrayProto[method]
  Object.defineProperty(arrayMethods, method, {
    value:function mutator(...args) {
      return original.apply(this, args)
    },
    enumerable:false, 
    writable:true, 
    configurable: true 
  })
})
复制代码

2. 覆盖响应式数据的 Array.prototype

我们用 arrayMethods 去覆盖 Array.prototype。但是我们又不能直接覆盖,因为这样会污染全局的 Array,我们希望拦截操作只针对那些被侦测了变化的数据生效,也就是说希望拦截器只覆盖那些响应式数组的原型。而将一个数据转换成响应式的,需要通过 Observer,所以我们只需要在 Observer 中使用拦截器覆盖那些即将被转换成响应式 Array 类型数据的原型就好了

export class Observer {
  constructor(value){
    this.value = value
    if(Array.isArray(value)){
      value.__proto__ = arrayMethods//新增
    } else {
      this.walk(value)
    }
  }
}
复制代码

value.__proto__ = arrayMethods 的作用是将拦截器(加工后具备拦截功能的 arrayMethods)赋值给 value.__proto__,通过 __proto__ 可以很巧妙地实现覆盖 value 原型的功能

3. 处理没有__proto__的情况

处理不能使用 __proto__ 的情况,Vue的做法非常粗暴,如果不能使用 __proto__,就直接将 arrayMethods 身上的这些方法设置到被侦测的数组上

//_proto_是否可用
const hasProto = '__proto__' in {}
const arrayKeys = Object.getOMnPropertyNames(arrayMethods)
export class Observer {
  constructor(value){
    this.value = value
    if (Array.isArray(value)) {
      // 修改
      const augment = hasProto ? protoAugment : copyAugment
      augment(value, arrayMethods, arrayKeys)
    } else {
      this.walk(value)
    }
  }
}

function protoAugment(target, src, keys) {
  target.__proto__ = src
}
function copyAugment(target, src, keys) {
  for(let i=0,l = keys.length; i<l; i++) {
    const key = keys[i]
    def(target,key,src[key])
  }
}
复制代码

4. 为数组添加 __ob__

__ob__ 的作用不仅仅是为了在拦截器中访问Observer实例这么简单,还可以用来标记当前 value 是否已经被 Observer 转换成了响应式数据。 也就是说,所有被侦测了变化的数据身上都会有一个 __ob__ 属性来表示它们是响应式的。如果value是响应式的,则直接返回 __ob__;如果不是响应式的,则使用 new Observer 来将数据转换成响应式数据。 当value身上被标记了 __ob__ 之后,就可以通过 value.__ob__ 来访问observer实例。如果是Array拦截器,因为拦截器是原型方法,所以可以直接通过 this.__ob__ 来访问Observer 实例

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
;[
  'push', 
  'pop', 
  'shift', 
  'unshift', 
  'splice', 
  'sort', 
  'reverse'
].forEach(function(method) {
  // 缓存原始方法
  const original= arrayProto[method]
  def(arrayMethods,method, function mutator(...args){
    const result = original.apply(this,args)
    const ob = this.__ob__
    ob.dep.notify() // 向依赖发送消息
    return result
  })
})
复制代码

5. 侦测数组元素变化

export class Observer {
  constructor(value){
    this.value = value
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      // 修改
      const augment = hasProto ? protoAugment : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observerArray(value)
    } else {
      this.walk(value)
    }
  }
  
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}
复制代码

6. 对数组新增元素监听

;[
  'push', 
  'pop', 
  'shift', 
  'unshift', 
  'splice', 
  'sort', 
  'reverse'
].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.observerArray(inserted)
    ob.dep.notify() // 向依赖发送消息
    return result
  })
})
复制代码

我们通过 switch 对 method 进行判断,如果 method 是 pushunshiftsplice 这种可以新增数组元素的方法,那么从 args中将新增元素取出来,暂存在 inserted 中。接下来使用 Observer 把 inserted 中的元素转换成响应式的。

4.5 异步更新渲染队列

  • has 对象保证同一个 Watcher 只添加一次

  • flushing 正在刷新,表示队列是否正在被处理,如果没有被处理,直接添加到队列中,否则插入到队列中

  • waiting 表示当前队列是否正在执行,如果没有被执行,就调用flushSchedulerQueue, 保证对 nextTick(flushSchedulerQueue) 的调用逻辑只有一次

  • queue排序目的

      1. 组件更新顺序从父组件到子组件,因为先创建父组件再创建子组件
      1. 用户的自定义 watcher 要优先于渲染 watcher 执行;因为用户自定义 watcher 是在渲染 watcher 之前创建的
      1. 如果一个组件在父组件的 watcher 执行期间被销毁,那么它对应的 watcher 执行都可以被跳过,所以父组件的 watcher 应该先执行
  • 遍历watcher

    • 不要去缓存 queue 长度,因为这个队列是在动态添加的
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)
    }
  }
}


function flushSchedulerQueue () {
  currentFlushTimestamp = getNow()
  flushing = true
  let watcher, id
  // 排序目的
  // 1. 组件更新顺序从父组件到子组件,因为先创建父组件再创建子组件
  // 2. 组件的用户watcher要在渲染watcher之前运行
  // 3. 如果一个组件在运行前父组件被销毁了,那么应该跳过本次更新
  queue.sort((a, b) => a.id - b.id)

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  // 不要去缓存queue长度,因为这个队列是在动态添加的
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    watcher.run()
  }

  // keep copies of post queues before resetting state
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()

  resetSchedulerState()

  // call component updated and activated hooks
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)

  // devtool hook
  /* istanbul ignore if */
  if (devtools && config.devtools) {
    devtools.emit('flush')
  }
}
复制代码

5. 原理图