Vue源码学习 -- 响应式原理之观察者模式

Feronia ·
更新时间:2024-11-14
· 976 次阅读

1. 观察者模式 概念

观察者模式又被称为 发布-订阅 模式,这种模式定义了对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并完成自动更新。

优点 观察者与被观察者是抽象耦合的 建立了一套触发机制 缺点 被观察者有很多的观察者时,通知更新这一过程会花费很多的时间 观察者和被观察者之间存在循环依赖的话,可能导致系统奔溃 现实中的例子
在这里插入图片描述

从上图可以看到,一个 subject 可以被多个 observer 订阅/观察。当 subject 状态发生变化时,就会通知订阅者进行更新操作。在这样的一个模式中,订阅者的数量以及具体订阅者都是不确定的(订阅者列表是动态变化的),但仍能保证整个机制的正常运转。

2. Vue 的响应式原理

Vue.js 实现响应式的核心是利用了 ES5 中的 Object.defineProperty 方法(这也是为什么 Vue.js 不能兼容 IE8 及以下版本浏览器的原因)。

Vue 会为每个组件实例都创建一个 watcher ,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖(依赖收集)。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染(派发更新)。

代码实现上: 创建 Vue 实例时,会传入一个 function / object 作为 data 属性。Vue 会遍历 data 属性所对应的对象的属性,并借助 Object.defineProperty 方法,进行数据劫持,将每个数据属性都转化为响应式对象(拥有settergetter)。

在 getter 方法中,进行依赖收集 在 setter 方法中,进行派发更新 3. 简要分析源码中的观察者模式部分

说明: 下文分析的是 vue.js 2 的部分源码。

数据劫持,将数据属性转化为响应式对象
核心为 defineReactive 方法 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 if ((!getter || setter) && arguments.length === 2) { val = obj[key] } 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() } // #7981: for accessor properties without setter if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) // 派发更新 dep.notify() } }) } 依赖收集
依赖收集从 getter 中的 dep.depend() 方法说起,下面是 Dep 类中的 depend 方法的实现: depend () { if (Dep.target) { // 订阅 Dep.target.addDep(this) } }

其中调用了 Watcher 实例中的 addDep 方法:

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)) { // 添加当前 watcher 到依赖列表 subs 中 dep.addSub(this) } } } 派发更新
派发更新从 setter 中的 dep.notify() 说起,下面是 Dep 类中的 notify 方法的实现: notify () { // stabilize the subscriber list first const subs = this.subs.slice() if (process.env.NODE_ENV !== 'production' && !config.async) { // subs aren't sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order subs.sort((a, b) => a.id - b.id) } // 遍历依赖列表,通知订阅者更新 for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } }

其中调用了 watcher 实例中的 update 方法:

update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { // 将更新 watcher 放入更新队列中,在下一个 tick 调用 queueWatcher(this) } } 4. 简易实现

源码中考虑的情况比较多,看起来比较复杂。为了便于理解观察者模式使用的核心思想,可以暂时忽略分支逻辑,只查看主要的逻辑。下面的代码,就是忽略各种分支情况后,最最简单的一个实现:

// 观察者模式 class Watcher { constructor(vm, expr, callback) { this.vm = vm; this.expr = expr; this.callback = callback; this.oldValue = this.get(); } get() { Dep.target = this; let value = CompileUtil.getValue(this.vm, this.expr); Dep.target = null; return value; } update() { let newValue = CompileUtil.getValue(this.vm, this.expr) if (this.oldValue !== newValue) { this.callback(newValue) } } } // 发布/订阅 class Dep { constructor() { // 存储所有的观察者 this.subs = []; } // 订阅 addSub(watcher) { this.subs.push(watcher); } // 发布 notify() { this.subs.forEach((watcher) => { watcher.update() }) } } // 实现数据劫持 // 观察类 将传入的数据的定义都改为 defineProperty 的方式 class Observer { constructor(data) { this.observe(data); } observe(data) { if (data && typeof data === 'object') { for (let key in data) { this.defineReactive(data, key, data[key]); this.observe(data[key]); } } } defineReactive(obj, key, value) { this.observe(value); let dep = new Dep(); Object.defineProperty(obj, key, { get() { // 添加观察者 Dep.target && dep.addSub(Dep.target); return value; }, set: (newValue) => { if (newValue !== value) { // 将新值转为 get set 形式后再进行赋值 this.observe(newValue); value = newValue; // 通知观察者 dep.notify(); } } }) } }
作者:来打小怪啊



VUE 学习 观察者模式

需要 登录 后方可回复, 如果你还没有账号请 注册新账号