簡要實現vue雙向繫結

is墨魚發表於2020-03-14

使用 vue 已經很久了,vue的一大特性,雙向繫結的原理也瞭解一個大概,大致是通過 Object.defineProperty()的 set 和 get 方法來劫持屬性,修改屬性。這次疫情在家無事,看了不少這方面的原始碼,所以打算自己寫一個簡要版本的雙向繫結.考慮不周,大家發現問題的話,歡迎提出來,一起學習。

實現思路

1.需要一個 oberser,用來監聽所有的資料和屬性變化,有變動通知訂閱者

2.需要一個compile 掃描節點的指令,初始化模板,更新檢視

3.需要一個watcher 接受受來自於oberser 的變化,然後執行響應的方法

簡要實現vue雙向繫結

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))
    }



花了幾天時間,看了很多原始碼,才寫出來這個簡要的雙向繫結,程式碼有很多問題,比如說多層巢狀之後就不能使用了,但是精力有限,只能先到這裡。
複製程式碼

參考文獻

juejin.im/post/5b2df5…

相關文章