數據綁定
數據綁定一般就是指的 將數據 展示到 視圖上。目前前端的框架都是使用的mvvm模式實現雙綁的。大體上有以下幾種方式:
- 發布訂閱
- ng的臟檢查
- 數據劫持
vue的話采用的是數據劫持和發布訂閱相結合的方式。 而數據劫持用的是Object.defineProperty來實現的, 可以通過綁定get和set來在獲取和設置數據的時候觸發相應的函數。
實現
所以我們需要一個監聽器Observe來監聽數據的變化。在數據發生變化時,我們需要一個Watcher訂閱者來更新視圖,我們還需要一個指令的解析器compile來解析指令和初始化視圖。
-
- Observe 監聽器: 監聽數據的變化, 通知訂閱者
- Watcher 訂閱者: 收到數據的變化, 更新視圖
- Compile 解析器: 解析指令,初始化模板,綁定訂閱者
Observe
監聽數據的每一個屬性, 由於可能會有多個watcher,所以需要一個容器來存儲。

function Sub() { this.subs = []; } Sub.prototype = { add(sub) { this.subs.push(sub); }, trigger() { this.subs.forEach(sub => { sub.update(); }) } }; Sub.target = null; function observe(data) { if (typeof data !== 'object' || !data) return; Object.keys(data).forEach(item => { let val = data[item]; let sub = new Sub(); Object.defineProperty(data, item, { enumerable: true, configurable: false, get() { if (Sub.target) { sub.add(Sub.target); } return val; }, set(newVal) { val = newVal; sub.trigger(); } }) }) }
Watcher
將對應屬性的watcher添加到sub容器中, 當屬性變化時,執行更新函數。

function Watcher(vm, prop, callback) { this.vm = vm; this.prop = prop; this.callback = callback; Sub.target = this; let val = this.vm.$data[prop]; Sub.target = null; this.vaule = val; } Watcher.prototype.update = function () { let newValue = this.vm.$data[this.prop]; if (this.value !== newValue) { this.value = newValue; this.callback.call(this.vm, newValue); } }
Compile
獲取到dom中的指令和初始化模板, 添加watcher更新視圖。

function Compile(vm) { this.vm = vm; this.el = vm.$el; this.init(); } Compile.prototype.init = function () { let fragment = document.createDocumentFragment(); let child = this.el.firstChild; while(child) { fragment.append(child); child = this.el.firstChild; } let childNodes = fragment.childNodes; Array.from(childNodes).forEach(node => { if (node.nodeType === 1) { let attrs = node.attributes; Array.from(attrs).forEach(attr => { let name = attr.nodeName; if (name === 'v-model') { let prop = attr.nodeValue; let value = this.vm.$data[prop]; node.value = value; new Watcher(this.vm, prop, val => { node.value = val; }); node.addEventListener('input', e => { let newVal = e.target.value; if (value !== newVal) { this.vm.$data[prop] = newVal; } }) } }) } let reg = /\{\{(.*)\}\}/; let text = node.textContent; if (reg.test(text)) { let prop = RegExp.$1; let val = this.vm.$data[prop]; node.textContent = val; new Watcher(this.vm, prop, val => { node.textContent = val; }); } }) this.el.appendChild(fragment); }
到這里, 基本的思路已經實現完畢, 這里只實現了v-model指令。
最后,結合 Observe Watcher和 Compile, 就可以成為一個完整的mvvm了。
<div id="app"> <div>{{val}}</div> <input type="text" id="input" v-model="val"> </div> <script> function MyVue(options) { this.$options = options; this.$el = options.el; this.$data = options.data; this.init(); } MyVue.prototype.init = function () { observe(this.$data); new Compile(this); }; new MyVue({ el: document.getElementById('app'), data: { val: 123 } }) </script>
當然,這只是簡單的實現,沒考慮細節,主要是學習思路。
查看效果 (代碼直接在頁面上)