<div id="app"> <div id="app"> <p>{{ a.a }}</p> <span>{{ b }}</span> </div> </div> <script> const vm = new Mvvm({ el: '#app', data: { a: { a: '我是a' }, b: '我是b' } }) </script>
答:經過Object.defineProperty()方法,對data中的屬性,在訪問或者修改對象的其中某個屬性時,經過一段代碼攔截這個行爲,進行額外的操做或者修改返回結果node
// TODO:1.定義Mvvm類 function Mvvm(options={}){ // TODO:this表明的是fade實例對象 // TODO:將全部屬性掛載到$options this.$options = options var data = this._data = this.options.data // TODO:調用數據劫持 observe(data) } // TODO:3.觀察者 function Observe(obj){ for (let key in obj) { let val = obj[key] // TODO:深度劫持 observe(val) Object.defineProperty(obj,key,{ enumerable : true, get(){ return val }, set(newVal){ if (newVal === val) return val = newVal // TODO:深度劫持 observe(newVal) } }) } } // TODO:2.數據劫持-使每一個對象都具備get和set方法 function observe(vmData){ if (typeof data !== 'object') return return new Observe(vmData) }
答:對於每一個data上的屬性,都在app上作一個代理,實際操做的是this.data
實現的代碼以下:數組
function Mvvm(options = {}) { // TODO:this表明的是zhufeng實例對象 // TODO:將全部屬性掛載到$options this.$options = options //this._data var data = this._data = this.$options.data observe(data) // TODO:4.數據代理 for (let key in data) { Object.defineProperty(this,key,{ enumerable : true, get(){ return this._data[key] }, set(newVal){ this._data[key] = newVal } }) } }
答:經過獲取vm管理DOM的根節點,讓其在內存中完成相關的正則匹配工做,替換DOM中的文本節點緩存
// TODO:5數據編譯 function Compile(el,vm){ vm.$el = document.querySelector(el) let fragment = document.createDocumentFragment() while (child = vm.$el.firstChild) { fragment.appendChild(child) } replace(fragment) // TODO:6.數據替換 function replace(frag){ Array.from(frag.childNodes).forEach(function (node) { let text = node.textContent let regExp = /\{\{(.*)\}\}/ if (node.nodeType === 1 && regExp.test(text)) { let arr = RegExp.$1.trim().split('.') let val = vm arr.forEach(function (k) { val = val[k] }) node.textContent = text.replace(regExp,val).trim() } if (node.childNodes && node.childNodes.length) { replace(node) } }) } vm.$el.appendChild(fragment) }
function Mvvm(options = {}) { .... // TODO:進行編譯 new Compile(optionns.el,this) }
答:發佈訂閱主要靠的就是數組關係,訂閱就是放入函數,發佈就是讓數組裏的函數執行app
// TODO:8.發佈訂閱模式 // TODO:橋樑 function Dep(){ // 橋樑 this.subs = [] // 訂閱事件池 } // TODO:進行訂閱的方法(往裏面扔函數) Dep.property.addSub = function (sub) { //sub就是watcher this.subs.push(sub) } // TODO:進行發佈/通知的方法(讓函數的每一項一次執行) Dep.prototype.notify = function () { this.subs.forEach(sub => sub.update())//綁定的事件,都有一個update屬性 } // TODO:訂閱者 function Watcher(fn){ //Watcher是一個類,經過這個類建立的實例都擁有update方法 this.fn = fn } Watcher.prototype.update = function () { //調用fn() this.fn() }
答:如今咱們要訂閱一個事件,當數據改變須要從新刷新視圖,這就須要在replace替換的邏輯裏來處理
經過new Watcher把數據訂閱一下,數據一變就執行改變內容的操做函數
// TODO:6.數據替換 function replace(frag) { ... node.textContent = text.replace(regExp, val).trim() // TODO:監聽變化 new Watcher(vm,RegExp.$1,function (newVal) { node.textContent = text.replace(regExp,newVal).trim() }) if (node.childNodes && node.childNodes.length) { replace(node) } ... }
// TODO:訂閱者 function Watcher(vm,exp,fn){ //Watcher是一個類,經過這個類建立的實例都擁有update方法 this.fn = fn this.vm = vm this.exp = exp Dep.target = this let arr = exp.trim().split('.') let val = vm arr.forEach(function (key) { val = val[key] }) Dep.target = null // // 上面獲取val[key]的時候會調用get方法, 所以使用完畢以後須要把該屬性置爲null }
重寫數據劫持get和set方法this
// TODO:3.觀察者 function Observe(obj){ // TODO:建立橋樑 let dep = new Dep() for (let key in obj) { let val = obj[key] // TODO:深度劫持 observe(val) Object.defineProperty(obj,key,{ enumerable : true, get(){ // TODO:將watcher添加到訂閱事件中 [watcher] Dep.target && dep.addSub(Dep.target) return val }, set(newVal){ // 更改值得時候 if (newVal === val) return // 設置的值和之前的是同樣的東西 val = newVal // 若是之後在獲取值的時候將剛纔設置的值丟回去 // TODO:深度劫持 observe(newVal) // TODO:執行update方法 dep.notify() } }) } }
修改watcher的update方法spa
Watcher.prototype.update = function () { //調用fn() this.fn() // notify的時候值已經更改了 // 再經過vm, exp來獲取新的值 let arr = this.exp.trim().split('.') let val = this.vm arr.forEach(function(key){ val = val[key] }) this.fn(val) // 將每次拿到的新值去替換{{}}的內容便可 }
未完待續...prototype