To manually implement a simple two-way data binding mvvm

Data Binding

  Data Binding generally refers to the data will show the view. Currently mvvm distal frame mode is used to achieve double tied. There are several general ways:

  1.  Publish and subscribe
  2.    ng dirty check
  3.    Data hijacking

  vue, then uses the data hijacking and publishing subscribe combination. The data used is Object.defineProperty hijacking to achieve, can be triggered when the corresponding function to get and set data by binding get and set.

achieve

  So we need a listener to listen Observe changes in the data. When data changes, we need a Watcher subscribers to update the view, we also need to compile a parser to parse commands and instructions to initialize the view.

    • Observe listener: monitor data changes, notify subscribers
    • Watcher subscribers: receive data changes, update view
    • Compile parser: parsing command to initialize templates, binding subscribers

  Observe

    Attribute data for each monitor, since there may be a plurality Watcher, it is necessary to store the container.

    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 corresponding attribute added to the sub container, when the attribute change, to perform the update function.

    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 to obtain the instructions and initialization template, add watcher update the view.

    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

 

Here, the basic idea has been implemented is completed, just to achieve a v-model command.

  Finally, Observe Watcher and Compile, can become a complete mvvm up.

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

 

 Of course, this is just a simple realization, did not consider the details, mainly the study of ideas.

 

    

  

  

Guess you like

Origin www.cnblogs.com/wjyz/p/11419073.html