从vue源码中学习观察者模式

本文深入探讨了观察者模式,通过实例解析Vue.js如何利用观察者模式实现数据劫持,包括递归遍历、发布/订阅、Observer和Watcher的实现,以及消息封装的重要性。
摘要由CSDN通过智能技术生成

摘要:源码解读设计模式系列文章将陆陆续续进行更新中 ~
摘要:源码解读设计模式系列文章将陆陆续续进行更新中 ~

观察者模式

首先话题下来,我们得反问一下自己,什么是观察者模式?

概念

观察者模式(Observer):通常又被称作为发布-订阅者模式。它定义了一种一对多的依赖关系,即当一个对象的状态发生改变的时候,所有依赖于它的对象都会得到通知并自动更新,解决了主体对象与观察者之间功能的耦合。

讲个故事

上面对于观察者模式的概念可能会比较官方化,所以我们讲个故事来理解它。

  • A:是密探,代号 001(发布者)
  • B:是通信人员,负责与 A 进行秘密交接(订阅者)
  1. A 日常工作就是在明面采集一些情报
  2. B 则负责暗中观察着 A
  3. 一旦 A 传递出一些有关消息(更多时候需要对消息进行封装传递,后面根据源码具体分析)
  4. B 会立马订阅到该消息,然后做一些相对应的变更,比如说通知做一些事情应对的一些动作。

适用性

以下任一场景都可以使用观察者模式

  1. 当一个抽象模型有两个方面,其中一个方面依赖于另一方面。讲这两者封装在独立的对象中可以让它们可以各自独立的改变和复用
  2. 当一个对象的改变的时候,需要同时改变其它对象,但是却不知道具体多少对象有待改变
  3. 当一个对象必须通知其它对象,但是却不知道具体对象到底是谁。换句话说,你不希望这些对象是紧密耦合的。

vue 对于观察者模式的使用

vue 使用到观察者模式的地方有很多,这里我们主要谈谈对于数据初始化这一块的。

var vm = new Vue({
   
  data () {
   
    return {
   
      a: 'hello vue'
    }
  }
})

1、实现数据劫持

上图我们可以看到,vue 是利用的是 Object.defineProperty() 对数据进行劫持。 并在数据传递变更的时候封装了一层中转站,即我们看到的 DepWatcher 两个类。

这一小节,我们只看如何通过观察者模式对数据进行劫持。

1.1、递归遍历

我们都知道,vue 对于 data 里面的数据都做了劫持的,那只能对对象进行遍历从而完成每个属性的劫持,源码具体如下

walk (obj: Object) {
   
  const keys = Object.keys(obj)
  // 遍历将其变成 vue 的访问器属性
  for (let i = 0; i < keys.length; i++) {
   
    defineReactive(obj, keys[i], obj[keys[i]])
  }
}

参考vue源码视频讲解:进入学习
1.2、发布/订阅

从上面对象的遍历我们看到了 defineReactive ,那么劫持最关键的点也在于这个函数,该函数里面封装了 gettersetter 函数,使用观察者模式,互相监听

// 设置为访问器属性,并在其 getter 和 setter 函数中,使用发布/订阅模式,互相监听。
export function defineReactive (
  obj: Object,
  key: string,
  val: any
) {
   
  // 这里用到了观察者(发布/订阅)模式进行了劫持封装,它定义了一种一对多的关系,让多个观察者监听一个主题对象,这个主题对象的状态发生改变时会通知所有观察者对象,观察者对象就可以更新自己的状态。
  // 实例化一个主题对象,对象中有空的观察者列表
  const dep = new Dep()

  // 获取属性描述符对象(更多的为了 computed 里面的自定义 get 和 set 进行的设计)
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
   
    return
  }

  const getter = property && property.get
  const setter = property && property.set

  let childOb = 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
      if (newVal === value || (newVal !== newVal && value !== value)) {
   
        return
      }
      if (setter) {
   
        setter.call(obj, newVal)
      } else {
   
        val = newVal
      }
      childOb = observe(newVal)
      dep.notify()
    }
  })
}

1.3、返回 Observer 实例

上面我们看到了observe 函数,核心就是返回一个 Observer 实例

return new Observer(value)

2、消息封装,实现 “中转站”

首先我们要理解,为什么要做一层消息传递的封装?

我们在讲解观察者模式的时候有提到它的 适用性 。这里也同理,我们在劫持到数据变更的时候,并进行数据变更通知的时候,如果不做一个"中转站"的话,我们根本不知道到底谁订阅了消息,具体有多少对象订阅了消息。

这就好比上文中我提到的故事中的密探 A(发布者) 和 B(订阅者)。密探 A 与 B 进行信息传递,两人都知道对方这么一个人的存在,但密探 A 不知道具体 B 是谁以及到底有多少(订阅者)订阅着自己,可能很多都订阅着密探 A 的信息,so 密探 A(发布者) 需要通过暗号 收集到所有订阅着其消息的(订阅者),这里对于订阅者的收集其实就是一层封装。然后密探 A 只需将消息发布出去,而订阅者们接受到通知,只管进行自己的 update 操作即可。

简单一点,即收集完订阅者们的密探 A 只管发布消息, B 以及更多的只管订阅消息并进行对应的 update 操作,每个模块确保其独立性,实现高内聚低耦合这两大原则。

废话不多说,我们接下来直接开始讲 vue 是如何做的消息封装的

2.1、Dep

Dep,全名 Dependency,从名字我们也能大概看出 Dep 类是用来做依赖收集的,具体怎么收集呢。我们直接看源码

let uid = 0

export default class Dep {
   
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
   
    // 用来给每个订阅者 Watcher 做唯一标识符,防止重复收集
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值