【源码分析】Vue的响应数据

1,088 阅读4分钟

前言

上篇文章【源码解析】创建Vue实例时干了什么?提到响应数据这个词,到底什么样的数据是响应数据呢?

小提示:配合源码食用更佳美味。

怎么就是响应数据?

先讲这样一个过程。

在$mont()的时候,会创建Watcher实例的过程,把Dep.target设置为当前Watcher,然后会开始render,render的时候就会读取到响应数据,从而触发get,只有被观察的数据才配置了get,get执行过程中会创建一个Dep实例,此时有了Watcher和Dep,他们会建立关系。他们建立关系之后,当一旦被观察的数据发生改变,就会触发set,set调用dep.notify(),dep则会让跟他有关系的Watcher进行更新。

被观察的数据更改会导致组件进行更新从而影响到dom的改变。这个被观察的数据就是响应数据,而这个get的过程我们叫做依赖收集(后续分析)。

可观察对象

在init过程中 我们调用了initState -> initData

观察data

src/core/instance/state.js

// 观察data
observe(data, true /* asRootData */)

看一下observe函数的实现。进行各种判断,最终返回Observer实例。

export function observe (value: any, asRootData: ?boolean): Observer | void {
  // vnode和不是对象的不需要被观察
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  
  // 性能优化:带有__ob__的是已经被观察的数据 直接返回value.__ob__
  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
  ) {
    // 需要被观察的 直接创建Observer实例
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

Observer类

由于数组和普通对象的劫持方式不同,因此 新建一个Observer类进行来统一。

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

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    // __ob__赋值this,性能优化并表示数据已经被观察
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      // 数组的观察逻辑
      // 重写数组方法进行响应 见下文
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 观察数组
      this.observeArray(value)
    } else {
       // 对象的观察逻辑
      this.walk(value)
    }
  }

  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      // 定义响应数据 obj
      defineReactive(obj, keys[i])
    }
  }

  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      // 观察每一项
      observe(items[i]) 
    }
  }
}

响应数组

对数组方法进行重写, 并且响应数据

src/core/observer/array.js

import { def } from '../util/index'

// 数组原型
const arrayProto = Array.prototype
// 创建新对象 不会对原数组产生影响
export const arrayMethods = Object.create(arrayProto)

// 常会对着7个方法进行劫持,仅有这7个方法会对数组改变
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]


methodsToPatch.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.observeArray(inserted)
    // 触发更新 重中之重就是这个地方 依赖收集的时候分析
    ob.dep.notify()
    return result
  })
})

响应普通对象

调用了defineReactive(obj, keys[i])

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


  // 这个对象可能设置过getter和setter,需要多调用一次用户配置的
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  // 孩子是对象的递归观察
  let childOb = !shallow && observe(val)
  
  // 劫持get和set 响应数据核心
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      // 先掉一下用户的getter
      const value = getter ? getter.call(obj) : val
      
      // 收集依赖相关
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      
      // 一样的值则不需要被赋值
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }

      if (getter && !setter) return
      if (setter) {
        // 用户的setter
        setter.call(obj, newVal)
      } else {
        // 赋值  闭包
        val = newVal
      }
      // 被设置的需要进行观察
      childOb = !shallow && observe(newVal)
      // 触发更新
      dep.notify()
    }
  })
}

系列

结尾

感谢各位的阅读,错误是在所难免的,若有错误,或者有更好的理解,请在评论区留言,再次感谢。希望大家相互学习,共同进步。