若是你有讀過Vue的源碼,或者有了解過Vue的響應原理,那麼你必定知道Object.defineProperty(), 那麼你也應該知道,Vue 2.x裏,是經過 遞歸 + 遍歷 data
對象來實現對數據的監控的, 你可能還會知道,咱們使用的時候,直接經過數組的下標給數組設置值,不能實時響應,是由於Object.defineProperty() 沒法監控到數組下標的變化,而咱們日常所用的數組方法 push
, pop
, shift
, unshift
, splice
, sort
, reverse
, 其實不是真正的數組方法,而是被修改過的,這些都是由於 Object.defineProperty() 提供的能力有限,沒法作到完美。javascript
網上看過不少關於Vue的源碼解讀或者實現一個簡易版的Vue的教程,還都是用 Object.defineProperty (大概是爲兼容性考慮吧), 而 Object.defineProperty() 確實存在諸多限制, 聽說Vue的3.x版本會改用Proxy,那麼今天咱們就先來嚐嚐鮮,用Proxy實現一個簡單版的Vuehtml
Proxy 用於修改某些操做的默認行爲,等同於在語言層面作出修改,因此屬於一種「元編程」(meta programming),即對編程語言進行編程。前端
Proxy 能夠理解成,在目標對象以前架設一層「攔截」,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫。Proxy 這個詞的原意是代理,用在這裏表示由它來「代理」某些操做,能夠譯爲「代理器」。vue
以上引用內容來自阮一峯的es6教程的Proxy章節 原文地址請戳這裏。java
咱們來看看如何用Proxy去定義一個監聽數據的函數node
_observe (data){
var that = this
// 把代理器返回的對象存到 this.$data 裏面
this.$data = new Proxy(data, {
set(target,key,value){
// 利用 Reflect 還原默認的賦值操做
let res = Reflect.set(target,key,value)
// 這行就是監控代碼了
that.handles[key].map(item => {item.update()})
return res
}
})
}
複製代碼
當觸發set的時候,就會執行 that.handles[key].map(item => {item.update()})
,這句代碼的做用就是執行 該屬性名下的全部 "監視器"
git
那麼,監視器怎麼來的呢? 請繼續看如下代碼es6
_compile (root){
const nodes = Array.prototype.slice.call(root.children)
let data = this.$data
nodes.map(node => {
// 若是不是末尾節點,就遞歸
if(node.children.length > 0) this._complie(node)
// 處理 v-bind 指令
if(node.hasAttribute('v-bind')) {
let key = node.getAttribute('v-bind')
this._pushWatcher(new Watcher(node,'innerHTML',data,key))
}
// 處理 v-model 指令
if(node.hasAttribute('v-model')) {
let key = node.getAttribute('v-model')
this._pushWatcher(new Watcher(node,'value',data,key))
node.addEventListener('input',() => {data[key] = node.value})
}
// 處理 v-click 指令
if(node.hasAttribute('v-click')) {
let methodName = node.getAttribute('v-click')
let mothod = this.$methods[methodName].bind(data)
node.addEventListener('click',mothod)
}
})
}
複製代碼
上面這段代碼,看起來很長,但是實際上,只作了意見很簡單的事情, 就是 "編譯" html 模板
,把有 v-bind
、v-model
、v-click
都給加上對應的 通知
和 監控
github
通知 就是 this._pushWatcher(...)
, 至關因而安裝一個監聽器,這樣只要 this.$data 有發生 set 操做的話,就會執行 this._pushWatcher
括號裏面傳的函數,來通知節點更新數據編程
監控 就是 node.addEventListener(...)
監聽相應的事件,而後執行對應的 watcher
或者 methods
this._pushWatcher
又作了什麼呢?
_pushWatcher (watcher) {
if (!this._binding[watcher.key]) this._binding[watcher.key] = []
this._binding[watcher.key].push(watcher)
}
複製代碼
這個就更簡單了,若是 this._binding[watcher.key]
爲空,就初始化,而後向裏面添加一個 監聽器
最後,咱們再來看看,監聽器是怎麼實現的
class Watcher {
constructor (node,attr,data,key) {
this.node = node
this.attr = attr
this.data = data
this.key = key
}
update () {
this.node[this.attr] = this.data[this.key]
}
}
複製代碼
Watcher
是一個類,只有一個方法,就是更新數據,怎麼知道要更新哪一個節點,更新爲何數據呢? 在實例化(new)的時候,傳的參數就是定義這些的
這樣,咱們就實現初步的雙向綁定了,整個代碼大概只有50行。其實還能夠更少, 可是更少的話,就是繼續閹割功能了(雖然目前實現的也是嚴重閹割版的), 可是我以爲實現這些,恰好能夠很少很多幫我咱們理解vue的本質。
本文最終實現代碼已經放在 github上,想要直接看效果的同窗,能夠上去直接copy,運行。
若是以爲本文對您有用,請給本文的github加個star,萬分感謝
另外,github上還有其餘一些關於前端的教程和組件,有興趣的童鞋能夠看看,大家的支持就是我最大的動力。