本身手動實現簡單的雙向數據綁定 mvvm

數據綁定

  數據綁定通常就是指的 將數據 展現到 視圖上。目前前端的框架都是使用的mvvm模式實現雙綁的。大致上有如下幾種方式:html

  1.  發佈訂閱
  2.    ng的髒檢查
  3.    數據劫持

  vue的話採用的是數據劫持和發佈訂閱相結合的方式。 而數據劫持用的是Object.defineProperty來實現的, 能夠經過綁定get和set來在獲取和設置數據的時候觸發相應的函數。前端

實現

  因此咱們須要一個監聽器Observe來監聽數據的變化。在數據發生變化時,咱們須要一個Watcher訂閱者來更新視圖,咱們還須要一個指令的解析器compile來解析指令和初始化視圖。vue

    • Observe 監聽器: 監聽數據的變化, 通知訂閱者
    • Watcher 訂閱者: 收到數據的變化, 更新視圖
    • Compile 解析器: 解析指令,初始化模板,綁定訂閱者

  Observe

    監聽數據的每個屬性, 因爲可能會有多個watcher,因此須要一個容器來存儲。node

    function Sub() {
        this.subs = [];
    }
    Sub.prototype = {
        add(sub) {
            this.subs.push(sub);
        },
        trigger() {
            this.subs.forEach(sub => {
                sub.update();
            })
        }
    };
    Sub.target = null;

    function observe(data) {
        if (typeof data !== 'object' || !data) return;
        Object.keys(data).forEach(item => {
            let val = data[item];
            let sub = new Sub();
            Object.defineProperty(data, item, {
                enumerable: true,
                configurable: false,
                get() {
                    if (Sub.target) {
                        sub.add(Sub.target);
                    }
                    return val;
                },
                set(newVal) {
                    val = newVal;
                    sub.trigger();
                }
            })
        })
    }
View Code

  

  Watcher

    將對應屬性的watcher添加到sub容器中, 當屬性變化時,執行更新函數。git

    function Watcher(vm, prop, callback) {
        this.vm = vm;
        this.prop = prop;
        this.callback = callback;
        Sub.target = this;
        let val = this.vm.$data[prop];
        Sub.target = null;
        this.vaule = val;
    }

    Watcher.prototype.update = function () {
        let newValue = this.vm.$data[this.prop];
        if (this.value !== newValue) {
            this.value = newValue;
            this.callback.call(this.vm, newValue);
        }
    }
View Code

  

  Compile

    獲取到dom中的指令和初始化模板, 添加watcher更新視圖。github

    function Compile(vm) {
        this.vm = vm;
        this.el = vm.$el;
        this.init();
    }
    
    Compile.prototype.init = function () {
        let fragment = document.createDocumentFragment();
        let child = this.el.firstChild;
        while(child) {
            fragment.append(child);
            child = this.el.firstChild;
        }
        let childNodes = fragment.childNodes;
        Array.from(childNodes).forEach(node => {
            if (node.nodeType === 1) {
                let attrs = node.attributes;
                Array.from(attrs).forEach(attr => {
                    let name = attr.nodeName;
                    if (name === 'v-model') {
                        let prop = attr.nodeValue;
                        let value = this.vm.$data[prop];
                        node.value = value;
                        new Watcher(this.vm, prop, val => {
                            node.value = val;
                        });
                        node.addEventListener('input', e => {
                            let newVal = e.target.value;
                            if (value !== newVal) {
                                this.vm.$data[prop] = newVal;
                            }
                        })
                    }
                })
            }
        
            let reg = /\{\{(.*)\}\}/;
            let text = node.textContent;
            if (reg.test(text)) {
                let prop = RegExp.$1;
                let val = this.vm.$data[prop];
                node.textContent = val;
                new Watcher(this.vm, prop, val => {
                    node.textContent = val;
                });
            }
        })
        this.el.appendChild(fragment);
    }
View Code

 

到這裏, 基本的思路已經實現完畢, 這裏只實現了v-model指令。app

  最後,結合 Observe Watcher和 Compile, 就能夠成爲一個完整的mvvm了。框架

    <div id="app">
        <div>{{val}}</div>
        <input type="text" id="input" v-model="val">
    </div>


<script>
     function MyVue(options) {
        this.$options = options;
        this.$el = options.el;
        this.$data = options.data;
        this.init();
    }

    MyVue.prototype.init = function () {
        observe(this.$data);
        new Compile(this);
    };


    new MyVue({
        el: document.getElementById('app'),
        data: {
            val: 123
        }
    })
</script>

 

 固然,這只是簡單的實現,沒考慮細節,主要是學習思路。dom

 查看效果 (代碼直接在頁面上)mvvm

相關文章
相關標籤/搜索