前言
如果你有讀過Vue的原始碼,或者有了解過Vue的響應原理,那麼你一定知道Object.defineProperty(),
那麼你也應該知道,Vue 2.x裡,是通過 遞迴 + 遍歷 data
物件來實現對資料的監控的,
你可能還會知道,我們使用的時候,直接通過陣列的下標給陣列設定值,不能實時響應,是因為Object.defineProperty()
無法監控到陣列下標的變化,而我們平常所用的陣列方法 push
, pop
, shift
, unshift
, splice
, sort
, reverse
,
其實不是真正的陣列方法,而是被修改過的,這些都是因為 Object.defineProperty() 提供的能力有限,無法做到完美。
網上看過很多關於Vue的原始碼解讀或者實現一個簡易版的Vue的教程,還都是用 Object.defineProperty (大概是為相容性考慮吧),
而 Object.defineProperty() 確實存在諸多限制, 據說Vue的3.x版本會改用Proxy,那麼今天我們就先來嚐嚐鮮,用Proxy實現一個簡單版的Vue
proxy 介紹
Proxy 用於修改某些操作的預設行為,等同於在語言層面做出修改,所以屬於一種“超程式設計”(meta programming),即對程式語言進行程式設計。
Proxy 可以理解成,在目標物件之前架設一層“攔截”,外界對該物件的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。Proxy 這個詞的原意是代理,用在這裡表示由它來“代理”某些操作,可以譯為“代理器”。
以上引用內容來自阮一峰的es6教程的Proxy章節 原文地址請戳這裡。
我們來看看如何用Proxy去定義一個監聽資料的函式
定義 observe
_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()})
,這句程式碼的作用就是執行 該屬性名下的所有 "監視器"
那麼,監視器怎麼來的呢? 請繼續看以下程式碼
定義 compile
_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
都給加上對應的 通知
和 監控
-
通知 就是
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]
為空,就初始化,然後向裡面新增一個 監聽器
最後,我們再來看看,監聽器是怎麼實現的
定義 Watcher
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上還有其他一些關於前端的教程和元件,有興趣的童鞋可以看看,你們的支援就是我最大的動力。