With about two years of VUE, although read vUE source, recommend Huang Yi big guy VUE source analysis, quite in place. Combs the vUE implementation process from scratch. Over the weekend, I read an open class vUE source code analysis, wondering whether I can also write one to achieve, just do it, start coding! The latest version of VUE still uses Object.defineProperty() internally to hijack data attributes, so as to monitor data changes.

  • You need a data listener Observer that can listen for all attributes of a data object, get the latest values and notify subscribers of any changes.
  • The instruction parser Compile is required to scan and parse the instructions of each element node, replace the data according to the instruction template, and bind the corresponding update function.
  • A Watcher, acting as a bridge between the Observer and Compile, can subscribe to and receive notification of each property change, and execute the corresponding callback function of the directive binding to update the view.
  • The MVVM entry function integrates the above three to realize the data response. For those of you who have read vUE’s official website, you must have seen the chart below, which explains how VUE implements responsive data binding.

An implementation of the Observer class

We use the object.defineProperty () method to iterate over the data, adding getters () and setters () to each Object. The main code is as follows:

class Observer{
    constructor(data){
        this.data=data;
        this.traverse(data);
    }
    traverse(data) {
        var self = this;
        Object.keys(data).forEach(function(key) {
            self.convert(key, data[key]);
        });
    }
    convert(key,val){
        this.defineReactive(this.data, key, val);
    }

    defineReactive(data, key, val) {
        var dep = new Dep();
        var childObj = observe(val);

        Object.defineProperty(data, key, {
            enuselfrable: true, // Enumerates different signals:false, // cannot define againget() {if (Dep.target) {
                    dep.depend();
                }
                return val;
            },
            set(newVal) {
                if (newVal === val) {
                    return; } val = newVal; ChildObj = observe(newVal); // Notify the subscriber dep.notify(); }}); }}function observe(value, vm) {
    if(! value || typeof value ! = ='object') {
        return;
    }
    return new Observer(value);
}
Copy the code

By doing this, we have hijacked the data attributes.

The Compile class implementation

Mainly used to parse various instructions, such as V-modal, V-ON :click and other instructions. Then replace the variables in the template with data, render the view, bind the update function to the node corresponding to each instruction, add subscribers to listen for data, and update the view when the data changes to be notified.

class Compile{
    constructor(el,vm){
        this.$vm = vm;
        this.$el = this.isElementNode(el) ? el : document.querySelector(el);
        if (this.$el) {
            this.$fragment = this.node2Fragment(this.$el);
            this.init();
            this.$el.appendChild(this.$fragment); } } node2Fragment(el){ var fragment = document.createDocumentFragment(), child; // Copy the native node to the fragmentwhile (child = el.firstChild) {
            fragment.appendChild(child);
        }

        return fragment;
    }

    init(){
        this.compileElement(this.$fragment);
    }

    compileElement(el){
        var childNodes = el.childNodes,
            self = this;

        [].slice.call(childNodes).forEach(function(node) {
            var text = node.textContent;
            var reg = /\{\{(.*)\}\}/;

            if (self.isElementNode(node)) {
                self.compile(node);

            } else if (self.isTextNode(node) && reg.test(text)) {
                self.compileText(node, RegExp.The $1);
            }

            if(node.childNodes && node.childNodes.length) { self.compileElement(node); }}); } compile(node){ var nodeAttrs = node.attributes, self = this; [].slice.call(nodeAttrs).forEach(function(attr) {
            var attrName = attr.name;
            if(self.isDirective(attrName)) { var exp = attr.value; var dir = attrName.substring(2); // Event commandif (self.isEventDirective(dir)) {
                    compileUtil.eventHandler(node, self.$vm, exp, dir); }}else {
                    compileUtil[dir] && compileUtil[dir](node, self.$vm, exp); } node.removeAttribute(attrName); }}); } compileText(node, exp){ compileUtil.text(node, this.$vm, exp);
    }

    isDirective(attr){
        return attr.indexOf('v-') = = 0; } isEventDirective(dir){return dir.indexOf('on') = = = 0; } isElementNode(node){return node.nodeType == 1;
    }

    isTextNode(node){
        returnnode.nodeType == 3; } // compileUtil = {text:function(node, vm, exp) {
        this.bind(node, vm, exp, 'text');
    },

    html: function(node, vm, exp) {
        this.bind(node, vm, exp, 'html');
    },

    model: function(node, vm, exp) {
        this.bind(node, vm, exp, 'model');

        var self = this,
            val = this._getVMVal(vm, exp);
        node.addEventListener('input'.function(e) {
            var newValue = e.target.value;
            if (val === newValue) {
                return;
            }

            self._setVMVal(vm, exp, newValue);
            val = newValue;
        });
    },

    class: function(node, vm, exp) {
        this.bind(node, vm, exp, 'class');
    },

    bind: function(node, vm, exp, dir) {
        var updaterFn = updater[dir + 'Updater'];

        updaterFn && updaterFn(node, this._getVMVal(vm, exp));

        new Watcher(vm, exp, function(value, oldValue) { updaterFn && updaterFn(node, value, oldValue); }); }, // eventHandler eventHandler:function(node, vm, exp, dir) {
        var eventType = dir.split(':')[1],
            fn = vm.$options.methods && vm.$options.methods[exp];

        if (eventType && fn) {
            node.addEventListener(eventType, fn.bind(vm), false);
        }
    },

    _getVMVal: function(vm, exp) {
        var val = vm;
        exp = exp.split('. ');
        exp.forEach(function(k) {
            val = val[k];
        });
        return val;
    },

    _setVMVal: function(vm, exp, value) {
        var val = vm;
        exp = exp.split('. ');
        exp.forEach(function(k, I) {// Not the last key, updates the value of valif (i < exp.length - 1) {
                val = val[k];
            } else{ val[k] = value; }}); }}; var updater = { textUpdater:function(node, value) {
        node.textContent = typeof value == 'undefined' ? ' ' : value;
    },

    htmlUpdater: function(node, value) {
        node.innerHTML = typeof value == 'undefined' ? ' ' : value;
    },

    classUpdater: function(node, value, oldValue) {
        var className = node.className;
        className = className.replace(oldValue, ' ').replace(/\s$/, ' ');

        var space = className && String(value) ? ' ' : ' ';

        node.className = className + space + value;
    },

    modelUpdater: function(node, value, oldValue) {
        node.value = typeof value == 'undefined' ? ' ': value; }};Copy the code

Implementation of the Watcher class

As a bridge between links, compile and Observer are linked. Add a subscriber that executes its own update() method when it detects a property change and receives notification from dep.notify()

class Watcher{
    constructor(vm, expOrFn, cb){
        this.cb = cb;
        this.vm = vm;
        this.expOrFn = expOrFn;
        this.depIds = {};

        if (typeof expOrFn === 'function') {
            this.getter = expOrFn;
        } else {
            this.getter = this.parseGetter(expOrFn);
        }

        this.value = this.get();
    }
    update(){
        this.run();
    }
    run(){
        var value = this.get();
        var oldVal = this.value;
        if(value ! == oldVal) { this.value = value; this.cb.call(this.vm, value, oldVal); } } addDep(dep){if (!this.depIds.hasOwnProperty(dep.id)) {
            dep.addSub(this);
            this.depIds[dep.id] = dep;
        }
    }
    get() {
        Dep.target = this;
        var value = this.getter.call(this.vm, this.vm);
        Dep.target = null;
        return value;
    }

    parseGetter(exp){
        if (/[^\w.$]/.test(exp)) return;

        var exps = exp.split('. ');

        return function(obj) {
            for (var i = 0, len = exps.length; i < len; i++) {
                if(! obj)return;
                obj = obj[exps[i]];
            }
            returnobj; }}}Copy the code

The MVVM implementation

MVVM, as the entry of data binding, integrates Observer, Compile and Watcher, uses Observer to monitor its model data changes, and uses Compile to parse and Compile template instructions. Finally, Watcher is used to build a communication bridge between Observer and Compile to achieve data changes and trigger view updates. View interaction changes (INPUT) trigger the bidirectional binding effect of data Model changes.

class Mvvm{
    constructor(options){
        this.$options=options;
        this.data=this._data = this.$options.data;
        console.log(this.$options) var self = this; Keys (this.data).foreach (function(key) {
            self.defineReactive(key);
        });
        this.initComputed();

        new Observer(this.data, this);

        this.$compile = new Compile(this.$options.el || document.body, this)
    }
    defineReactive(key){
        var self=this;
        Object.defineProperty(this,key,{
            configurable:false,
            enuselfrable:true.get() {return self.data[key];
            },
            set(newValue){ self.data[key]=newValue; }}}})Copy the code

Basically done, most of the code is a reference to the vUE source code implementation, learn to read the source code, experience the elegance of VUE design. Octotree is a chrome plugin for github to read the source code. See Github for the full code for this article

This article was published on my blog

By the way, recently began to look for a job, coordinates Beijing, if you have the opportunity, hope to recommend ha, in this first thanks!