observer
實現了一個簡單的訂閱觀察者類,這個類被用於在資料修改時通知各個 watcher 以觸發對應的更新,從而實現資料的響應,這個會在後續的資料響應化裡提到。
src/observer
src/observer/dep.js
dep 被 watcher 引用
A dep is an observable that can have multiple
directives subscribing to it.
(dep 是一個可以被多個指令訂閱的觀察者)
// src/observer/dep.js
import { toArray } from `../util/index`
let uid = 0
export default function Dep () {
this.id = uid++
this.subs = []
}
Dep.target = null
Dep.prototype.addSub = function (sub) {
this.subs.push(sub)
}
Dep.prototype.removeSub = function (sub) {
this.subs.$remove(sub)
}
Dep.prototype.depend = function () {
Dep.target.addDep(this)
}
Dep.prototype.notify = function () {
var subs = toArray(this.subs)
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
程式碼量很少,但是幾個點暫時不明,一個是 this.subs.$remove(sub)
, 貌似是給陣列新增了一個 remove 方法, target 擁有 addDep 方法, subs的 item 擁有 update 方法
更正,dep 類用來做依賴採集,是一個簡單的訂閱-觀察者模式,而 target 和 subs 用於存放 watcher,dep 類用於資料響應化的實現,在後續會講到這個
src/observer/array.js
實現了 arrayMethods 類,該類繼承了 array 物件:
...
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
...
同時還對 array 擴充了 $set $remove 方法, 這就是在 dep.js 裡使用到的 $remove 方法
def(
arrayProto,
`$set`,
function $set (index, val) {
if (index >= this.length) {
this.length = index + 1
}
return this.splice(index, 1, val)[0]
}
)
def(
arrayProto,
`$remove`,
function $remove (item) {
/* istanbul ignore if */
if (!this.length) return
var index = indexOf(this, item)
if (index > -1) {
return this.splice(index, 1)
}
}
)
def 方法的實現:
export function def (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
除此之外, arrayMethods 重寫了
`push`,
`pop`,
`shift`,
`unshift`,
`splice`,
`sort`,
`reverse`
這些原型方法:
;[
`push`,
`pop`,
`shift`,
`unshift`,
`splice`,
`sort`,
`reverse`
]
.forEach(function (method) {
// cache original method
var original = arrayProto[method]
def(arrayMethods, method, function mutator () {
// avoid leaking arguments: 避免洩露引數 arguments
// http://jsperf.com/closure-with-arguments
var i = arguments.length
var args = new Array(i)
while (i--) {
args[i] = arguments[i]
}
var result = original.apply(this, args)
var ob = this.__ob__
var inserted
switch (method) {
case `push`:
inserted = args
break
case `unshift`:
inserted = args
break
case `splice`:
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})
通過改寫陣列的幾個原型方法,從而能夠在開發者操作 data 陣列時,能夠觸發更新事件: ob.dep.notify()
src/observer/index.js
index.js 實現了 Observer 類, 觀察者類繫結了每一個被觀察的物件,一旦繫結,Observer 類會將目標物件的屬性property keys 轉化為 getter/setters, 以收集依賴關係和分派更新
/**
* Observer class that are attached to each observed
* object. Once attached, the observer converts target
* object`s property keys into getter/setters that
* collect dependencies and dispatches updates.
*
* @param {Array|Object} value
* @constructor
*/
export function Observer (value) {
this.value = value
this.dep = new Dep()
def(value, `__ob__`, this)
if (isArray(value)) {
var augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}
}
...
從建構函式可以知道之前程式碼裡一直出現的 _ob_ 屬性就是指 Observer 類
其中 hasProto 實現在 util/env 中:
export const hasProto = `__proto__` in {}
實現的原理是利用 Object 的 Geeter 和 Setter:
/**
* Define a reactive property on an Object.
*
* @param {Object} obj
* @param {String} key
* @param {*} val
*/
export function defineReactive (obj, key, val) {
var dep = new Dep()
// cater for pre-defined getter/setters
var getter, setter
if (config.convertAllProperties) {
var property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
getter = property && property.get
setter = property && property.set
}
var childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
}
if (isArray(value)) {
for (var e, i = 0, l = value.length; i < l; i++) {
e = value[i]
e && e.__ob__ && e.__ob__.dep.depend()
}
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val
if (newVal === value) {
return
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = observe(newVal)
dep.notify()
}
})
}