在vue原理中,最重要的部分就是如何實現資料的觀測,依賴的收集,檢視的更新。本文講的就是Observer, Dep, Watcher這三個的簡單實現。 pub(publish)表示釋出者,sub(subscribe)表示訂閱者, cb(callback)表示回撥函式 如果你覺得這篇講的對你有所幫助,請幫我點個star
observer的實現
Observer的作用簡單來說就是讓object物件的屬性都用Object.defineProperty()來進行定義,這樣當獲取object的屬性,或者修改屬性的時候,就能夠觸發get,set達到資料的觀測的效果。
class Observer {
constructor(value) {
this.value = value
this.walk(this.value)
}
walk (value) {
// 遞迴遍歷value的屬性
Object.keys(value).forEach((key) = > {
defineReactive(value, key, value[key])
})
}
}
function defineReactive(obj, key ,val) {
let childOb = observe(val)
Obeject.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
console.log('')
return val
},
set(newVal) {
console.log('')
val = newVal
childOb = observe(val)
}
})
}
function observe (value) {
if (typeof value === 'object' && !Array.isArray(value)) {
value = new Observer(value)
}
}
複製程式碼
defineReactive的作用就是給物件的屬性進行簡單的資料觀測,一旦值獲取或者設定就會觸發一些行為.因為一個物件的屬性可能還是物件,所以在這裡我們新增observe函式來遍歷值,讓一個物件的屬性的屬性還是可以進行觀測的,簡單呢來說的意思就是讓所有屬性都可以進行忽略。當然在實際情況中,我們還需要考慮陣列的情況,但都大同小異。 這樣做程式碼似乎有點醜,我們在設定屬性觸發set會發生console.log()函式,有沒有一種更加智慧的方式來實現通知變化呢。這裡我們就需要用訊息訂閱器來進行實現,這樣做我們就不需要通過觀察console.log()輸出的值來看進行的情況,我們只需要在set方法裡邊加一個通知,一旦值發生變化,就通知外邊值發生了改變
Dep的實現:
Dep的作用就是用來收集屬性值的變化,一旦set方法觸發的時候,就更新檢視。那就準備一個陣列來進行收集吧! 下面是Dep的實現:
class Dep {
constructor() {
this.subs = []
}
addSub (sub) {
this.subs.push(sub)
}
notify () {
const subs = this.subs.slice()
subs.forEach((sub) => {
sub.update() // 檢視更新
})
}
}
複製程式碼
上面就是Dep的簡單實現,addSub的作用是增加訂閱者,因為有很多訂閱者,我們需要用一個陣列將它進行儲存,notify()函式的作用就是當set發生的時候,進行通知,update()這個函式待會在watcher中會講到。實現了Dep我們是不是該更改了set()函式了呢,下面是defineReactive()修改後的程式碼
function defineReactive(obj, key ,val) {
let dep = new Dep() // 畢竟要使用Dep的方法
let childOb = observe(val)
Obeject.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
return val
},
set(newVal) {
val = newVal
childOb = observe(val)
dep.notify() // 因為資料改變了,我們就通知Dep
}
})
}
複製程式碼
一旦觸發set,就呼叫dep.notify(),notify的作用就是針對訂閱者遍歷進行更新。
Watcher的簡單實現:
watcher的作用,就是當狀態發生改變的時候,更新檢視,我們可以假設
class Watcher {
constructor (vm, cb, expOrFn) {
this.vm = vm // 這表示一個Vue的例項
this.cb = cb
// 這裡需要考慮expOrFn是字串或者函式的情況
// 這裡做一個簡化,只考慮函式的情況
this.getter = expOrFn
this.value = this.get()
}
get () {
Dep.target = this
const vm = this.vm
value = this.getter.call(this.vm, vm)
Dep.target = null
return value
}
update () {
this.run()
}
run () {
const value = this.get()
if (value !== this.value) {
const oldValue = this.value
this.value = value
this.cb.call(this.vm, value, oldValue)
}
}
}
複製程式碼
Watcher的簡單實現就完成了,在Dep()建構函式中,我們使用了sub.update()這行程式碼,而update函式是Watcher裡邊的方法,說明每一個sub都是Wathcer的例項,問題是我們應該如何通過addSub()這個方法,將Watcher加入到subs這個陣列中盡心儲存呢,答案還是在defineReactive()裡邊進行修改
function defineReactive(obj, key ,val) {
let dep = new Dep() // 畢竟要使用Dep的方法
let childOb = observe(val)
Obeject.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
if(Dep.target) {
dep.addSub(Dep.target)
}
return val
},
set(newVal) {
val = newVal
childOb = observe(val)
dep.notify() // 因為資料改變了,我們就通知Dep
}
})
}
複製程式碼
這樣是不是就實現了往Dep裡邊加Watcher了,vue原始碼中比這個複雜的多,各種引數,看著頭大。本文的宗旨就是通過簡化讓你瞭解內部原理,如果需要更深入瞭解就需要閱讀原始碼了。