來一張原理圖:html
實現思路:vue
(1)綁定data 種的數據,爲每一個數據添加指令。經過Object,defineProperty() 來通知屬性是否更改node
(2) 找到每一個DOM節點的指令。綁定事件。並綁定watcherapp
(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>