双向绑定是MVVM框架中一个基本也是很诱人的功能,它能够让我们减少dom操作,进而极大减少我们的代码量。我们可以通过指令把一个input的value值和Model中字段关联,而不再需要dom取出此节点,然后获取节点的value。
本文以v-model
指令为例,分析vue中双向绑定的基本实现。在vue源码解读 -- 整体架构一文中,我们已经分析vue的基本运行机制,本文就不再对这些方面的内容进行展开。
一个例子
如下面的例子,当input框中的值改变时,p标签中的值也跟着变化
<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结构进行compile后生成linker函数,linker函数执行后返回一个unlinker函数供后续节点移除时调用(接触事件绑定、指令监听等)。linker函数执行会生成DOM中调用到的指令列表,指令经过优先级排序,然后执行bind操作:
dirs[i]._bind()
一个Directive对象的构成如下所示。el指DOM节点;expression表示指令中的表达式;name是基础指令的名称,通过它找到对应的方法;priority表示优先级。
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1975863-c8b91641484af0b4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
Directive的_bind函数主要做了两件事情:调用基本指令的bind和生成watcher对象。 前者是指令自身逻辑的确定;后者是绑定data的view的桥梁,当数据变化时会通过update实现指令更新。
#####指令的bind和update
######1. v-text
{{}}和v-text都会调用text指令,下面是其bind和update方法:bind方法进行一些预处理工作,update则自己进行view的更新。
bind () {
// 节点为element时 通过textContent更新
// 节点为text时,通过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对input的change和input事件进行了监听,当input中值改变是,会调用Directive的set方法,然后一步调用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
// 如果有过滤器 执行过滤器逻辑
if (this.filters) {
value = scope._applyFilters(
value, this.value, this.filters, true)
}
try {
// 调用setter更新vm中的值 同事执行依赖的notify
this.setter.call(scope, scope, value)
}
...
}
上述就是vue实现双向绑定的一个基本思路,其根本还是通过DOM的事件体系进行监听。当数据变化时,首先改变vm中的值,然后调用依赖的notify方法实现其他View的同步更新。