Web前端之路

vue源码分析之响应式原理(Watcher、Observer、D

2019-07-31  本文已影响0人  慕时_木雨凡

vue作为最受欢迎的前端开发框架。非常值得我们倾心研究一番。

读源码的动力

Vue响应式原理

  1. 使用Object.defineProperty将data数据变成响应式对象,通过Observer给对象添加get 和 set属性
  2. 调用对象数据时会触发getter,改变对象数据时会触发setter
  3. 在getter中进行依赖收集(使用到当前数据的地方会被作为一个Watcher对象处理)
  4. 在setter中通过Dep进行派发更新(通过处理watcher对象Dep.target = watcher从而调用nextTick进行数据更新)

通过Observer处理响应式对象

  1. 初始化Vue实例时调用initState(this)
  2. 在initState(this)函数中调用new Observer(vm.$options.data)处理data数据
  3. 在Observer(val)中通过调用walk()循环data数据并调用defineReactive(obj, key, val)实现响应式绑定
Vue.prototype._init = function() {
    initState(this)
}

function initState(vm) {
  new Observer(vm.$options.data)
}

class Observer {
  constructor (value) {
    this.walk(value)
  }
  walk (obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]])
    }
  }
}

function defineReactive (obj, key, val) {
  val = obj[key]
  Object.defineProperty(obj, key, {
    get: function reactiveGetter () {
      return value
    },
    set: function reactiveSetter (newVal) {
      val = newVal
    }
  })
}

给对象添加getter和setter属性

  1. 利用Object.defineProperty函数的特性给对象添加get和set属性
  2. 监测getter和setter是否存在,不存在就添加get/set属性
function defineReactive (obj, key, val) {
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }
  const getter = property && property.get
  const setter = property && property.set
  Object.defineProperty(obj, key, {
    get: function reactiveGetter () {
      return value
    },
    set: function reactiveSetter (newVal) {
      val = newVal
    }
  })
}

利用Dep在get中进行依赖收集

  1. Dep是Watcher的管理器
  2. Dep.target就是当前的Watcher
  3. depend()中会调用执行Dep.target.addDep(this)
  4. 通过一连串的函数调用最终将当前的watcher存储在subs[]中
  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()
      }
    }
    return value
  }
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
  addDep (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)
      }
    }
  }

在set中进行派发更新

  1. set函数会在修改当前data数据时调用
  2. set中会执行dep.notify()函数 -- 派发更新的重点在notify()中
  3. notify会循环subs得到之前在get中存储的Watcher并调用update()
  4. update中会执行Watcher的run函数
  5. run函数中执行this.cb.call(this.vm, value, oldValue),最终会触发updateComponent函数进行Dom更新
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* 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()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
    notify () {
      const subs = this.subs.slice()
      for (let i = 0, l = subs.length; i < l; i++) {
        subs[i].update()
      }
    }
    update () {
      if (this.lazy) {
        this.dirty = true
      } else if (this.sync) {
        this.run()
      } else {
        queueWatcher(this)
      }
    }
    run () {
      this.cb.call(this.vm, value, oldValue)
    }

Watcher是什么时候创建的?Watcher是如何触发Dom更新的

  1. 在初始化虚拟Dom时initVirtualComponent()函数中会执行 new Watcher(vm, updateComponent, noop, null, true)创建Watcher
  2. Watcher对象的cb数据updateComponent函数,执行this.cb.call(this.vm, value, oldValue)也就是执行了updateComponent
  3. updateComponent()中调用_update(),进而更新Dom
上一篇下一篇

猜你喜欢

热点阅读