• 基于 getter 和 setter 撸一个简易的MVVM


    Angular 和 Vue

    在对Angular的学习中,了解到AngularJS 的两个主要缺点:

    • 对于每一次界面时间,Ajax 或者 timeout,都会进行一个脏检查,而每一次脏检查又会在内部循环检查,当界面绑定的model 很多,就会造成严重的性能问题 。
    • Angular 混乱的模块,并不能起到命名空间的作用,因而在大项目中组织模块又是一件头疼的事。

    但是 Angular 开发中小型的应用是很棒的,也是将 MVC 引入到前端的开始。定一个目标,两年时间理解 《Build Your Own AngularJS》。

    Vue 抛弃了Angular 中脏检查的方式,而使用 Javascript 中变量的setter 属性来截获数据变化,更加巧妙和机智。
    在 MVVM 时代,浏览器自身属性变得更加重要起来。getter 和 setter 曾被认为是一个无太大用处的一个属性。

    为了较为深入的理解 Vue, 自己撸了一个简易版 MVVM 。 先看效果吧 。

        <input type="text" v-model="hello" value="">
        <div id="template">
            {{hello}}
        </div>
        <div class="template">
            {{bind}}
        </div>
        <script src="app.js"></script>
    
        var vue = new Vue({
            ele: '#template',
        });
        var vue1 = new Vue({
            ele: '.template',
            data: {
                bind: 'hey i am a vue lover'
            }
        })
    

    开撸开撸

    若有关于 gettersetter 问题 ,请先移步 MDN 。
    申明Vue 对象

    function Vue(option) {
        this.init(option);
    }
    

    初始化

    Vue.prototype.init = function(option) {
        this._data = option.data || {};
        this._method = option.method || {};
        this.bingdings = {};
        this.elements = typeof option.ele === "string" ? document.querySelectorAll(option.ele) : option.ele;
        this.bind();
        this.observe();
        this.react();
        this.initMvvM();
    }
    
    

    将界面上的模型数据绑定到后台

    
    Vue.prototype.bind = function() {
        for (var i = 0, length = this.elements.length; i < length; i++) {
            var ele = this.elements[i],
                html = ele.innerHTML,
                spans, span, dataAttr;
            html = html.replace(/{{(.*?)}}/g, function(a, b) {
                var span = '<span v-data="' + b + '"></span>';
                return span;
            })
            ele.innerHTML = html;
            spans = ele.querySelectorAll('[v-data]');
            for (var j = 0, l = spans.length; j < l; j++) {
                span = spans[j];
                dataAttr = span.getAttribute('v-data');
                if (!this.bingdings[dataAttr]) {
                    this.bingdings[dataAttr] = { value: this._data[dataAttr] || '', ele: [] };
                }
    
                this.bingdings[dataAttr].ele.push(span);
                span.innerHTML = this.bingdings[dataAttr].value;
                span.removeAttribute('v-data');
            }
        }
    }
    

    // 核心部分,使用 setter 观测 _data

    Vue.prototype.observe = function() {
        var self = this,
            eles;
        for (var key in self.bingdings) {
            Object.defineProperty(self._data, key, {
                get: function() {
                    return self.bingdings[key].value;
                },
    
                set: function(newVal) {
                    if (newVal !== self.bingdings[key].value) {
                        self.bingdings[key].value = newVal;
                        eles = self.bingdings[key].ele;
                        for (var i = 0, l = eles.length; i < l; i++) {
                            eles[i].innerHTML = newVal;
                        }
                    }
                }
            })
        }
    }
    

    到这里,_data_ 的赋值已经可以引起界面上dom 的变化。也就是说完成了 model 到view 的数据绑定和响应。
    现在来完成 dom 到 view 的事件响应。

    
    Vue.prototype.initMvvM = function() {
        var self = this;
        var models = document.querySelectorAll('[v-model]'),
            model, dataModel;
        for (var i = 0, l = models.length; i < l; i++) {
            model = models[i];
            dataModel = model.getAttribute('v-model');
            self._data[dataModel] = model.value;
        }
    }
    

    // 事件响应

    Vue.prototype.react = function() {
        var self = this;
        var models = document.querySelectorAll('[v-model]'),
            model, dataModel;
        for (var i = 0, l = models.length; i < l; i++) {
            model = models[i];
            model.addEventListener('change', function(e) {
                dataModel = this.getAttribute('v-model');
                self._data[dataModel] = this.value;
            })
    
            model.addEventListener('keyup', function(e) {
                dataModel = this.getAttribute('v-model');
                self._data[dataModel] = this.value;
            })
        }
    }
    

    最后,更新init 函数。

    Vue.prototype.init = function(option) {
        this._data = option.data || {};
        this._method = option.method;
        this.bingdings = {};
        this.elements = typeof option.ele === "string" ? document.querySelectorAll(option.ele) : option.ele;
        this.bind();
        this.observe();
        this.react();
        this.initMvvM();
    }
    

    总结

    大功告成。
    但是相比与真正的Vue还是差了很多,比如 这里的数据绑定仅支持基本类型,函数绑定也没有完成。在Vue 中,每一个监控的属性都会设置依赖,从而陷入避免和 Angular 中一样的循环检查。
    另外,在Vue中,数据模版并不是这样使用插入一个span,而是创建一个 文本节点,然后append 到当前父元素上。

    这只是一个简易版的,把所有的部分都放在Vue上,非常不合适。 后面会进行重构,把数据监控使用订阅和发布模式封装。

    在这之前,需要进行模块化和依赖注入,所以,接下来要完成一个简易的requirejs,会提供三个api ,define, require 和 use。

    代码托管到了 github(但是今天的网速不好,改天补充.),
    如有错误,希望不吝赐教。

    如果您有合适的前端工资机会,也期待您的邮件。

  • 相关阅读:
    六大设计原则之依赖倒置原则
    六大设计原则之里氏替换原则
    六大设计原则之单一设计原则
    六、Spring之DI的Bean的作用域
    五、spring之DI循环依赖
    四、spring之DI
    十二 NIO和IO
    十一 Pipe
    十 DatagramChannel
    九 ServerSocketChannel
  • 原文地址:https://www.cnblogs.com/likeFlyingFish/p/6201106.html
Copyright © 2020-2023  润新知