看了一些關於雙向綁定的文章,如今來整理一下思路。 首先實現雙向綁定有三個步驟:html
- 須要一個方法來識別哪個的view被綁定了相應的數據
- 須要監視數據和view的變化
- 須要將全部變化傳播到綁定的對象和對應的view
爲了解決第一個問題,要在對應的dom上添加相應的data-bind-<prop_name>屬性,好比:vue
num: <input type="number" data-bind-num>
<div data-bind-num></div>
複製代碼
爲了解決第二個問題,一方面監聽數據改變,須要這樣添加一個set()方法進行監聽:git
const Vue = {
data: {
num: 0
},
set(key, val) {
this.data[key] = val
}
}
複製代碼
規定經過set(key, val)的方式來修改數據。 另外一邊監聽對應視圖改變就直接監聽input事件。github
爲了解決第三個問題就須要用發佈訂閱模式實現一個事件樞紐:bash
const EventHub = {
callbacks: {},
on(eventName, callback){
this.callbacks[eventName] = this.callbacks[eventName] || [];
this.callbacks[eventName].push(callback);
},
emit(eventName, ...rest){
this.callbacks[eventName] = this.callbacks[eventName] || [];
for(let i = 0; i < this.callbacks[eventName].length; i++){
this.callbacks[eventName][i].call(this,...rest);
}
}
}
複製代碼
一方面將數據層的變化傳播到視圖,須要用特定名稱與dom上的屬性對應:dom
//觸發事件就修改視圖
eventHub.on('num:change', (val) => {
$(`input[data-bind-num]`).val(val)
$(`div[data-bind-num]`).text(val)
})
//經過set()修改data來觸發對應的change事件
set(key, val) {
this.data[key] = val
EventHub.emit('num:change', val)
}
複製代碼
將視圖層的變化傳播到數據:優化
$(`input[data-bind-num]`).on('input', function() {
let val = $(this).val() === '' ? 0 : parseInt($(this).val())
Vue.set(key, val)
})
複製代碼
至此雙向綁定就實現完成!可是這樣一個個寫事件名和屬性名有點蠢,優化一下ui
const fn = (prop_name) => {
return {
dataBind: `data-bind-${prop_name}`,//對應dom的data屬性名
eventName: `${prop_name}:change`//對應數據的change事件名稱
}
}
//給全部data綁定change事件,給全部data對應的view綁定input事件
Object.keys(Vue.data).map((key) => {
//data修改改變view
EventHub.on(fn(key).eventName, (val) => {
$(`input[${fn(key).dataBind}]`).val(val)
$(`div[${fn(key).dataBind}]`).text(val)
})
//view改變data
$(`input[${fn(key).dataBind}]`).on('input', function() {
let val = $(this).val() === '' ? '' : parseInt($(this).val())
Vue.set(key, val)
})
})
複製代碼
這樣實現的雙向綁定依賴於用set()來改變數據,而咱們都但願經過 vm.property = value
這種方式直接來修改數據,這就須要用到Object.defineProperty()
來劫持各個數據的getter
,setter
。this
//給各個數據添加監聽器,用數據劫持替換原先的set(key,value)
const Observer = {
mapProp(obj) {
if(!obj || typeof obj !== 'object') {
return
}
Object.keys(obj).map((key) => {
this.defineReactive(obj, key, obj[key])
})
},
defineReactive(obj, key, val) {
this.mapProp(val)
Object.defineProperty(obj, key, {
enumerable: true, // 可枚舉
configurable: false, // 不能再define
get() {
return val
},
set(newVal) {
console.log(`數據 ${key} 從${val}->${newVal}`)
//當數據變化就貴觸發對應的change事件
EventHub.emit(fn(key).eventName, newVal)
val = newVal
}
})
}
}
複製代碼
這樣只須要調用一次Observer.mapProp(Vue.data)
就會監聽全部data,原先的set()均可以用直接賦值代替。spa
改變data效果:
修改input效果:
文章相關代碼已經同步到Github,歡迎查閱~