来一张原理图:
实现思路:
(1)绑定data 种的数据,为每个数据添加指令。通过Object,defineProperty() 来通知属性是否更改
(2) 找到每个DOM节点的指令。绑定事件。并绑定watcher
(3) 实现DOM事件改变之后, 响应data数据,实现视图更新
<!DocType> <html> <title>vue 的双向绑定事件</title> <body id="app"> <input type="text" v-model="number"/> <span v-bind="number"></span> <input type="text" v-model="age"/> <span v-bind="age"></span> </body> <script> function Vue (options) { this._init(options); } Vue.prototype._init = function (options) { this.$data = options.data; this.$methods = options.data.methods; this.$el = document.querySelector(options.el); this.$methods = options.methods; this.$key = ''; this._binding = {}; // 观测数据 this._observer(this.$data); this._complie(this.$el); // this._test(this.$data); } // 观测数据 Vue.prototype._observer = function (obj) { var value; let _this = this; for (key in obj) { if (obj.hasOwnProperty(key)) { this._binding[key] = { _directives: [] }; value = obj[key]; if (typeof value === 'object') { this._observer(value); } Object.defineProperty(this.$data, key, { enumerable: true, configurable: true, get: function () { console.log(`获取${value}`, key); return value; }, set: function (newVal) { console.log('key:', key, _this.$key); if (value !== newVal) { value = newVal; _this._binding[_this.$key]._directives.forEach(function (item, index) { item.update(); }) } } }) } } } // 为DOM节点添加指令事件 Vue.prototype._complie = function (root) { var _this = this; var nodes = root.children; for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; if (node.children.length) { this._complie(node); } if (node.hasAttribute('v-click')) { node.onclick = (function () { var attrVal = nodes[i].getAttribute('v-click'); return _this.$methods[attrVal].bind(_this.$data); })(); } if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) { node.addEventListener('input', (function(key) { var attrVal = node.getAttribute('v-model'); _this._binding[attrVal]._directives.push(new Watcher( 'input', node, _this, attrVal, 'value' )) return function() { _this.$key = attrVal _this.$data[attrVal] = nodes[key].value; } })(i)); } if (node.hasAttribute('v-bind')) { var attrVal = node.getAttribute('v-bind'); _this._binding[attrVal]._directives.push(new Watcher( 'text', node, _this, attrVal, 'innerHTML' )) } } } function Watcher(name, el, vm, exp, attr) { this.name = name; //指令名称,例如文本节点,该值设为"text" this.el = el; //指令对应的DOM元素 this.vm = vm; //指令所属myVue实例 this.exp = exp; //指令对应的值,本例如"number" this.attr = attr; //绑定的属性值,本例为"innerHTML" this.update(); } // 更新数据 Watcher.prototype.update = function () { this.el[this.attr] = this.vm.$data[this.exp]; } // 测试 Vue.prototype._test = function($data) { var a = $data.number; $data.number = 32; } window.onload = function () { var app = new Vue({ el: '#app', data: { number: 12, age: 444 }, methods: {} }) } </script> </html>