使用 vue 已經很久了,vue的一大特性,雙向繫結的原理也瞭解一個大概,大致是通過 Object.defineProperty()的 set 和 get 方法來劫持屬性,修改屬性。這次疫情在家無事,看了不少這方面的原始碼,所以打算自己寫一個簡要版本的雙向繫結.考慮不周,大家發現問題的話,歡迎提出來,一起學習。
實現思路
1.需要一個 oberser,用來監聽所有的資料和屬性變化,有變動通知訂閱者
2.需要一個compile 掃描節點的指令,初始化模板,更新檢視
3.需要一個watcher 接受受來自於oberser 的變化,然後執行響應的方法
oberser
`Vue.prototype.oberser = function (obj) { let that = this
Object.keys(obj).forEach(key => {
var value = obj[key]; //獲取迴圈每一項的值
Object.defineProperty(that.data,key, {
configurable: true, // 可以刪除
enumerable: true, // 可以遍歷
get() {
return value; // 獲取值的時候 直接返回
},
set(newVal) { // 改變值的時候 觸發set
value = newVal;
that.Pool.notice() //執行方法更新檢視
}
})
})
}
複製程式碼
這裡直接使用 forEach 來迴圈傳入的資料,然後我們就可以使用 Object.defineProperty來獲取資料和設定新的資料。
oberser
Vue.prototype.compile = function (el) {
let that = this,nodes = el.children;
for(let i = 0;i<nodes.length;i++) {
if(nodes[i].hasAttribute('v-model') && nodes[i].tagName == 'INPUT'){
nodes[i].addEventListener('input',(function(key) { //監聽輸入框的事件
var attVal = nodes[i].getAttribute('v-model');
that.Pool.add(
new Watcher(
nodes[i],
that,
attVal,
'value'
)
)
return function () {
that.data[attVal] = nodes[key].value; // input值改變的時候 將新值賦給資料 觸發set=>set觸發watch 更新檢視
}
})(i))
}
if (nodes[i].hasAttribute('v-bind')) { // v-bind指令
var attVal = nodes[i].getAttribute('v-bind'); // 繫結的data
that.Pool.add(new Watcher(
nodes[i],
that,
attVal,
'innerHTML'
))
}
}
}
複製程式碼
迴圈 app,裡面的子節點,如果是 輸入框並且有 v-modle的話,就給輸入框一個監聽事件,獲取值,並且往訂閱池裡面新增一個 watcher,後續用來更新輸入框。 如果節點是有 v-bind 的話,也是往訂閱池新增一個 watcher,但是這次是改成了innerHTML,後續用來改變文字
Watcher
function Watcher(el,vm,val,attr) {
this.el =el;
this.vm = vm;
this.val = val;
this.attr = attr;
this.update();
}
Watcher.prototype.update = function () {
this.el[this.attr] = this.vm.data[this.val]
}
複製程式碼
上面是一個watcher ,用來接收來自 compile 的資料,watcher有一個更新方法,根據傳入的節點,更新頁面
function Pool() {
this.subs = [],
this.add(),
this.notice()
}
Pool.prototype.add = function (sub) {
if(sub) {
this.subs.push(sub)
}
}
Pool.prototype.notice = function () {
this.subs.forEach(item => {
item.update()
})
}
這是一個訂閱池,用來收集所有的 watcher,裡面有兩個屬性,一個是新增新的watcher,另一個迴圈執行 watcher 的更新方法
複製程式碼
總結
最後所有的函式都需要頁面載入之後,把他們放到一個主函式裡面。
window.onload = function () {
//載入好頁面之後就載入例項
var app = new Vue({
el: '#app',
data: {
data1: '888888'
}
})
}
function Vue(config = {}) {
this.watcher = {}
this.data = config.data
this.Pool = new Pool()
this.oberser(config.data)
this.compile(document.querySelector(config.el))
}
花了幾天時間,看了很多原始碼,才寫出來這個簡要的雙向繫結,程式碼有很多問題,比如說多層巢狀之後就不能使用了,但是精力有限,只能先到這裡。
複製程式碼