function Observer(data) {
    this.data = data;
    this.walk(data);
}

Observer.prototype = {
    constructor: Observer,
    walk: function(data) {
        var me = this;
        Object.keys(data).forEach(function(key) {
            me.convert(key, data[key]);
        });
    },
    convert: function(key, val) {
        this.defineReactive(this.data, key, val);
    },

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

        Object.defineProperty(data, key, {
            enumerable: true./ / can be enumerated
            configurable: false.// cannot define again
            get: function() {
                if (Dep.target) {
                    dep.depend();
                }
                return val;
            },
            set: function(newVal) {
                if (newVal === val) {
                    return;
                }
                val = newVal;
                // Listen if the new value is object
                childObj = observe(newVal);
                // Notify subscribersdep.notify(); }}); }};function observe(value, vm) {
    if(! value ||typeofvalue ! = ='object') {
        return;
    }

    return new Observer(value);
};

Copy the code
var uid = 0;
function Dep() {
    this.id = uid++;
    this.subs = [];
}

Dep.prototype = {
    addSub: function(sub) {
        this.subs.push(sub);
    },

    depend: function() {
        Dep.target.addDep(this);
    },

    removeSub: function(sub) {
        var index = this.subs.indexOf(sub);
        if(index ! = -1) {
            this.subs.splice(index, 1); }},notify: function() {
        this.subs.forEach(function(sub) { sub.update(); }); }}; Dep.target =null;

Copy the code
function Compile(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);
    }
}

Compile.prototype = {
    constructor: Compile,
    node2Fragment: function(el) {
        var fragment = document.createDocumentFragment(),
            child;

        // Copy the native node to the fragment
        while (child = el.firstChild) {
            fragment.appendChild(child);
        }

        return fragment;
    },

    init: function() {
        this.compileElement(this.$fragment);
    },

    compileElement: function(el) {
        var childNodes = el.childNodes,
            me = this;

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

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

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

            if(node.childNodes && node.childNodes.length) { me.compileElement(node); }}); },compile: function(node) {
        var nodeAttrs = node.attributes,
            me = this;

        [].slice.call(nodeAttrs).forEach(function(attr) {
            var attrName = attr.name;
            if (me.isDirective(attrName)) {
                var exp = attr.value;
                var dir = attrName.substring(2);
                // Event command
                if (me.isEventDirective(dir)) {
                    compileUtil.eventHandler(node, me.$vm, exp, dir);
                    // Common instruction
                } else{ compileUtil[dir] && compileUtil[dir](node, me.$vm, exp); } node.removeAttribute(attrName); }}); },compileText: function(node, exp) {
        compileUtil.text(node, this.$vm, exp);
    },

    isDirective: function(attr) {
        return attr.indexOf('v-') = =0;
    },

    isEventDirective: function(dir) {
        return dir.indexOf('on') = = =0;
    },

    isElementNode: function(node) {
        return node.nodeType == 1;
    },

    isTextNode: function(node) {
        return node.nodeType == 3; }};// Instruction processing set
var 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 me = this,
            val = this._getVMVal(vm, exp);
        node.addEventListener('input'.function(e) {
            var newValue = e.target.value;
            if (val === newValue) {
                return;
            }

            me._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);
        });
    },

    // Event processing
    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, update the value of val
            if (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
function Watcher(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.trim());
    }

    this.value = this.get();
}

Watcher.prototype = {
    constructor: Watcher,
    update: function() {
        this.run();
    },
    run: function() {
        var value = this.get();
        var oldVal = this.value;
        if(value ! == oldVal) {this.value = value;
            this.cb.call(this.vm, value, oldVal); }},addDep: function(dep) {
        // 1. Every time run() is called, the getter for the corresponding property is fired
        // The getter will fire dep.depend(), which will fire addDep here
        // 2. If the depIds of the corresponding attribute are already in the depIds of the current watcher, it is not a new attribute, but has changed its value
        // There is no need to add the current watcher to the deP of the property
        // 3. If the property is a new property, add the current watcher to the deP of the new property
        // If the value of child.name is changed by vm.child = {name: 'a'}, child.name is a new attribute
        // Add the current watcher(child.name) to the dep of the new child.name
        // If watcher is not added to the dep of the new child.name, the setter and dep are invalid
        // The watcher will not receive notification when the child. Name = XXX is assigned
        // 4. The watcher of each child is added to the deP of the child as well as the parent
        // The watcher of the child property is notified to update the parent property when the parent property changes
        // this is done in this.get() --> this.getVmVal (). ForEach will evaluate from the parent, indirectly calling its getter
        // addDep() is triggered, and the current wacher is added to the deP of each parent procedure attribute throughout the forEach process
        // For example, if the current watcher is 'child.child.name', then the deP of child, child.child, and child.child.name will be added to the current watcher
        if (!this.depIds.hasOwnProperty(dep.id)) {
            dep.addSub(this);
            this.depIds[dep.id] = dep; }},get: function() {
        Dep.target = this;
        var value = this.getter.call(this.vm, this.vm);
        Dep.target = null;
        return value;
    },

    parseGetter: function(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
function MVVM(options) {
    this.$options = options || {};
    var data = this._data = this.$options.data;
    var me = this;

    // Data broker
    // implement vm.xxx -> vm._data.xxx
    Object.keys(data).forEach(function(key) {
        me._proxyData(key);
    });

    this._initComputed();

    observe(data, this);

    this.$compile = new Compile(options.el || document.body, this)
}

MVVM.prototype = {
    constructor: MVVM,
    $watch: function(key, cb, options) {
        new Watcher(this, key, cb);
    },

    _proxyData: function(key, setter, getter) {
        var me = this;
        setter = setter || 
        Object.defineProperty(me, key, {
            configurable: false.enumerable: true.get: function proxyGetter() {
                return me._data[key];
            },
            set: function proxySetter(newVal) { me._data[key] = newVal; }}); },_initComputed: function() {
        var me = this;
        var computed = this.$options.computed;
        if (typeof computed === 'object') {
            Object.keys(computed).forEach(function(key) {
                Object.defineProperty(me, key, {
                    get: typeof computed[key] === 'function' 
                            ? computed[key] 
                            : computed[key].get,
                    set: function() {}}); }); }}};Copy the code