依赖收集: vue 响应式原理

522 阅读3分钟

一、在这之前可以看看观察者模式和发布订阅者模式的概念

1、观察者模式demo:

    var subject={
        observers:[],
        notify(num){
            this.observers.forEach(observer=>{
                observer.update.call(this,num)
            })
        },
        attach(observer){
            this.observers.push(observer)
        }
    }
    var observerMother = {
        update(msg){
            console.log(msg,',妈妈好开心呢')
        }
    }
    var observerSister = {
        update(msg){
            console.log(msg,',哥哥终于结婚了')
        }
    }
    subject.attach(observerMother)
    subject.attach(observerSister)
    subject.notify('哥哥要结婚了')

发布订阅者demo:

    var pubsub ={
        list:{},
        subscribe(key,fn){
            this.list[key] = this.list[key] || []
            this.list[key].push(fn)
        },
        publish(key,...args){
            for(let fn of this.list[key]){
                fn.call(this,...args)
            }
        }
    }
    pubsub.subscribe('data1',(value)=>{console.log('data1变了,需要更新视图')})  
    pubsub.subscribe('data1',(value)=>{console.log('watch到data1变了,需要重新请求数据')})  
    
    pubsub.subscribe('data2',(value)=>{console.log('data3=fn(data1)需要重新计算')})
    
    pubsub.publish('data1',6)
    pubsub.publish('data2',8)

2.从上面可以看出,

  • 观察者模式:
    一般一对多:一个事件变化通知多个观察者;
    一个对象(Subject)维持一系列依赖于它的对象(Observer),当有关状态发生变更时 Subject 对象则通知一系列 Observer 对象进行更新

  • 发布订阅者模式:
    可以多对多: 多个事件对应多个订阅者

3.vue中使用订阅者模式

  • vue中每个响应式数据对应一个 dep 订阅中心,dep有subs属性是 watcher 数组
  • 当数据变化时,会调用dep实例的notify方法
  • new Watcher会传人一个回调cb函数,调用update方法即执行该回调
    function Dep(){
        this.subs = []
    }
    Dep.prototype.addsub = function(watcher){
        this.subs.push(watcher)
    }
    Dep.prototype.notify = function(){
        this.subs.forEach(watcher=>{watcher.undate()})
    }
    

    function Watcher(fn){
        this.cb = fn
        Dep.target = this   //new Watcher()
    }
    Watcher.prototype.update = function(){
        this.cb()
    }
   

二、vue源码

  • observe
  • Observer
  • defineReactive
  • Dep
  • watcher

总结

  • 初始化data等数据,会给每个响应对象创建dep依赖收集中心
  • 初始化vue会new 渲染Watcher
  • 创建虚拟dom会触发所有数据的 getter方法进行watcher收集,数据变化的时候会让dep触发watcher
  • 除了渲染Watcher,还有computed 和watch 会创建watcher

observe

vue在初始化的时候initData,initProps都会调用这个方法;
返回一个Observer对象

function observe(value) {
    if (Object.prototype.toString.call(value) === '[object Object]' || Array.isArray(value)) {
        return new Observer(value)
    }
}

Observer

对象多了一个 __ob__ 的属性,就是对应Observer实例;
给data,props等响应化,对象和数组需要遍历深度响应

const { arrayMethods } = require('./array')

class Observer {
    constructor(value) {
        Object.defineProperty(value, '__ob__', {
            value: this,
            enumerable: false,
            writable: true,
            configurable: true
        })
        if(Array.isArray(value)) {
            value.__proto__ = arrayMethods
            this.observeArray(value)
        } else {
            this.walk(value)
        }
    }

    walk(data) {
        let keys = Object.keys(data)
        for(let i = 0; i < keys.length; i++) {
            const key = keys[i]
            const value = data[key]
            defineReactive(data, key, value)
        }
    }

    observeArray(items) {
        for(let i = 0; i < items.length; i++) {
            observe(items[i])
        }
    }
}

defineReactive

对象内部通过 defineReactive 方法,使用 Object.defineProperty 将属性进行劫持(只会劫持已经存在的属性),数组则是通过重写数组方法来实现。

function defineReactive(data, key, value) {
    const childOb = observe(value)

    const dep = new Dep()

    Object.defineProperty(data, key, {
        get() {
            if (Dep.target) {  //对应watcher
                dep.depend()   //收集watcher

                if (childOb) {
                    childOb.dep.depend()

                    if (Array.isArray(value)) {
                        dependArray(value)
                    }
                }
            }
            return value
        },
        set(newVal) {
            if (newVal === value) return
            observe(newVal)
            value = newVal
            dep.notify()   //通知watcher
        }
    })
}



function dependArray(value) {
    for(let e, i = 0, l = value.length; i < l; i++) {
        e = value[i]
        // 数组每一项对应一个dep收集中心
        e && e.__ob__ && e.__ob__.dep.depend()

        if (Array.isArray(e)) {
            dependArray(e)
        }
    }
}

// array.js
const arrayProto = Array.prototype

const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'reverse',
    'sort'
]
//重写数据上的方法
methodsToPatch.forEach(method => {
    arrayMethods[method] = function (...args) {
        const result = arrayProto[method].apply(this, args)

        const ob = this.__ob__

        var inserted

        switch (method) {
            case 'push':
            case 'unshift':
                inserted = args
                break;
            case 'splice':
                inserted = args.slice(2)
            default:
                break;
        }

        if (inserted) ob.observeArray(inserted)

        ob.dep.notify()

        return result
    }
})



Dep订阅中心

当页面使用对应属性时,每个属性都拥有自己的dep属性,存放他所依赖的 watcher(依赖收集)

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

let uid = 0
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 () {
    // 依赖次数据的watcher执行update()
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// 同一时间,全局唯一的watcher
// 在使用watcher的时候,会把watcher赋值给Dep.target,在响应式数据get的时候push进dep
Dep.target = null
const targetStack = []

//获取watcher的value,会执行pushTarget,给Dep.target赋值
export function pushTarget (_target: ?Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

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

Watcher 观察者

  • 当属性变化后会通知自己对应的 watcher 去更新(派发更新),有三类
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  dep: Dep;
  deps: Array<Dep>;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    
    this.cb = cb
    this.id = ++uid
    this.deps = []
    this.newDeps = []

    this.getter = expOrFn
    if (this.computed) {
      this.value = undefined
      this.dep = new Dep()
    } else {
      this.value = this.get()
    }
  }

  get () {
    //Dep.target = this
    pushTarget(this)

    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      ...
    } finally {
      popTarget()
      this.cleanupDeps()
    }
    return value
  }
  
  // watcher添加到dep
  addDep (dep: Dep) {
     dep.addSub(this)
     this.newDeps.push(dep)
  }

  cleanupDeps () {
     dep.removeSub(this)
     this.newDeps = this.deps
  }
  
  // 派发更新
  update () {
    if (this.computed) {
      if (this.dep.subs.length === 0) {
        this.dirty = true
      } else {
        this.getAndInvoke(() => {
          this.dep.notify()
        })
      }
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }  
  
  
  // queueWatcher维护watcher队列,依次执 watcher.run()
  run () {
    if (this.active) {
      this.getAndInvoke(this.cb)
    }
  }

  getAndInvoke (cb: Function) {
    const value = this.get()
    cb.call(this.vm, value, oldValue)
  }

}

queueWatcher 会执行queue.push(this) 把当前watcher推入队列,最终执行watcher.run()

getAndInvoke 对于渲染 watcher ,this.cb如下:

updateComponent = () => {
  vm._update(vm._render(), hydrating)
}

flushSchedulerQueue 更新队列

let flushing = false
let index = 0
function flushSchedulerQueue () {
  flushing = true
  let watcher, id

  // watcher队列先后顺序:父先于子,custom watcher先于渲染 `watcher`
  queue.sort((a, b) => a.id - b.id)

  // 循环执行 watcher.run()
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    watcher.run()
  }
  ...
  // 更新队列状态清空
  resetSchedulerState()

  // 触发 更新钩子函数
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)
}

参考 ustbhuangyi.github.io/vue-analysi…