zoukankan      html  css  js  c++  java
  • 手动实现一个vue的mvvm,思路解析

    1、解析dom、fragment编译,初始化new watcher

    2 ,数据劫持,Object.defineProperty(obj,key,{

       configurable:true,// 可以配置

       enumerable:true, // 可以枚举

       get:function(){return value}, // 添加wacher,添加订阅者

       set:function(newValue){ return newValue}  // noticfy:执行,更新数据

    })

    3, 发布订阅模式:

    什么是发布订阅者模式呢?举个例子:大爷我要传授们手艺,我这里收了很多徒弟,为了节约时间,我将他们记录在我的邮件里,等我准备好资料,在我这里留底的人员进行群发


    接下里开始代码实现的过程

     1,创建一个简单的应用,

    <div id="mvvm-app">
        <input type="text" v-model="word">
        <p>{{word}}</p>
        <button v-on:click="sayHi">change model</button>
    </div>
    
    <script src="./js/observer.js"></script>
    <script src="./js/watcher.js"></script>
    <script src="./js/compile.js"></script>
    <script src="./js/mvvm.js"></script>
    <script>
    var vm = new MVVM({
        el: '#mvvm-app',
            data: {
                word: 'Hello World!'
            },
            methods: {
                sayHi: function() {
                    this.word = 'Hi, everybody!';
                }
            }
        });
    </script>

    效果: img1

    2,我们先初始化将模板中的 {{}}替换,创建compile函数进行编译,替换

         function MVVM(options) {

        this.$options = options; // this是挂载vm上的,防止和data里面取值冲突,我们这里定义属性的,传递的data,用一些特定的字符来区别,按照我们在
        var data = this._data = this.$options.data;// 源码上注解,我们的data采用,_data,我们的属性采用$options
        observe(data, this);
        this.$compile = new Compile(options.el || document.body, this)
    }
    3,我们要开始实现我们的Compile函数
    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 = {
    node2Fragment: function(el) {
    var fragment = document.createDocumentFragment(),
    child;

    // 将原生节点拷贝到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);
    }

    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);
    // 事件指令
    if (me.isEventDirective(dir)) {
    compileUtil.eventHandler(node, me.$vm, exp, dir);
    // 普通指令
    } 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;
    }
    };

    // 指令处理集合
    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);
    });
    },

    // 事件处理
    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) {
    // 非最后一个key,更新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;
    }
    };

    实现watcher

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

    this.value = this.get();
    }

    Watcher.prototype = {
    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. 每次调用run()的时候会触发相应属性的getter
    // getter里面会触发dep.depend(),继而触发这里的addDep
    // 2. 假如相应属性的dep.id已经在当前watcher的depIds里,说明不是一个新的属性,仅仅是改变了其值而已
    // 则不需要将当前watcher添加到该属性的dep里
    // 3. 假如相应属性是新的属性,则将当前watcher添加到新属性的dep里
    // 如通过 vm.child = {name: 'a'} 改变了 child.name 的值,child.name 就是个新属性
    // 则需要将当前watcher(child.name)加入到新的 child.name 的dep里
    // 因为此时 child.name 是个新值,之前的 setter、dep 都已经失效,如果不把 watcher 加入到新的 child.name 的dep中
    // 通过 child.name = xxx 赋值的时候,对应的 watcher 就收不到通知,等于失效了
    // 4. 每个子属性的watcher在添加到子属性的dep的同时,也会添加到父属性的dep
    // 监听子属性的同时监听父属性的变更,这样,父属性改变时,子属性的watcher也能收到通知进行update
    // 这一步是在 this.get() --> this.getVMVal() 里面完成,forEach时会从父级开始取值,间接调用了它的getter
    // 触发了addDep(), 在整个forEach过程,当前wacher都会加入到每个父级过程属性的dep
    // 例如:当前watcher的是'child.child.name', 那么child, child.child, child.child.name这三个属性的dep都会加入当前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]];
    }
    return obj;
    }
    }
    };
    实现observe
    function Observer(data) {
    this.data = data;
    this.walk(data);
    }

    Observer.prototype = {
    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, // 可枚举
    configurable: false, // 不能再define
    get: function() {
    if (Dep.target) {
    dep.depend();
    }
    return val;
    },
    set: function(newVal) {
    if (newVal === val) {
    return;
    }
    val = newVal;
    // 新的值是object的话,进行监听
    childObj = observe(newVal);
    // 通知订阅者
    dep.notify();
    }
    });
    }
    };

    function observe(value, vm) {
    if (!value || typeof value !== 'object') {
    return;
    }

    return new Observer(value);
    };


    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;

     整体实现此路,需要一张图演示实现思路

     图片上传不好使了,上传一个图片,沾上图片链接

    https://wx2.sinaimg.cn/mw690/9f27f7e9ly1g2v1spxutbj20s00860tp.jpg
  • 相关阅读:
    Effective C# 原则12:选择变量初始化而不是赋值语句
    Effective C# 原则20:明辨接口实现和虚函数重载的区别(译)
    Effective C# 原则18:实现标准的处理(Dispose)模式(译)
    Effective C# 原则19:选择定义和实现接口而不是继承(译)
    Is JoyFM goes over? Nope, I believe that JoyFM will stick together with us for a long time.
    Effective C# 原则15:使用using和try/finally来做资源清理(译)
    Effective C# 第二章:.Net资源管理(翻译)
    Effective C# 原则10: 明白GetHashCode()的缺陷(译)
    Effective C# 原则8:确保0对于值类型数据是有效的(翻译)
    Effective C# 原则23:避免返回内部类对象的引用(翻译)
  • 原文地址:https://www.cnblogs.com/yayaxuping/p/10837744.html
Copyright © 2011-2022 走看看