雙向綁定是MVVM框架中一個(gè)基本也是很誘人的功能,它能夠讓我們減少dom操作,進(jìn)而極大減少我們的代碼量。我們可以通過指令把一個(gè)input的value值和Model中字段關(guān)聯(lián),而不再需要dom取出此節(jié)點(diǎn),然后獲取節(jié)點(diǎn)的value。
本文以v-model指令為例,分析vue中雙向綁定的基本實(shí)現(xiàn)。在vue源碼解讀 -- 整體架構(gòu)一文中,我們已經(jīng)分析vue的基本運(yùn)行機(jī)制,本文就不再對(duì)這些方面的內(nèi)容進(jìn)行展開。
一個(gè)例子
如下面的例子,當(dāng)input框中的值改變時(shí),p標(biāo)簽中的值也跟著變化
<div id="app">
<input type="text" v-model="a" />
<p>{{a}}</p>
</div>
<script>
var model = new Vue({
el: '#app',
data: {
a: 1
}
});
</script>```
#####基本原理
DOM結(jié)構(gòu)進(jìn)行compile后生成linker函數(shù),linker函數(shù)執(zhí)行后返回一個(gè)unlinker函數(shù)供后續(xù)節(jié)點(diǎn)移除時(shí)調(diào)用(接觸事件綁定、指令監(jiān)聽等)。linker函數(shù)執(zhí)行會(huì)生成DOM中調(diào)用到的指令列表,指令經(jīng)過優(yōu)先級(jí)排序,然后執(zhí)行bind操作:
dirs[i]._bind()
一個(gè)Directive對(duì)象的構(gòu)成如下所示。el指DOM節(jié)點(diǎn);expression表示指令中的表達(dá)式;name是基礎(chǔ)指令的名稱,通過它找到對(duì)應(yīng)的方法;priority表示優(yōu)先級(jí)。

Directive的_bind函數(shù)主要做了兩件事情:調(diào)用基本指令的bind和生成watcher對(duì)象。 前者是指令自身邏輯的確定;后者是綁定data的view的橋梁,當(dāng)數(shù)據(jù)變化時(shí)會(huì)通過update實(shí)現(xiàn)指令更新。
#####指令的bind和update
######1. v-text
{{}}和v-text都會(huì)調(diào)用text指令,下面是其bind和update方法:bind方法進(jìn)行一些預(yù)處理工作,update則自己進(jìn)行view的更新。
bind () {
// 節(jié)點(diǎn)為element時(shí) 通過textContent更新
// 節(jié)點(diǎn)為text時(shí),通過data更新
this.attr = this.el.nodeType === 3
? 'data'
: 'textContent'
},
update (value) {
this.el[this.attr] = _toString(value)
}
######2. v-model
例子中的v-model,其指令的bind和update方法如下:
bind () {
...
if (tag === 'INPUT') {
handler = handlers[el.type] || handlers.text
...
handler.bind.call(this)
this.update = handler.update
this._unbind = handler.unbind
},
handler的bind和update方法:
bind () {
var self = this
// Now attach the main listener
this.listener = function () {
var val = number || isRange ? toNumber(el.value) : el.value
self.set(val)
nextTick(function () {
if (self._bound && !self.focused) {
self.update(self._watcher.value)
}
})
}
...
this.on('change', this.listener)
if (!lazy) {
this.on('input', this.listener)
}
...
},
update (value) {
this.el.value = _toString(value)
},
v-model指令中的bind對(duì)input的change和input事件進(jìn)行了監(jiān)聽,當(dāng)input中值改變是,會(huì)調(diào)用Directive的set方法,然后一步調(diào)用update方法。set方法的邏輯如下:
Directive.prototype.set = function (value) {
if (this.twoWay) {
this._withLock(function () {
this._watcher.set(value)
})
}
...
}
Watcher.prototype.set = function (value) {
var scope = this.scope || this.vm
// 如果有過濾器 執(zhí)行過濾器邏輯
if (this.filters) {
value = scope._applyFilters(
value, this.value, this.filters, true)
}
try {
// 調(diào)用setter更新vm中的值 同事執(zhí)行依賴的notify
this.setter.call(scope, scope, value)
}
...
}
上述就是vue實(shí)現(xiàn)雙向綁定的一個(gè)基本思路,其根本還是通過DOM的事件體系進(jìn)行監(jiān)聽。當(dāng)數(shù)據(jù)變化時(shí),首先改變vm中的值,然后調(diào)用依賴的notify方法實(shí)現(xiàn)其他View的同步更新。