实现数据绑定的几种方法
- 发布者-订阅者模式
- 脏值检查:如angular.js,在指定事件触发时,通过脏值检测的方式比对数据是否有变更,来决定是否更新视图。这些事件有:DOM事件(输入文本、点击按钮)、XHR响应事件、浏览器Location变更事件、Timer事件($timeout、$interval)、执行$digest()或apply()。
- 数据劫持:如vue.js,采用数据劫持结合发布者-订阅者模式,通过
Object.defineProperty()
来劫持各个属性的setter
、getter
,在数据变动时发布消息给订阅者,触发相应的监听回调。
实现vue的双向绑定
主要通过Object
的defineProperty
属性,重写data的set
和get
函数来实现的。

下面是一个实现的例子,主要实现v-model
、v-bind
和v-click
三个命令。
最后实现结果
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实例
myVue
构造函数
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) this.$data = options.data this.$methods = options.methods
this._binding = {} this._observe(this.$data) this._compile(this.$el) }
|
Observer 监听器
监听每个数据的变化,并且在监听到变化时通知订阅者。
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() }) } }) } } }
|
订阅者
用来绑定更新函数,实现对DOM元素的更新
1 2 3 4 5 6 7 8 9 10 11 12
| function Watcher(el, vm, exp, attr) { this.el = el this.vm = vm this.exp = exp this.attr = attr
this.update() }
Watcher.prototype.update = function() { this.el[this.attr] = this.vm.$data[this.exp] }
|
Compile
解析
解析v-bind
、v-model
、v-click
等指令,并对view与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) } if (node.hasAttribute('v-click')) { var methodName = node.getAttribute('v-click') node.onclick = _this.$methods[methodName].bind(_this.$data) } 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 }) } if (node.hasAttribute('v-bind')) { var dataName = node.getAttribute('v-bind') _this._binding[dataName]._watchers.push(new Watcher(node, _this, dataName, 'innerHTML')) } } }
|
源码
mvvm-demo
参考