Vue2 源码分析(三): 观察者模式 & 深入响应式原理

1,861 阅读4分钟

千呼万唤始出来,终于到了数据响应式

个人认为说到Vue2的数据响应式,该从两个方面去分析:

1.  观察者模式;

2.  Object.defineProperty();

一、观察者模式:

举个🌰:
    AB、C 想知道每天天气情况,三者都订阅了墨迹APP的推送通知,墨迹每天定时发送天气预报并推送到AB、C的手机上;
    A看到可能要下雨的天气推送,做的操作是:带把伞;B操作是:带雨衣;C 操作是: 不一定下,啥也不干。
    
   上面"墨迹APP"作为被观察者的角色出现,管理AB、C这些订阅者,并且在天气变化时通知订阅者,
   AB、C 作为订阅者的角色出现,也是具体的观察者(观察天气预报),在接收到被观察者的通知时做出不同的反应。 

概念:

观察者模式,目标和观察者是基类,目标提供维护观察者的一系列方法,观察者提供更新接口,具体观察者和具体目标继承各自的基类,然后具体观察者把自己注册到具体目标里,在具体目标发生变化时候,调度观察者的更新方法
  • 观察者模式是发布/订阅模式中特殊的一种,包含四个角色:

       观察者Observer、目标Subject、具体观察者(订阅者,继承自观察者)、具体被观察者(发布者, 继承自目标);
    
  • 不同于传统发布/订阅模式的是:

      观察者和目标之间不存在第三方的调度,观察者和目标存在关联(内部基于发布订阅模式)
    
  • Subject 目标(被观察者)的要求:

      维护一系列观察者,方便添加或删除观察者
    
  • Observer 观察者的要求:

      提供更新接口,供观察者状态变化时得到通知
    
  • ConcreteSubject 具体目标(发布者):

      状态发生变化时,向Observer发出通知,储存ConcreteObserver的状态       
    
  • ConcreteObserver 具体观察者(订阅者):

      存储一个指向 ConcreteSubject的引用,实现一个更新接口,以保证自身状态总是和目标状态保持一致
    

WechatIMG269.png

具体实现一个观察者模式:

1、我们先定义一个ObserverList来模拟目标管理一系列的Observer

这里要明确的是帮助谁去管理观察者?(目标/被观察者,也就是说 ObserverList 的用户是目标)

module.exports = class ObserverList {
  constructor() {
    this.observerList = []
  }

  add(obj) {
    return this.observerList.push(obj)
  }

  empty() {
    this.observerList = []
  }

  count() {
    return this.observerList.length
  }

  get(index) {
    return this.observerList[index]
  }

  insert(obj, index) {
    let pointer = -1
    if (!index || index === this.observerList.length) {
      this.observerList.splice(index, 0, obj)
      pointer = index
    }
    return pointer
  }

  indexAt(obj, startIndex) {
    let i = startIndex, pointer =-1
    while(i < this.observerList.length) {
      if(obj === this.observerList[i]) {
        pointer = i
      }
      i++
    }
    return pointer
  }

  removeAt(index) {
    this.observerList.splice(index, 1)
  }
}

2、模拟目标Subject

const ObserverList = require('./observer-list')
module.exports = class Subject {
  constructor() {
    this.observers = new ObserverList()
  }

  addObserver(observer) {
    this.observers.add(observer)
  }

  removeObserver(observer) {
    this.observers.removeAt(this.observers.indexAt(observer, 0))
  }

  notify(ctx) {
    for(let i = 0; i < this.observers.count(); i++) {
      this.observers.get(i).update(ctx)
    }
  }
}

3、模拟Observer

module.exports =  class Observer {
  constructor(name) {
    this.name = name
  }

  update(ctx) {
    // ...具体更新逻辑
    console.log(`${this.name}: ${ctx}`);
  }
}

4、测试观察者模式

// observer-mode.js
const Observer = require('./observer')
const Subject = require('./subject')

const subInstance = new Subject()
class ObserverA extends Observer {
  constructor(name) {
    super(name)
  }
  update() {
    console.log(`${this.name}: 带伞`)
  }
}

class ObserverB extends Observer {
  constructor(name) {
    super(name)
  }
  update() {
    console.log(`${this.name}: 带雨衣`)
  }
}

class ObserverC extends Observer {
  constructor(name) {
    super(name)
  }
  update() {
    console.log(`${this.name}: 大头大头,下雨不愁`)
  }
}
subInstance.addObserver(new ObserverA('A'))
subInstance.addObserver(new ObserverB('B'))
subInstance.addObserver(new ObserverC('C'))
subInstance.notify("今天可能要下雨")

打印结果如下:

image.png

总结:

  • 目标对观察者:一对多
  • 一个目标在不知道自己具体有多少观察者时适用

在实现了观察者模式后,我们可以思考一下,日常Vue开发中,谁应该作为观察者出现,谁又该作为订阅这出现?

二、Vue2 的数据响应式

在数据驱动的MVVM模式下,视图接收数据的变化触发更新,
那么是否可以初步判断:数据作为 目标/被观察者/发布者 出现,视图作为 具体观察者/订阅者出现。
结合Object.defineProperty,何时收集依赖:get的时候,何时去通知更新:set的时候。**
  • 源码里涉及到数据双向绑定的,主要涉及到了三大块:Observer, Dep, Watcher
  • Vue 源码里的 Observer类承担了目标/被观察者的角色,Dep更多的承担了ObserverList的角色,但还兼顾了notify,Wacther承担了订阅者/具体的观察者 的角色
  • 带着上面的判断,这里会接着上一篇,从this.initState作为入口去讲

initState:

export function initState (vm: Component) {
  vm._watchers = [] // 初始化了_watchers 变量为数组
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props) // 初始化属性
  if (opts.methods) initMethods(vm, opts.methods) // 初始化方法
  if (opts.data) {
    initData(vm) // 初始化data
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed) // 初始化computed
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch) // 初始化watch
  }
}

initProps, initData 等实现响应式都是相似的,我们用initData 分析

initData值得关注的点:

   1proxy(vm, `_data`, key)
   2observe(data, true /* asRootData */) 

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? 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)
    }
  }
  // 这里加了一些打印,方便去理解
  // observe data
  const vmDataObserveInstace = observe(data, true /* asRootData */)
  console.log('after invoke initData:')
  console.log('1: the vm data is instanceof Observer', vmDataObserveInstace) // true
  console.log('1: the vm data is instanceof Observer', vmDataObserveInstace instanceof Observer) // true
  console.log('2: proxy _data every key',)
}

先看 proxy(vm, _data, key)

export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

调用proxy后, 对data中的每个key, 在vm 上建立数据劫持,等同于:

get时: vm.xxx => return vm._data.xxx
set时: vm.xxx = 2 => vm._data.xxx = 2

在下一篇demo篇也会说到这块,接下来就是大篇幅的源码,也可下看下最后的流程图,再看源码,可能更好理解,或者看完第四篇demo,再回来看流程图。

看下observe的代码:


/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 尝试为value创建一个observer实例,如果成功被观察,则返回新的观察者或者如果只已经存在,则返回已经存在的
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    observerState.shouldConvert &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob // 返回值为一个Observer 实例
}

对于initData中调用observe(value, true),实现的功能就是将data中的对象初始化为一个Observer实例

Vue源码:Observer 的实现代码: (转译过的ES5代码)

/***
** By default, when a reactive property is set, the new value is*\
** also converted to become reactive. However when passing down props,*\
** we don't want to force conversion because the value may be a nested value*\
** under a frozen data structure. Converting it would defeat the optimization.*\
 默认情况下,当一个响应式属性被设置的时候,新的值有会被转化为可响应的,但是,当我们传递props 的时候,我们不想被强制转化,原因是在冻结的数据结构下传递的数据可能是嵌套的值,强制转化会破会优化
**/  

var observerState = {
    shouldConvert: true
};
/**
* Observer class that are attached to each observed
* object. Once attached, the observer converts target
* object's property keys into getter/setters that
* collect dependencies and dispatches updates.
Observer 类和每个被观察的数据挂接,一旦关联,观察者会将目标数据的keys 都转化为getter 和setter,一边手机依赖和触发更新
*/   

构造函数:
var Observer = function Observer(value) {  // value 就是你真正需要观察的数据
    this.value = value;                    
    this.dep = new Dep();
    this.vmCount = 0;
    def(value, '__ob__', this); // 每个Observer 的实例都有__ob__属性

    if (Array.isArray(value)) {
        var augment = hasProto ? protoAugment : copyAugment;
        augment(value, arrayMethods, arrayKeys);
        this.observeArray(value);
    } else {
        this.walk(value);
    }
};
/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/ 遍历每个key,将他们转化为getter/setter, 该方法仅在值类型是对象的时候别调用

Observer.prototype.walk = function walk(obj) {
    var keys = Object.keys(obj);
    for (var i = 0; i < keys.length; i++) {
        defineReactive(obj, keys[i], obj[keys[i]]);
    }
};
/**
* Observe a list of Array items.
*/
Observer.prototype.observeArray = function observeArray(items) {
    for (var i = 0, l = items.length; i < l; i++) {
        observe(items[i]);
    }
};

Observer根据参数的类型,分别调用walk 和 observeArray 来遍历参数中的每个key, 将它们转换为响应式数据,无论哪种方式,最终肯定都是通过defineReactive转化为可响应数据的

看下defineReactive(obj, keys[i], obj[keys[i]]):

/**
 * Define a reactive property on an Object.
 */
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

  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()
        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()
    }
  })
}

defineReactive 中终于看到了 Object.defineProperty(),每个key都声明了一个dep实例 在getter中调用了depend,在setter中调用了notify, dep.depend(),看下dep 的代码:

Dep 的实现:(接受的参数都是watcher 实例)

未编译前的源码:

src/core/observer/dep.js
/* @flow */

import type Watcher from './watcher'
import { remove } from '../util/index'

let uid = 0

/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
export default class Dep {
  static target: ?Watcher; // 通过target属性指向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)
  }

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

  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()
}

dep.depend: Dep.target.addDep(this),实际就是 watcher.addDep(dep) // 订阅者收集依赖

再看下watcher.addDep():

Watcher 的实现


src/core/observer/watcher.js
/* @flow */

import { queueWatcher } from './scheduler'
import Dep, { pushTarget, popTarget } from './dep'

import {
  warn,
  remove,
  isObject,
  parsePath,
  _Set as Set,
  handleError
} from '../util/index'

import type { ISet } from '../util/index'

let uid = 0

/**
 * A watcher parses an expression, collects dependencies,
 * and fires callback when the expression value changes.
 * This is used for both the $watch() api and directives.
 watcher 解析表达式,收集依赖并且当表达式值变化时触发回调,这被用来$watch()的调用和指令
 */
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean; // true表示组件中定义的watch配置,false 表示每个组件的render watcher以及为computed 创建的watcher
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>; // Dep 类型的数组
  newDeps: Array<Dep>;
  depIds: ISet;
  newDepIds: ISet;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: Object
  ) {
    this.vm = vm
    vm._watchers.push(this) // _watchers 是在initState 函数中初始化为空数组的
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function () {}
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get() // 这就挺重要,获取value 的时候可能会触发get的调用,之后我们会结合例子去讲
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    pushTarget(this) // 执行完,targetStack 中存放了该watcher
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

  /**
   * Add a dependency to this directive.
   */
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)  // 添加watcher 到订阅者列表
      }
    }
  }

  /**
   * Clean up for dependency collection.
   */
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   每个订阅者的职责就是提供更新接口,供发布者在notify 的时候调用
   */
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }

  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   调度器接口,将被调度器调用
   */
  run () {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */
  evaluate () {
    this.value = this.get()
    this.dirty = false
  }

  /**
   * Depend on all deps collected by this watcher.
   * 依赖于所有deps被该观察者收集
   */
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

  /**
   * Remove self from all dependencies' subscriber list.
   */
  teardown () {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}

这里把watcher 和dep 相互建立了关系,

建立Dep与Watcher的关系是为了通知更新
建立 Watcher 与 Dep 的关系是为了清除watcher 时调用

流程图

下图为initData 为例的流程图:

processcn.png

源码较长,梳理成以下流程图:

WechatIMG270.png

接下来我们会结合具体demo 一步一步讲

Vue2 源码分析(一): 入口platforms/web/runtime/index.js & initGlobalAPI

Vue2 源码分析(二)# Vue构造函数 & this._init

Vue2 源码分析(四): 深入响应式原理-demo篇