Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vue源码阅读七:响应式原理 #37

Open
yangrenmu opened this issue Sep 22, 2020 · 0 comments
Open

vue源码阅读七:响应式原理 #37

yangrenmu opened this issue Sep 22, 2020 · 0 comments
Labels

Comments

@yangrenmu
Copy link
Owner

前言

vue 中数据是普通的js对象,当数据变化时,视图会进行更新。数据之所以能驱动视图的更新,关键的部分就是它的响应式系统。

初始化数据

  • initState

new Vue()初始化阶段,this._init(options)方法执行的时候,会执行initState(vm),它定义在src/core/instance/state.js中。

export function initState (vm: Component) {
  // ...
  if (opts.data) {
    initData(vm)
  }
  // ...
}

initState的作用主要是初始化props,methods,data,computed,watcher等属性,这里我们重点看看initData

  • initData

function initData (vm: Component) {
  // ...
  observe(data, true /* asRootData */)
}

initData,初始化data用的,在最后有一行observe(data, true)

  • observe

export function observe (value: any, asRootData: ?boolean): Observer | void {
  // ...
  ob = new Observer(value)
  // ...
}

observe()是将用户定义的数据data变成响应式的数据。主要是去实例化一个Observe对象实例,用于收集依赖和派发更新。

  • new Observer(value)

export class Observer {
  constructor (value: any) {
    this.value = value
    this.walk(value)
  }

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

Observer中,对对象的每一项执行,defineReactive(obj, keys[i])

  • defineReactive

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {

  const dep = new Dep() // 依赖管理器
  
  val = obj[key]
  observe(val) // 递归对象

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      // 收集依赖
    },
    set: function reactiveSetter (newVal) {
      // 更新依赖
    }
  })
}

收集依赖

// mountComponent
export function mountComponent(
  vm: Component, // 组件实例
  el: ?Element, // 挂载的元素
  hydrating?: boolean // 服务端渲染相关
): Component {
  ...
  updateComponent = () => {
    // vm._render(),生成 vnode,在 instance/render.js 中
    // vm._update(),更新 dom
    vm._update(vm._render(), hydrating)
  }
  // watcher 会调用 updateComponent,先生成 vnode ,然后调用 update 更新 dom;
  new Watcher(vm, updateComponent, noop, {
    before() { ... }
  }, true /* isRenderWatcher */)
  return vm
}

// watcher
export default class Watcher {
  constructor(
    vm: Component, // 组件实例
    expOrFn: string | Function, 
    cb: Function, // 当监听的数据变化时,会触发该回调
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    ...
    // expOrFn 是 `updateComponent` 方法
    this.getter = expOrFn
    this.value = this.lazy
      ? undefined
      : this.get()
  }
  get() {
    // 添加 target,相当于 watcher
    pushTarget(this)
    ...
    try {
      // 相当于执行 updateComponent()
      value = this.getter.call(vm, vm)
    } catch (e) {
    ... 
  }
}

vuemount 过程中会执行mountComponent,而在mountComponent中会new 一个 Watcher,然后里面执行this.get()方法。这个get()方法中,又执行了pushTarget(this),是将当前的watcher保存到数组中。

Dep.target = null
const targetStack = []

export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target // 当前的 watcher 实例
}

pushTarget 的定义在 src/core/observer/dep.js 中。然后get()方法中,执行了

this.getter.call(vm, vm)  // 相当于执行vm._update(vm._render(), hydrating)

首先执行的vm._render()方法中,如果有使用data数据,会触发defineProperty重写的get方法。每个对象值都会持有一个dep,在触发get时,会调用dep.depend()方法。

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) {
      // 派发更新
    }
  })
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  // ...
  constructor () {
    this.id = uid++
    this.subs = [] // 对象 key 值对应的 watcher 集合
  }

  addSub (sub: Watcher) {
    this.subs.push(sub) // 将 watcher 添加到subs 数组中
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this) // 执行 addDep
    }
  }
  // ...
}
  -------------------------------------------------
addDep(dep: Dep) {
  const id = dep.id
  if (!this.newDepIds.has(id)) {
    // ...
    dep.addSub(this) // 执行 dep.addSub
  }
}

dep.depend()会执行Dep.target.addDep(this)方法。接着执行dep.addSub(this)方法,最后会执行this.subs.push(sub)。也就是说,data对象每个key值对应的watcher,都放入到subs中,后续数据如果有变化,可以通知 watcher 需要进行更新。

派发更新

Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: function reactiveGetter () {
    // 收集依赖
  },
  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()
  }
})

当我们对响应的数据做了改变时,会触发set 方法,有两个比较关键的地方是,一个是childOb = !shallow && observe(newVal) ,它是将新设置的值变成一个响应式的值。另一个是dep.notify(),通知watcher进行更新。notify -> update -> queueWatcher

notify () {
  const subs = this.subs.slice()
  // ...
  for (let i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
  }
}
------------------------------------------------
update() {
  // ...
  queueWatcher(this) // 
}
  • queueWatcher

const queue = []
let has = {}

export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    // ...
    queue.push(watcher) // 将要更新的 watcher 添加到 queue 队列
    // ...
    nextTick(flushSchedulerQueue) // 异步更新,相当于 promise.then
  }
}

queueWatcher是将要更新的watcher添加到queue,然后在nextTick后执行flushSchedulerQueue,统一更新这些watcher

  • flushSchedulerQueue

function flushSchedulerQueue () {
  currentFlushTimestamp = getNow()
  flushing = true
  let watcher, id

  queue.sort((a, b) => a.id - b.id)

  // ...
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    watcher.run()
  }
  // ...
}

flushSchedulerQueue主要的一些作用是:

  • 队列排序queue.sort((a, b) => a.id - b.id)
    每次更新按着id(new watcher 时生成) 的大小顺序来更新。
    1、组件更新顺序是,先父组件,然后在是子组件。
    2、自定义的watch,优先于渲染watcher,因为自定义watcher先创建的.computed watcher->自定义 watcher -> render watcher,这个顺序是根据它们初始化顺序来的。
    3、如果一个子组件在父组件的watcher执行期间被销毁,那么这个子组件的watcher会被跳过,所以父组件应该先执行。

  • watcher.run()

run() {
  if (this.active) {
    const value = this.get()
    // ...
    if (
      value !== this.value
    ) {
      const oldValue = this.value
      this.value = value
      // ...
      this.cb.call(this.vm, value, oldValue)
      // ...
    }
    // ...
  }
}

run()方法是执行watcher 的回调函数,先通过this.get()获得当前的新值value,使用this.value 获取旧值oldValue,然后将这两个值传入到回调函数中,所以,在我们在watch 中可以拿到新值和旧值的原因。同时,在执行this.get()方法时,会再次触发vm._update(vm._render(), hydrating),然后进行diff比对,进行视图的更新。

总结

在组件初始化时,vue会使用defineProperty劫持data 数据的getset 方法。在new Watcher 时,会触发data对象的get方法,进行数据依赖收集。当数据改变时,会触发data对象的set方法,进行数据的更新。

@yangrenmu yangrenmu added the mode label Sep 22, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant