一.数据劫持

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

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

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

1.数据的初始化

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

  1. function initData (vm: Component) {
  2. let data = vm.$options.data
  3. data = vm._data = typeof data === 'function'
  4. ? getData(data, vm)
  5. : data || {}
  6. // 1.数据不是对象则发生异常
  7. if (!isPlainObject(data)) {
  8. data = {}
  9. process.env.NODE_ENV !== 'production' && warn(
  10. 'data functions should return an object:\n' +
  11. 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
  12. vm
  13. )
  14. }
  15. const keys = Object.keys(data)
  16. const props = vm.$options.props
  17. const methods = vm.$options.methods
  18. let i = keys.length
  19. // 2.校验数据是否在method中已经声明过
  20. while (i--) {
  21. const key = keys[i]
  22. if (process.env.NODE_ENV !== 'production') {
  23. if (methods && hasOwn(methods, key)) {
  24. warn(
  25. `Method "${key}" has already been defined as a data property.`,
  26. vm
  27. )
  28. }
  29. }
  30. // 3.校验数据是否在属性中已经声明过
  31. if (props && hasOwn(props, key)) {
  32. process.env.NODE_ENV !== 'production' && warn(
  33. `The data property "${key}" is already declared as a prop. ` +
  34. `Use prop default value instead.`,
  35. vm
  36. )
  37. } else if (!isReserved(key)) {
  38. // 4.将_data代理到实例上
  39. proxy(vm, `_data`, key)
  40. }
  41. }
  42. // 5.观测数据
  43. observe(data, true /* asRootData */)
  44. }

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

2.观测数据

  1. export function observe (value: any, asRootData: ?boolean): Observer | void {
  2. // 1.如果不是对象直接return
  3. if (!isObject(value) || value instanceof VNode) {
  4. return
  5. }
  6. let ob: Observer | void
  7. // 2.如果已经观测过则直接返回上次观测的实例
  8. if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { //
  9. ob = value.__ob__
  10. } else if (
  11. shouldObserve &&
  12. !isServerRendering() &&
  13. (Array.isArray(value) || isPlainObject(value)) &&
  14. Object.isExtensible(value) &&
  15. !value._isVue
  16. ) {
  17. // 3.如果可以观测就进行观测
  18. ob = new Observer(value)
  19. }
  20. // 4.如果是根数据 vmCount标注为1
  21. if (asRootData && ob) {
  22. ob.vmCount++
  23. }
  24. return ob
  25. }

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

  1. export class Observer {
  2. constructor (value: any) {
  3. this.value = value
  4. this.dep = new Dep()
  5. this.vmCount = 0
  6. def(value, '__ob__', this)
  7. // 1.数组的话重写数组原型方法
  8. if (Array.isArray(value)) {
  9. if (hasProto) {
  10. protoAugment(value, arrayMethods)
  11. } else {
  12. copyAugment(value, arrayMethods, arrayKeys)
  13. }
  14. // 2.观测数组中是对象类型的数据
  15. this.observeArray(value)
  16. } else {
  17. // 3.对象的话使用defineProperty重新定义属性
  18. this.walk(value)
  19. }
  20. }
  21. walk (obj: Object) {
  22. const keys = Object.keys(obj)
  23. for (let i = 0; i < keys.length; i++) {
  24. defineReactive(obj, keys[i])
  25. }
  26. }
  27. observeArray (items: Array<any>) {
  28. for (let i = 0, l = items.length; i < l; i++) {
  29. observe(items[i])
  30. }
  31. }
  32. }

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

3.对象的观测

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

  1. export function defineReactive (
  2. obj: Object,
  3. key: string,
  4. val: any,
  5. customSetter?: ?Function,
  6. shallow?: boolean
  7. ) {
  8. // 1.如果对象不可配置则直接退出
  9. const property = Object.getOwnPropertyDescriptor(obj, key)
  10. if (property && property.configurable === false) {
  11. return
  12. }
  13. // 2.获取getter和setter
  14. const getter = property && property.get
  15. const setter = property && property.set
  16. // 3.重新定义set和get方法
  17. Object.defineProperty(obj, key, {
  18. enumerable: true,
  19. configurable: true,
  20. get: function reactiveGetter () {
  21. const value = getter ? getter.call(obj) : val
  22. return value
  23. },
  24. set: function reactiveSetter (newVal) {
  25. const value = getter ? getter.call(obj) : val
  26. if (newVal === value || (newVal !== newVal && value !== value)) {
  27. return
  28. }
  29. if (getter && !setter) return
  30. if (setter) {
  31. setter.call(obj, newVal)
  32. } else {
  33. val = newVal
  34. }
  35. }
  36. })
  37. }

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

【3】响应式变化原理 - 图1

4.数组的观测

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

  1. export const arrayMethods = Object.create(arrayProto)
  2. const methodsToPatch = [
  3. 'push',
  4. 'pop',
  5. 'shift',
  6. 'unshift',
  7. 'splice',
  8. 'sort',
  9. 'reverse'
  10. ]
  11. methodsToPatch.forEach(function (method) {
  12. // cache original method
  13. const original = arrayProto[method]
  14. def(arrayMethods, method, function mutator (...args) {
  15. const result = original.apply(this, args)
  16. const ob = this.__ob__
  17. let inserted
  18. switch (method) {
  19. case 'push':
  20. case 'unshift':
  21. inserted = args
  22. break
  23. case 'splice':
  24. inserted = args.slice(2)
  25. break
  26. }
  27. // 对新增的属性再次进行观测
  28. if (inserted) ob.observeArray(inserted)
  29. return result
  30. })
  31. })

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

【3】响应式变化原理 - 图2

二.依赖收集

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

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

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

2.1 对象依赖收集

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

  1. class Watcher {
  2. constructor (
  3. vm: Component,
  4. expOrFn: string | Function, // updateComponent
  5. cb: Function,
  6. options?: ?Object,
  7. isRenderWatcher?: boolean
  8. ) {
  9. this.vm = vm
  10. if (isRenderWatcher) {
  11. vm._watcher = this
  12. }
  13. this.cb = cb
  14. this.id = ++uid // uid for batching
  15. this.active = true
  16. this.dirty = this.lazy // for lazy watchers
  17. this.deps = []
  18. this.newDeps = []
  19. this.depIds = new Set()
  20. this.newDepIds = new Set()
  21. this.expression = process.env.NODE_ENV !== 'production'
  22. ? expOrFn.toString()
  23. : ''
  24. // 将updateComponent 放到this.getter上
  25. this.getter = expOrFn
  26. this.value = this.lazy
  27. ? undefined
  28. : this.get() // 执行get方法
  29. }
  30. get () {
  31. pushTarget(this) // Dep.target = 渲染watcher
  32. let value
  33. const vm = this.vm
  34. try {
  35. value = this.getter.call(vm, vm) // 开始取值 那么在get方法中就可以获取到这个全局变量Dep.target
  36. } catch (e) {
  37. if (this.user) {
  38. handleError(e, vm, `getter for watcher "${this.expression}"`)
  39. } else {
  40. throw e
  41. }
  42. } finally {
  43. // "touch" every property so they are all tracked as
  44. // dependencies for deep watching
  45. if (this.deep) {
  46. traverse(value)
  47. }
  48. popTarget() // 结束后进行清理操作
  49. this.cleanupDeps()
  50. }
  51. return value
  52. }
  53. }

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

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

2.2 数组的依赖收集

  1. let childOb = !shallow && observe(val)
  2. get: function reactiveGetter () {
  3. const value = getter ? getter.call(obj) : val
  4. if (Dep.target) {
  5. dep.depend()
  6. if (childOb) { // 如果值也是个对象的话,对这个值进行依赖收集
  7. childOb.dep.depend()
  8. if (Array.isArray(value)) { // 如果是数组对数组中的内容继续进行依赖收集
  9. dependArray(value)
  10. }
  11. }
  12. }
  13. return value
  14. }
  15. // 调用数组方法时进行watcher的更新操作
  16. methodsToPatch.forEach(function (method) {
  17. ob.dep.notify()
  18. })

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

  1. export default class Dep {
  2. constructor () {
  3. this.id = uid++
  4. this.subs = []
  5. }
  6. addSub (sub: Watcher) {
  7. this.subs.push(sub)
  8. }
  9. depend () {
  10. if (Dep.target) {
  11. Dep.target.addDep(this) // 让watcher记住自己
  12. }
  13. }
  14. notify () {
  15. const subs = this.subs.slice()
  16. for (let i = 0, l = subs.length; i < l; i++) {
  17. subs[i].update() // 让存储的watcher依次调用更新方法
  18. }
  19. }
  20. }
  1. class Watcher {
  2. constructor (
  3. vm: Component,
  4. expOrFn: string | Function, // updateComponent
  5. cb: Function,
  6. options?: ?Object,
  7. isRenderWatcher?: boolean
  8. ) {
  9. this.deps = []
  10. this.newDeps = []
  11. this.depIds = new Set()
  12. this.newDepIds = new Set()
  13. this.expression = process.env.NODE_ENV !== 'production'
  14. ? expOrFn.toString()
  15. : ''
  16. // 1.将updateComponent 放到this.getter上
  17. this.getter = expOrFn
  18. this.value = this.lazy
  19. ? undefined
  20. // 2.执行get方法
  21. : this.get()
  22. }
  23. addDep (dep: Dep) {
  24. const id = dep.id
  25. if (!this.newDepIds.has(id)) {
  26. this.newDepIds.add(id)
  27. this.newDeps.push(dep)
  28. if (!this.depIds.has(id)) {
  29. // 3.让dep记录watcher
  30. dep.addSub(this)
  31. }
  32. }
  33. }
  34. update () {
  35. queueWatcher(this)
  36. }
  37. }

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

三.异步更新

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

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

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

【3】响应式变化原理 - 图3