Vue的主要原理中主要用到了定義的這麼幾個函式Dep,Watcher,observer。
我們來使用這幾個函式簡單的實現一下vue建構函式資料繫結和相互依賴部分,梳理一下它們之間的關係。
省略了編譯部分和proxy代理與其他的一些複雜邏輯。
Dep
Dep是依賴類,簡要實現為
class Dep {
constructor () {
// 放當時屬性的觀察者
this.subs = []
}
}
// target 用來掛載當時的watcher觀察者
Dep.target = null
observer
做屬性劫持,並做點其他事情
function observer (vm, key, val) {
let dep = new Dep()
Object.defineProperty(vm, key, {
/**
* get主要做兩個事情
* 1. 收集觀察當前key的wathcer(即依賴當前key的操作)
* 2. 獲取值
*/
get () {
// 這是作用1
if (Dep.target) {
dep.subs.push(Dep.target)
}
// 這是作用2
return val
},
/**
* set也是兩個事情
* 1. 修改目標值
* 2. 執行依賴當前key的watcher
*/
set (newVal) {
// 這是作用1
val = newVal
// 這是作用2
for(cb of dep.subs) {
cb.call(vm)
}
}
})
}
Watcher
Watcher是觀察者類,用來建立依賴某屬性的操作(如指令,渲染,計算屬性等)
class Watcher {
/**
* vm: 例項
* cb: 依賴某屬性的操作函式
*/
constructor (vm, cb) {
// 把當前的操作掛載到Dep上
Dep.target = cb
/**
* 執行操作,兩個作用
* 1. 進行操作的初始化
* 2. 觸發屬性的get方法,使當前cb被收集
*/
cb.call(vm)
Dep.target = null
}
}
demo
那麼我們就使用上面定義好的函式寫個例子
<div>
<p class="text"></p>
<div>
let vm = new Vue({
// 假設有data
data: {msg: 1},
// 有某個v-text操作,我們抽象為vText函式,依賴屬性msg(代表所有依賴其他屬性的操作)
renderFun: {
vText () {
document.querySelector(`.text`).innerText = this.msg
}
}
})
// 修改vue例項的值,觀察變化
vm.msg = 333
那麼我們也寫一個vue的簡易建構函式
class Vue {
constructor (options) {
let data = options.data
let renderFun = options.renderFun
// initData
Object.keys(data).forEach(key => {
observer(this, key, data[key])
})
// 模擬計算屬性,watcher,指令等依賴屬性的操作
Object.keys(renderFun).forEach(key => {
new Watcher(this, renderFun[key])
})
}
}
執行過程
完整的程式碼可以看demo部分的兩個連結
- 建立vue例項,執行
new Vue()
-
對data進行初始化,對
data
中屬性進行屬性劫持- 劫持過程中,在閉包內建立對當前屬性的依賴佇列(dep.subs)和值(val)。
get
進行觀察者watcher
的收集和值得獲取;set
進行值的更新和依賴佇列中watcher
的執行
- 劫持過程中,在閉包內建立對當前屬性的依賴佇列(dep.subs)和值(val)。
- 對編譯過程中如
computedwatcher
或模板編譯
過程中的指令
函式進行初始化,我們以renderFun
代替 - 針對
renderFun
中的每個功能函式進行new Watcher()
工作 -
以
vText
為例子,在new Wathcer()
過程中- 將
vText
掛載到全域性通用的Dep.target
上 - 執行
vText
,其中有讀vm.msg
的操作,則觸發msg屬性的get,進入Dep.target
判斷,將Dep.target
即vText
收集進msg
的subs
依賴佇列中,此時vText
執行完畢,頁面innetText
被修改 - 將
Dep.target
置空
- 將
-
執行
vm.msg = 333
,則觸發msg
的set
-
set
先修改msg
的值 - 再執行
msg
依賴佇列中的所有watcher
的函式,即vText
,頁面的innerText
被同步更新
-
總結
總之幾者的關係就是在observer
的get
中將對當前屬性的watcher
收集進dep
,在observer
的set
中執行收集到的watcher
。
而vue的真正的執行過程絕不是上面寫的這麼簡單,比如watcher的執行就絕不是簡單的遍歷執行,而且還對observer進行了很大程度的簡化。我們還省略了諸如_proxy
、defineReactive
等出現頻率較高的函式。寫這樣一個最簡實現主要是為了梳理一下主幹,降低閱讀原始碼的難度。
??????????????????????????????????