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
方法,進行數據劫持,將每個數據屬性都轉化爲響應式對象
(擁有setter
和 getter
)。
- 在 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();
}
}
})
}
}