Vue源码阅读之Observer观察者模式(三)

在使用Vue开发过程中,数据双向绑定、响应式变化等特性极大降低了开发难度、提高了开发效率。但是我们对底层原理却知之甚少,知其然而不知其所以然。Vue响应式的核心是基于观察者模式实现的,今天我们就从源码上分析Vue内部实现的观察者模式。

一、概念

观察者模式是一种行为模式,定义了对象间一对多的关系。即被观察者发生变动时,会通知依赖他的所有观察者自动更新。观察者模式一般使用三个类,Vue中为Observer、Dep、Watcher。

  • Observer类实现了观察初始逻辑,监听数据变动;
  • Dep类上带有添加、解绑以及通知观察者的方法;
  • Watcher类为具体观察者;

二、Vue中的观察者模式

下面我们以组件内部data数据为例,抽丝剥茧分析观察者模式是如何建立与运作的。

2.1 初始化ObServer、Dep

在组件内部状态state初始化完成后,会调用observer方法为data建立观察逻辑。


Vue实现观察者模式相关代码都在src/core/observer文件下。 

在Observer/index.js文件中,会优先调用new Observer(value)进行初始化。

 如果value是对象,Observer会为对象中的每组key/value值执行defineRective(),添加对象代理拦截。

 

defineReactive中会初始化dep实例,用于管理观察者在数据变动后作出响应。

注意到现在dep中订阅队列subs是空的,即还没有添加观察者。

2.2 将watcher添加到订阅队列中

之前只是初始化组件内部state数据,组件并没有挂载到页面上。挂载阶段才会将相应watcher添加到dep的订阅队列中。

 在mountComponent挂载方法内部,会new Watcher()观察者实例,注意这个updateComponent方法很重要,会被添加到dep订阅队列中在数据变动时执行

 

 Watcher类中会调用get()方法,pushTarget、popTarget两个方法会为Dep类设置target静态属性,即此时Dep.target=当前watcher。

this.getter()方法即上一步传入的updateComponent(),执行后会调用render函数触发页面视图更新。

执行render函数后会获取state的值,这样就会执行observer中defineReactive的getter拦截中。

 

由于Dep.target是有值的,会通过dep,depend()方法为当前dep添加观察者。

当render函数执行完毕后,会调用popTarget重置Dep.target的值。到此Vue基于Observer、Dep、Watcher的观察者模式就建立起来了。 

2.3 工作流程

观察者模式建立完成后,我们可以修改双向绑定的数据看看观察者模式是怎么工作的。

 state数据变动会执行defineReactive中setter的拦截操作,通过dep.notify()通知所有观察者更新数据。

 

最后observer监听到被观察者变动会走到每个观察者实例中,执行run()方法中的get()。继而会调用当前watcher中的getter()方法,这里就是之前初始化时传入的updateComponent(),执行render函数重绘视图。 

三、数据双向绑定实现原理

如果你对上述观察者模式的初始化及工作流程都了解清楚了,那么对于数据双向绑定的底层逻辑想必有了更加深入的认知。下面我们就一步步分析v-model双向绑定的原理。

  1. v-model是Vue默认指令,内部将表单常用事件--input、select等集成到指令中,但是最后都会进行赋值操作,即this.state=newValue,此时state的值发生改变但是视图并没有更新;
  2. 进行赋值后,就会被Observer监听到,进入到defineReactive的setter拦截中。继而dep.notify()发布订阅,通知观察者;
  3. watcher响应变化调用getter(),执行render函数更新视图。实现数据双向绑定逻辑。

四、自定义观察者模式

 综上所述观察者模式的核心是三大类的实现,即Observer、Dep、Watcher。其核心是如何知晓被观察者数据的变动,ES5/ES6的Object.definePeoperty(obj,  key,  des)提供了很好的实现。Vue实现的观察者模式考虑了许多框架自身的东西--Vue实例初始化、VNode创建与更新等。我们可以实现一套简易的观察者模式。

const sharedPropertyDefinition = {
    enumerable: true,
    configurable: true,
    get: null,
    set: null
}

// 初始化观察逻辑
function observer (target, sourceKey, key) {
    sharedPropertyDefinition.get = function proxyGetter () {
      return this[sourceKey][key]
    }
    sharedPropertyDefinition.set = function proxySetter (val) {
      this[sourceKey][key] = val
      subs.forEach(sub => sub.call(null, key, val))
    }
    Object.defineProperty(target, key, sharedPropertyDefinition)
}


// 订阅队列
const subs = new Set()

// 订阅队列添加观察者公共方法
function addSub (fn) {
    subs.add(fn)
}

// 观察者
function watcher (key, val) {
    console.log(`${key}最新值为:${val}`)
}

addSub(watcher)

效果如下:

 

系列相关文章:

Vue源码阅读之Vue构造函数(一)

Vue源码阅读之VNode虚拟DOM(二)

Vue源码阅读之Observer观察者模式(三)

posted on 2021-01-19 17:40  世界之魂  阅读(1067)  评论(0编辑  收藏  举报

导航