https://www.cnblogs.com/canfo...html
提到vue,你們確定會想到雙向數據綁定,數據驅動視圖,虛擬DOM,diff算法等等這些概念。在使用vue的時候,會感受到它的數據雙向綁定真的很爽啊。會不會在你用了很長時間後,會好奇到,這個是如何實現的?或者在遇到問題的時候,會不會想到,爲啥這個數據並無響應式的發生改變,視圖怎麼沒有變化...當你抱着這些疑問的時候你確定會想了解其中的原理了。那麼我也是..如今把以前學習和理解的內容整理一下,若是有什麼問題,請多多指教~vue
衆所周知,是個vue的使用者都知道其響應式數據是結合Object.defineProperty()
這個方法實現,那麼關於這個方法的使用和做用,請自行了解..一個合格的jser應該都知道的。node
核心是觀察者模式
,數據是咱們的被觀察者
,發生改變的時候,會通知咱們全部的觀察者
。算法
關於上面這個圖,請仔細的看下,從vue官網拔過來的...。主要包括數據變化更新視圖,視圖變化更新數據。其中主要涉及Observe
,Watcher
,Dep
這三個類,要了解vue的響應式原理,弄清楚這三個類是如何運做的,這樣就可以大概瞭解了。Observe
是數據監聽器,其實現方法就是Object.defineProperty
。Watcher
是咱們所說的觀察者。Dep
是能夠容納觀察者的一個訂閱器,主要收集訂閱者,而後在發生變化的時候通知每一個訂閱者。設計模式
observe的主要工做是針對vue中的響應式數據屬性進行監聽,因此經過遞歸的方法遍歷屬性。緩存
function observe(data) { if(!data || typeof data !== 'object') { return } Object.keys(data).map((k) => { defineReactive(data, k, data[k]) }) } function defineReactive(data, k, val) { observe(val) Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function() { return val }, set: function(newval) { val = newval } }) }
訂閱器負責收集觀察者,而後在屬性變化的時候通知觀察者進行更新。app
function Dep() { this.subs = [] } Dep.prototype.addSub = function(sub) { this.subs.push(sub) } Dep.prototype.notify = function() { this.subs.forEach(v => { v.update() }) }
那麼何時將一個觀察者添加到Dep裏面呢?設計上是將觀察者的添加放在getter裏面。dom
上面說到Watcher是在初始化的時候在getter裏面放進訂閱器Dep中。那麼在Watcher初始化的時候觸發getter。那麼還有個問題,在getter中是如何獲取到Watcher的?這個咱們能夠暫時緩存的放到Dep的靜態屬性Dep.target
上面。函數
function Watcher(vm, exp, cb) { this.vm = vm this.exp = exp this.cb = cb } Watcher.prototype.update = function() {} Watcher.prototype.run = function() { var value = this.vm.data[this.exp]; var oldVal = this.value; if (value !== oldVal) { this.value = value; this.cb.call(this.vm, value, oldVal); } } Watcher.prototype.get = function() { Dep.target = this // 在target上進行緩存 var value = this.vm.data[exp] //觸發getter Dep.target = null //清空null return value }
這個時候咱們還須要修改下Observe:學習
function defineReactive(data, k, val) { observe(val) var dep = new Dep(); Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function() { if(Dep.target) { dep.addSub(Dep.target) } return val }, set: function(newval) { if(newval === val) { return } val = newval dep.notify() } }) }
看到這裏,大體應該都明白如何把Observe
,Dep
,Watcher
如何運做到一塊兒了吧。
function MyVue(data, el, exp) { this.data = data observe(this.data) el.innerHTML = this.data[exp] new Watcher(this, exp, function(value) { el.innerHTML = value }) }
function MyVue(data, el, exp) { this.data = data Object.key(data).forEach(function(k) => { this.proxyKeys(k) }) observe(data); el.innerHTML = this.data[exp]; // 初始化模板數據的值 new Watcher(this, exp, function (value) { el.innerHTML = value; }); return this; } MyVue.prototype.proxyKeys = function(k) { Object.defineProperty(this, k, { enumerable: false, configurable: true, get: function proxyGetter() { return this.data[key]; }, set: function proxySetter(newVal) { this.data[key] = newVal; } }) }
上面的例子中沒有解析DOM節點的操做,只是針對指定dom節點進行內容的替換。那麼compile要進行哪寫操做呢?
function nodeToFragment(el) { var fragment = document.createDocumentFragment() var child = el.firstChild while(child) { fragment.appendChild(child) child = el.firstChild } return fragment }
接下來須要遍歷各個節點,對含有相關指定的節點進行特殊處理。
function compileElement(el) { var childNodes = el.childNodes; var self = this; [].slice.call(childNodes).forEach(function(node) { var reg = /\{\{(.*)\}\}/; var text = node.textContent; if (self.isTextNode(node) && reg.test(text)) { // 判斷是不是符合這種形式{{}}的指令 self.compileText(node, reg.exec(text)[1]); } if (node.childNodes && node.childNodes.length) { self.compileElement(node); // 繼續遞歸遍歷子節點 } }); } function compileText (node, exp) { var self = this; var initText = this.vm[exp]; updateText(node, initText); // 將初始化的數據初始化到視圖中 new Watcher(this.vm, exp, function (value) { // 生成訂閱器並綁定更新函數 self.updateText(node, value); }); }, function updateText (node, value) { node.textContent = typeof value == 'undefined' ? '' : value; }
獲取到最外層節點後,調用compileElement函數,對全部子節點進行判斷,若是節點是文本節點且匹配{{}}這種形式指令的節點就開始進行編譯處理,編譯處理首先須要初始化視圖數據,對應上面所說的步驟1,接下去須要生成一個並綁定更新函數的訂閱器,對應上面所說的步驟2。這樣就完成指令的解析、初始化、編譯三個過程,一個解析器Compile也就能夠正常的工做了。爲了將解析器Compile與監聽器Observer和訂閱者Watcher關聯起來,咱們須要再修改一下類MySelf函數:
function MyVue(opt) { let self = this this.vm = this this.data = opt.data Object.key(this.data).forEach(function(k) { this.proxyKeys(k) }) observe(this.data) new Compile(opt, this.vm) return this }
感受大體說的差很少了...但願可以對你們有點點啓發,謝謝。