VUE源码学习第十篇--响应式原理(观察者与发布器)

一、总述

     vue实现了响应式的双向绑定,相对于传统的dom操作,我们仅需要关心model的逻辑处理,vue将帮助我们完成view端的映射和渲染,这种模式大大提高了编程的效率。接下来的两个章节我们将一起了解下vue响应式的实现,在学习源码前,建议先阅读VUE探索第一篇--双向绑定原理了解响应式的原理。

    vue的响应式采用的是观察者+发布订阅相结合的模式来实现。模型的示意图如下:

这种设计非常像军队的组织,我们来做个角色扮演。

1、observe(观察者),侦查兵,观察敌情的变化,一旦有异动,要立即报告。vue的响应设计中,利用defineProperty劫持属性数据的get,set方法,实现变化的监听。

2、dep(发布类),指挥所,所谓知己知彼,百战不殆,一方面要充分了解自己的部队部署,另一方面,要对敌情的变化快速做出决策。vue的响应设计中,发布类存储每个属性数据相关的订阅类,一旦数据有更新,通知关联的订阅类执行更新操作。

3、watcher(订阅类),野战军,根据指挥部下达的的命令,实施作战。vue的响应设计中,属性表达式封装为订阅类,接受发布类的数据变更通知,通过回调,实现视图的更新。

二、observe观察者

第五章节,我们讲到initProvide,initProps,initData等方法时,出现了很多observer,defineReactive等方法,当时仅让大家做初步了解,那么本篇就为大家详细分析其过程。下面我们就已initData为例,详细了解下其实现原理。

function initData (vm: Component) {
  ...
  observe(data, true /* asRootData */)
}

接下来我们以下面定义的data为例,看下observe是如何创建的。

data:{
      msg:'this is msg'
      items:[
      {id:1},
      {id:2},
      {id:3}]
    }

1、observe

observe该方法位于src/core/observe/index.js

export function observe (value: any, asRootData: ?boolean): Observer | void {
  //1、value不是对象,或者是VNode类型,则不需要建立observe对象
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  //2、判断属性是否创建过observe,如已存在则返回对象,否则需要创建
  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
  ) {
     //3、核心代码,创建Observer对象
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

该方法的入参value就是定义的data值,asRootData表示是否是根data。

(1)、传入的属性如果不是对象(比如是常量),或者是VNode类型,则不需要创建observe;

(2)、判断属性是否创建过observe,如果已存在,则直接返回;

(3)、符合一系列的判断条件后,进入核心代码,调用new Observer创建对象。

Observer是一个class类。

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

  constructor (value: any) {
    //1、初始化
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    //2、定义属性‘__ob__’,def是defineProperty的简单封装
    def(value, '__ob__', this)
    //3、对于Array对象,则循环创建observer对象
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value)
    } else {//4、为每个属性注入getter/setter方法
      this.walk(value)
    }
  }

  /**
   * Walk through each property 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])
    }
  }

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

我们来看下构造函数。

(1)、初始化相关的变量,包括value(属性对象),dep(发布类对象),vmcount(计数器)。

(2)、在属性对象上定义一个"_obj_"属性,保存observer对象。属性是否创建observer对象,就是通过该值判断的。

(3)、属性对象如果是数组,则调用observeArray循环创建observer对象。

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

(4)、对于非数组,则调用walk方法为每个属性注入getter/setter方法,实现数据监听。

实例中data为非数组,进入walk执行方法。

2、walk

walk的方法比较简单,循环每个属性数据对象,调用defineReactive方法

walk (obj: Object) {
    const keys = Object.keys(obj)
    //循环属性对象,每个属性对象增加监听
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

如实例data中,key值分别是msg,items,进行两次循环,分别调用defineReactive。

3、defineReactive

defineReactive是实现观察者的核心类,在props和provide初始化中,就跳过了observer,而直接调用该方法。通过Object.defineProperty对数据属性的get和set方法的劫持,实现依赖收集和通知更新。

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  //1、创建属性数据发布器
  const dep = new Dep()

 ...
  
  ///2、如果属性的值也是对象,递归为每个对象创建observe对象
  let childOb = !shallow && observe(val)
  //3、核心方法,利用defineProperty方法进行数据劫持。
  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()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      //通知更新
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

(1)为该属性创建发布器对象。

(2)如果属性值也是对象,则递归调用observe创建对象,如本例中的items。

(3)通过defineProperty,对get,set方法注入劫持,dep.depend实现依赖收集,dep.notify实现通知更新。

此data实例中,最终生成的属性对象与observe属性关系图如下:

接下来,我们看下是如何通过发布器实现依赖收集和通知更新的。

三、Dep发布器

从上我们知道每个属性数据对象包含一个Observe对象,observe中会创建一个Dep对象。dep类位于src/core/observe/dep.js

export default class Dep {
  static target: ?Watcher;//当前的watcher
  id: number;//属性
  subs: Array<Watcher>;//watcher类集合

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

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

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

  //Dep.target为当前的watcher
  //将当前的watcher添加到相应的发布器Dep,进行依赖收集
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
  //通知相关的watcher类更新
  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
const targetStack = []

export function pushTarget (_target: ?Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

export function popTarget () {
  Dep.target = targetStack.pop()
}

1、Dep类定义了三个属性变量,并在构造函数中进行初始化。

(1)、target,当前的watcher对象。

(2)、id,Dep对象的唯一标示,该标示自增长。

(3)、subs,Watcher订阅对象的集合。

2、定义了一系列的方法,add,remove,实现watcher集合的添加和删除。depend,将当前的watcher添加到集合中并完成依赖收集;notify,循环集合,调用每个的watcher的update方法实现更新。(如何实现收集和更新,我们下一篇详细分析)

3、定义了全局变量Dep.target,targetStack来保存当前的watcher对象。

经过dep的初始化,实例的observe与dep的关系图如下:

此时,我们还没有在对象上添加任何watcher,所以sub集合都是空集。

四、总结

本章节主要通过对initdata的过程介绍了响应式的原理,并通过实例分析了observe与dep的源代码,其实对于props,provide,过程都是类似的,大家可以自行学习。接下来我们要对watcher进行分析。

发布了33 篇原创文章 · 获赞 95 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/tcy83/article/details/89597654