vue源码学习(二)变化监听

数据可监测性

数据一变化视图就会跟着改变,再通过监听数据的改变来改变视图。这一行为就叫数据的监听。

  1. 数据监听监听两种数据:
  • Object的监听
  • Arrary的监听(这是因为对于Object数据我们使用的是JS提供的对象原型上的方法Object.defineProperty,而这个方法是对象原型上的,所以Array无法使用这个方法,所以我们需要对Array型数据设计一套另外的变化侦测机制。)

Object的监听

  1. 原理
  • 判断数据的类型,如果是数组的话就跑数组的监听,如果是对象的话就跑对象的监听。
  • 定义defineReactive给数据添加一个getter和setter的属性。
  • getter的作用是获取数据,收集依赖。谁获取了这个数据,而可观测的数据被获取时会触发getter属性,那么我们就可以在getter中收集这个依赖。
  • setter的作用是设置数据,通知收集到的依赖更新更新新的依赖,watcher去更新视图。
  • 更新通过判断真实的dom对象来dipatch判断新的dom节点,构建虚拟dom,通过render函数来渲染dom
  1. 常用的方法

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

  • 配置
  • configurable
    当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。
    默认为 false。
  • enumerable
    当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。
    默认为 false。
    数据描述符还具有以下可选键值:
  • value
    该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。
    默认为 undefined。
  • writable
    当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变。
    默认为 false。
    存取描述符还具有以下可选键值:
  • get
    属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。
    默认为 undefined。
  • set
    属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。
    默认为 undefined。
  1. 代码实现
  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 {
      // walk将每一个属性转换成getter/setter的形式来侦测变化
      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将每一个属性转换成getter/setter的形式来侦测变化
  */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  /**
  * Observe a list of Array items.
  */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

Object.defineProperty修改对象,给对象加上getter和setter方法

//defineReactive中的getter和setter方法
Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: function reactiveGetter () {
    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
    /* 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来set新的值
      setter.call(obj, newVal)
    } else {
      val = newVal // 替换旧的val
    }
    childOb = !shallow && observe(newVal)
    dep.notify() // 通知去更新
  }

定义一个依赖类,对依赖的管理

  //收集类实际是一个数组,创建数组来保存收集到的依赖,然后通过通知依赖来调用notify()执行watcher.update()更新视图
  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()
    }
  }
}

posted @ 2020-10-30 18:12  年轻程序员博客  阅读(124)  评论(0)    收藏  举报