Vue Implementation Principles - Implementing Bidirectional Binding mvvm Study Notes
Vue Implementation Principles - Implementing Bidirectional Binding mvvm Study Notes
Several ways to implement data binding
- Publisher-subscriber pattern
- Dirty value checking: as angular.js, when a specified event is triggered, it will decide whether or not to update the view by comparing whether or not there is a change in the data by means of dirty value checking. These events are: DOM event (input text, click button), XHR response event, browser location change event, Timer event ($timeout, $interval), execute $digest() or apply().
- Data hijacking: such as vue.js, using data hijacking combined with the publisher-subscriber model, through the
Object.defineProperty()to hijack thesetter,getterof the individual properties, in the event of a change in the data publish a message to the subscriber, triggered by the corresponding listener callbacks.
Implementing two-way binding for vue
This is achieved primarily through the defineProperty property of Object, overriding the set and get functions of data.
Here’s an example implementation of the v-model, v-bind and v-click commands.
Final realization
1
2
3
4
5
<div id="app">
<input type="text" v-model="number" />
<button type="button" v-click="increment">增加</button>
<h3 v-bind="number"></h3>
</div>
1
2
3
4
5
6
7
8
9
10
11
var app = new myVue({
el: '#app',
data: {
number: 0
},
methods: {
increment: function() {
this.number++
}
}
})
mvvm example
The myVue constructor
1
2
3
4
5
6
7
8
9
10
11
12
13
function myVue(options) {
this._init(options)
}
myVue.prototype._init = function (options) {
this.$options = options
this.$el = document.querySelector(options.el) // 根节点dom
this.$data = options.data
this.$methods = options.methods
this._binding = {} // 绑定的订阅者
this._observe(this.$data) // 监听属性
this._compile(this.$el)
}
Observer listener
Listens for changes in each piece of data and notifies the subscriber when it listens for a change.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
myVue.prototype._observe = function(obj) {
var value
for (key in obj) {
if (obj.hasOwnProperty(key)) {
value = obj[key]
if (typeof value === 'object') {
this._observe(value)
}
this._binding[key] = {
_watchers: []
}
var binding = this._binding[key]
Object.defineProperty(this.$data, key, {
enumerable: true,
configurable: true,
get: function() {
console.log('获取属性' + key + ': ' + value)
return value
},
set: function(newVal) {
console.log('设置属性' + key + ': ' + newVal)
value = newVal
// 通知该属性的所有订阅者更新数据
binding._watchers.forEach(function(item) {
item.update()
})
}
})
}
}
}
Subscribers
Used to bind update functions to update DOM elements.
1
2
3
4
5
6
7
8
9
10
11
12
function Watcher(el, vm, exp, attr) {
this.el = el // 指令对应的DOM元素
this.vm = vm // 指令所属的myVue实例
this.exp = exp // myVue实例更新的属性
this.attr = attr // DOM元素绑定的属性值
this.update()
}
// 更新视图
Watcher.prototype.update = function() {
this.el[this.attr] = this.vm.$data[this.exp]
}
Compile parsing
Parses v-bind, v-model, and v-click directives and binds the view to the model.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
myVue.prototype._compile = function(app) {
var nodes = app.children
var _this = this
for (let i = 0, len = nodes.length; i < len; i++) {
var node = nodes[i]
if (node.children.length) {
this._compile(node)
}
// v-click
if (node.hasAttribute('v-click')) {
var methodName = node.getAttribute('v-click')
node.onclick = _this.$methods[methodName].bind(_this.$data)
}
// v-model
if (node.hasAttribute('v-model') && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')) {
var dataName = node.getAttribute('v-model')
// 添加订阅者
_this._binding[dataName]._watchers.push(new Watcher(node, _this, dataName, 'value'))
node.addEventListener('input', function() {
_this.$data[dataName] = this.value // 使data与dom的值保持一致
})
}
// v-bind
if (node.hasAttribute('v-bind')) {
var dataName = node.getAttribute('v-bind')
// 添加订阅者
_this._binding[dataName]._watchers.push(new Watcher(node, _this, dataName, 'innerHTML'))
}
}
}
Source code
Reference
- Dissect the principle of vue implementation, do-it-yourself mvvm
- [Interview question: can you write a two-way data binding for Vue?] (https://mp.weixin.qq.com/s/MGsEGejaADVHGlZFYQgCnQ)
This post is licensed under CC BY 4.0 by the author.