源码解读之响应式数据原理

前言

通过珠峰课程的学习来理解 Vue 源码的响应式原理 。

响应式原理

1.核心点: Object.defineProperty
2.默认 Vue 在初始化数据时,会给 data 中的属性使用 Object.defineProperty 重新定义所有属性,当页面获取到对应属性时。会进行依赖收集(收集当前组件的watcher) 如果属性发生变化会通知相关依赖进行更新操作。

什么叫依赖收集 ?
通过Object.defineProperty在重新定义data属性的时候,进行拦截,再进行实际渲染 ; 那实际渲染之前的一系列处理逻辑就是依赖收集上边有说,会在依赖收集的时候为每一个属性创建一个watcher,如果属性发生变化,则通知对应的 watcher 更新视图

来看看源码
1,首先从构造函数初始化看起 ,src/core/instance/index.js 由于我们主要分享响应式数据原理,也就是初始化Vue数据是如何渲染并建立监听的,主要看 stateMixin 模块
在这里插入图片描述
2,进入stateMixin 模块 , 我们直接看向 initData 函数

function initData (vm: Component) { // 初始化data
  let data = vm.$options.data // 获取到用户传入的data数据
  data = vm._data = typeof data === 'function' // 模板语法与标准语法区分获取data数据
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) { 
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
      proxy(vm, `_data`, key) // es6 proxy 代理
    }
  }
  /*
  中间我就跳过了,看意思是非生产环境下,对 props , methods 的一些定义,声明做的判断,不允许重复声明
  另外就是添加了 proxy , es6 新增代理属性 , 包含所有 Object.defineProperty 的功能, 重要的一点是解决 	了不能对象监听的问题等。
  */
  // observe data
  observe(data, true /* asRootData */) // 重点在这儿,为每一个data属性创建一个watcher
}

重点 : observe(data, true /* asRootData */) // 重点在这儿,为每一个data属性创建一个wathcer 接下来我们走进 observer 类

3, src/core/observer/index.js 37 行
就是如下这个 Observer 类,具体做了些什么呢,看代码

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor(value: any) { // 构造函数
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    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 through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) { // 遍历对象
      defineReactive(obj, keys[i]);  // 定义响应式数据,这里可以看到 defineReactive 方法
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) { // 遍历数组
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])  // 观测数组中的每一项 
    }
  }
}

通过上边的代码,我们看到了主要做了一件事,就是区分数组和对象,并对数据和对象遍历,创建观察者 watcher => defineReactive 方法

上边有说到数组会走到 observeArray 方法 , 而方法就是遍历调用 observer ,进行一些数据类型判断和是否被监听过,没有被监听到的则回调回去继续创建观测。

src/core/observer/index.js 113 行

export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    /* 
    不是对象不进行观测,如:不管是模板语法还是标准语法data均是一个对象
    
    data () {   模板语法返回一个对象
      return  {}
    } 
    new Vue ({  标准语法 data 也是一个对象
      data:{}
    })
    */
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { // 已经被监听的,不会重复监听
    ob = value.__b__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) && // 是否可扩展
    !value._isVue
  ) {
    ob = new Observer(value) //重点,重点,重点, 回调回去,观测对象类型
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

如果不是数组呢,则直接走到 defineReactive 方法
src/core/observer/index.js 148 行 defineReactive 响应式数据绑定关键方法 也就是我们常常说到的 Object.defineProperty() 应用的地方 ;所有的初始化数据都会走到这里 。

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

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  let childOb = !shallow && observe(val) // 是数组则递归观测
  Object.defineProperty(obj, key, { // 重点
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () { // 数据的取值
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()  // 收集依赖 watcher
        if (childOb) {
          childOb.dep.depend()  // 收集依赖
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    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() // 触发数据对应的依赖进行更新 , 重点,重点,往下看
    }
  })
}

Dep 所有观察者的集合,也就是 wathcer 的集合,在创建观测的时候为每一个data属性创建了watcher 观察者(Object.defineProperty方法的 get 里边),那么触发数据更新的 set 方法会调用 dep.notify(),看代码
src/core/observer/dep.js 13行,Dep 类看以看见有构造函数,添加watcher ,删除watcher 等方法,那么我看来看 更新方法notify()

export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

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

  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify () { // 通知存储的依赖更新
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    if (process.env.NODE_ENV !== 'production' && !config.async) {
      // subs aren't sorted in scheduler if not running async
      // we need to sort them now to make sure they fire in correct
      // order
      subs.sort((a, b) => a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update() // 依赖中对应修改属性的update方法
    }
  }
}

update () 方法在 src/core/observer/wachter.js 166行 ,其实这里是定义 wachter 观察者类,里边有各种操作 wachter 观察者的方法,如:增加,修改,清除等 。

总结

好了,分析到这里,其实就已经很明了,针对数据响应式原理,总体的过程就是:
在这里插入图片描述

发布了21 篇原创文章 · 获赞 40 · 访问量 6071

猜你喜欢

转载自blog.csdn.net/HiSen_CSDN/article/details/104807792