1

目录

1:前言

响应式系统式vue框架的核心内容之一,涉及到的知识点和源码部分比较多,这里目的是对主流程做一个梳理。建立起有关响应式的知识体系,细节的地方应该需要不断重复体会!

所以分享的思路是注重于流程介绍,相关函数仅作介绍。

2:什么是响应式

被观测的数据在改动时会通知视图的重新渲染,也就是model->view的流程,那么在vue中常见的被观测数据有哪些呢

  • data
  • props
  • computed
  • watch/method(和前面数据有关)

那么问题在与如何做到修改这些数据会触发视图更新?

3:主要流程

  • 数据监听
  • 依赖收集/派发更新
  • 观察者模式

3.1:数据监听

实现监听主要是做了什么?

使得我们可以在对一个响应式数据在访问,赋值的时候加以处理

实现对data里的数据观测,相关函数如下

  • observe(data)
  • defineReactive(obj,key)
  • Object.defineProperty

vue里的data出于作用域考虑设计成一个函数,这里是的data是最后的返回值为对象,observe会遍历data的每一个值,并递归处理存在嵌套的对象

/* 
模拟实现 observe 函数
*/
function observe (data) {
  let dep = new Dep() // 这是用来收集watcher的订阅器,在闭包中被get,set所引用
  // 需要保证提供的data是一个对象
  if (!data || typeof data !== 'object') return
  // 遍历data数据,逐一处理
  Object.keys(data).forEach((item) => {
  // 递归处理
    if (typeof item === 'object') {
      observe(item)
    } else {
      defineReactive(data,item,data[item])
    }
  })
}

function defineReactive (obj,key,value) {
  Object.defineProperty(obj,key,{
    enumerable: true, // 可枚举,可以遍历访问
    configurable: true, // 可以删除
    get: function reactiveGetter () {
      /* 
      收集依赖,返回值
      */
     console.log('collect')
     return value
    },
    set: function reactiveSetter () {
      /* 
      触发依赖
      */
     dep.notify()
    }
  })
}

3.1.1:存在问题

基于Object.defineProperty实现的数据观测存在一些问题

  • api层面无法监听数对象的增加删除,仅能处理值的改变
  • 无法监听数组变化,所以需要基于Object.create(Array.prototype)修改数组方法,主要是变异数组方法
  • 数组的下标访问修改,或者长度修改无法监听
  • 基于proxy对数据监听加以改进(待处理)

3.2:依赖收集/派发更新

3.2.1实现流程

前面我们大致提到在getter中会收集依赖,下面就来具体看看这个流程怎么实现的

  • 在数据观测的时候我们看到有一个let dep = new Dep() ,这就是订阅者
  • 对于每一个数据都有自己的dep实例
  • watcher是观察者,它是一个用到了data数据的表达式或者函数
// dep和watcher是如何构成观察者模式的
class Dep {
  constructor () {
    // 这是用来保存watcher的数组
    this.subs = []
  }
  addSubs (sub) {
    this.subs.push(sub)
  },
  notify () {
    this.subs.forEach((sub) => {
      sub.update()
    })
  }
}

这里再回顾一下

  • 当触发get的时候,watcher被收集
  • 当set触发的时候,所有watcher都要更新

需要明确一个概念,这里的dep--watcher之间建立的是观察者模式,而不是发布订阅模式!

在很多文章中都会看到一句话“观察者模式(observe)或者叫做发布订阅模式(pub/sub)”,这两者是有区别的,读者在看到类似的文章需要有意识识别!
image.png

这个知识点不是本文的重点,简单列举一二

  • pub/sub模式是基于observe的解耦,多了一层调度中心的概念
  • observe无法控制通知哪些订阅者,只能全部通知
  • 可以看到在触发更新的时候,watcher是无差别都要更新的,但是如果是pub/sub的实现可以决定哪些watcher更新

3.2.2:watcher实现

前面只是介绍了有watcher这个观察者,还需要了解一下大致实现

watcher: 是一个用到了data数据的表达式或者函数,在getter中收集,在setter中逐一触发

class Watcher {
  constructor(obj, key, cb) {
    // Dep.target 获取watcher实例
    Dep.target = this
    this.cb = cb
    this.obj = obj
    this.key = key
    // 这里触发getter
    this.value = obj[key]
    // 这是一个闭包变量,需要清空
    Dep.target = null
  }
  update() {
    // 获得新值
    this.value = this.obj[this.key]
   // 我们定义一个 cb 函数,这个函数用来模拟视图更新,调用它即代表更新视图
    this.cb(this.value)
  }
}

回顾一下流程

  • 在代码中通过表达式/函数操作data值,也就是触发了watcher,在watcher中触发了getter,从而收集当前的watcher
  • b = obj.a 这个操作加入了watcher的这个概念,本来我们认为这只是触发a的getter,b的setter

    // 这里再看看响应式的实现
    function defineReactive (obj, key, value) {
        let dp = new Dep() //一个闭包变量,作为观察者
        Object.defineProperty(obj, key, {
          enumerable: true, //可枚举(可以遍历)
          configurable: true, //可配置(比如可以删除)
          get: function reactiveGetter () {
            console.log('get', value) // 监听
         // 将 Watcher 添加到订阅
           if (Dep.target) {
             dp.addSub(Dep.target) // 新增
           }
            return value
          },
          set: function reactiveSetter (newVal) {
            observe(newVal) //如果赋值是一个对象,也要递归子属性
            if (newVal !== value) {
              console.log('set', newVal) // 监听
              render()
              value = newVal
         // 执行 watcher 的 update 方法
              dp.notify() //新增
            }
          }
        })
      }

    此时还剩一个地方,什么促成了watcher的触发,答案是render。

4:回顾与总结

image.png

  • data的数据经过observe处理重写getter/setter
  • 在render的时候通知watcher,触发getter收集依赖
  • 每一个数据的dep,所有的watcher构成了观察者模式
  • 当setter触发时,通知所有watcher触发更新,再执行render更新virtual-dom
  • 而virtual-dom到视图的过程,会有一个patch/mount的过程,属于虚拟dom的概念,不在此介绍

currygolden
31 声望1 粉丝

人生如逆旅,我亦是行人