• 我的MVVM框架 v3发布!


    人们总是爱探求完美的东西,医学界追求长生不死之药,炼金术师追求贤者之石,物理学家追求永动机……编程界也有自己的追求,完美架构什么的,什么从MVC,到MVP,到MVVM……当然MVC,MVP,MVVM有他们不同的场景,但MVVM在微软试水后已被证实为界面开发最好的方案了。于前端而言,一直纠缠于模板与组件的优劣。其实答案大家都知道,web page用模板, web app用组件,问题是如何将它们统合起来。页面之所以能交互,是因为它存在状态。因此核心问题是如何将这两者管辖的状态统合在一起。状态的由来有两个,直接从模型得到,比如后端传送过来的JSON与XML,或比较悲剧地把PHP序列化的字段还原出来,第二种是在程序中控制流程时不生的中间量。我们可以这些后端数据或中间量整成一个数据源,或作为模板的填充数据,或作为组件的传参最后成为它的属性。MVVM的出现就很好解决这问题,数据是数据,最好作为贫血模型而存在,对这些数据的操作以及基于这些操作的操作独立成另一个东西,ViewModel。View是设计师与页面重构师的阵地。于是我们能实现并行开发。View与ViewModel的连结是数据绑定,ko称之为声明式绑定。绑定可以分解成绑定器与相应的参数,它们调用ViewModel的东西,ViewModel操作Model。绑定器通常是非常薄的一层,很少被人提起,但意义重大。它实现了数据再加工与事件绑定(WPF称之为命令),从而让数据可视,操作可用!整个流程是非常清晰的,得到Model,抽象出ViewModel,在View中声明绑定,就完了。觉得框架提供的功能不足,就添加自定绑定器,在ViewModel添加命令(它可以作为事件回调,或数据的过滤器,验证函数,格式函数)。格式是固定的,像后端那梓,哪个页面应该由哪个action负责都有规可循。这正是我们前端梦寐以来的究极解决方案。

    但思路是有,实现起来不轻松,因此前端的MVVM框架也林林总总,每个月都冒出一两个出来。开源的世界类似于免费的世界,很容易引起马太效应,强者愈强,弱者愈弱,很易产生垄断。jQuery的一枝独秀也证明了这一点。MVVM框架的混乱说明还没有出现足够称得上强大的东西。现在比较拉风的是knouckoutjs,emberjs,angularjs,backbone(它也有数据绑定插件让它改装成真正的MVVM)。个人是比较看好knouckoutjs,毕竟是由MVVM的发源地微软的人搞的,是最正统的派系。但它的绑定也一直被人诟病,太复杂难用。后端的WPF由微软的强大工具撑着,因此人们觉得不怎么。但一旦要你们手写这些绑定时就惨了,加之前端经过jQuery那极简主义的DSL式API洗礼后,很多人无法接受这样复杂的用法。emberjs与angularjs对IE6支持不佳,因此在大陆没有销路,加之提供的API太多了,对应的概念也多,学习曲线陡峭。backbone是太笨重,没有干什么活,却要写一大堆代码,与jQuery反向而行。

    我的MVVM框架avalon v3两个重要借鉴者为knouckoutjs与rivetsjs。从knouckoutjs得到它的双向依赖链的架构,从学习到消化经历两个版本,v2的实现完全原创。从rivetsjs得到它的声明式绑定的API设计,但实现完全是自己的。v3对v2的双向依赖链的架构进行一些改进,只要是重命名,让这些概念更让人接受。

    avalon v3的双向绑定链架构图

        // ViewModel              框架              View
        //属性访问器  ┓
        //组合访问器 ┫→→→绑定器 ←←← DOM访问器 ←←← 数据绑定
        //集合访问器 ┫
        //命令       ┛
    

    ViewModel是一个由访问器与命令组成的对象。访问器即accessor, 取义自ruby的attr_accessor,是attr_writer, attr_reader的结合,用于对某个数据进行读写操作。比如Model中有个aaa属性,ViewModel就会对应生成一个叫aaa的函数,我们可以传参修改这个aaa的值,也可以从它那时得到aaa的值。之所以这么大周折,是因为IE9才支持用Object.defineProperty描述对象的属性的访问机制,它是否可遍历啊,可配置啊,读取时应该返回什么,写入时会进行什么处理。如果aaa属性与bbb属性有关联,我们可以在访问aaa时修改bbb,直接obj.aaa = "xxx"就行了。但为了兼并IE6,我们唯有obj.aaa("xxx")。emberjs就是基于Object.defineProperty构建它的双向绑定链,因此对IE9-支持不好。

    
    //IE9+ FF4+, safari5+, opera11+, chrome5(IE8只支持DOM)
    var obj = {}, aValue = 0;
    Object.defineProperty(obj, "aaa", {
        get : function(){
            return aValue;
        },
        set : function(newValue){ 
            obj.bbb += newValue
            aValue = newValue; 
        },
        enumerable : true,
        configurable : true
    })
    Object.defineProperty(obj, "bbb", {
        value :10,
        writable : true,
        enumerable : true,
        configurable : true
    });
                    
    console.log(obj.aaa)//0
    console.log(obj.bbb)//10
    obj.aaa = 7
    console.log(obj.aaa)//7
    console.log(obj.bbb)//17
    //IE6+
    var aValue = 0, bValue = 10;
    var obj = {
        aaa: function(newValue){
            if(arguments.length){
                bValue += newValue
                aValue = newValue; 
                
            }
            console.log("xxxxxxxxx")
            return aValue
        },
        bbb: function(newValue){
            if(arguments.length){
                bValue = newValue; 
            }
              
            return bValue
        }
    }
        
    console.log(obj.aaa())//0
    console.log(obj.bbb())//10
    obj.aaa(7)
    console.log(obj.aaa())//7
    console.log(obj.bbb())//17
    

    访问器又分四种,存在于ViewModel中的有三种。最简单的是属性访问器,它是对Model中某一个属性进行操作,相当于ko的监控属性。如果一个字段由模型中的两个属性,或两个以上,或要对这属性进行一下加工才产生它的值呢,这就要用到组合访问器,相当于ko的依赖监控属性,或emberjs中的computed。像程序中许多表示状态的中间量都可以抽象成一个组合访问器。组合访问器换言之,对已有的东西重新组合而成的属性的监控函数。集合访问器,是Model中的数组进行监控,如果它发生排序增删,它会通知双向依赖链的两端来刷新自身。集合访问器是个特殊的数组,它的方法都被重写了,虽然用法一样,但调用了它们会同步到对应的节点区域上!

    ViewModel中还存在一种叫命令的东西,打个比方,绑定器相当于MVC中的action,命令相当于helpers。它只是一个普通的函数,框架不会再对它加工。框架对命令与访问器的区分是,访问器是用$type 与 "$"+(new Date - 0)这两个属性。说得可能有点复杂,比如有个对象var model = {aaa:1, bbb:1},然后$.ViewModel( obj )就得到它对应的ViewModel了。

    接着我们看绑定部分。要实现事件绑定。knouckoutjs实现如下:

    <div>
        <div data-bind="event: { mouseover: enableDetails, mouseout: disableDetails }">
            Mouse over me
        </div>
        <div data-bind="visible: detailsEnabled">
            Details
        </div>
    </div>
     
    <script type="text/javascript">
        var viewModel = {
            detailsEnabled: ko.observable(false),
            enableDetails: function() {
                this.detailsEnabled(true);
            },
            disableDetails: function() {
                this.detailsEnabled(false);
            }
        };
        ko.applyBindings(viewModel);
    </script>
    

    avalon v3参考了rivetsjs的绑定语法,实现如下:

            <div>
                <div data-on-mouseover="enableDetails" data-on-mouseout="disableDetails" >
                    Mouse over me
                </div>
                <div data-display="detailsEnabled">
                    Details
                </div>
            </div>
            <script type="text/javascript">
                require("avalon,ready", function($) {
                    var VM = $.MVVM.convert({
                        detailsEnabled: false,
                        enableDetails: function() {
                            VM.detailsEnabled(true);
                        },
                        disableDetails: function() {
                            VM.detailsEnabled(false);
                        }
                    });
                    $.MVVM.render(VM)
                })
            </script>
    

    要实现循环绑定,knouckoutjs实现如下

    <table>
        <thead>
            <tr><th>First name</th><th>Last name</th></tr>
        </thead>
        <tbody data-bind="foreach: people">
            <tr>
                <td data-bind="text: firstName"></td>
                <td data-bind="text: lastName"></td>
            </tr>
        </tbody>
    </table>
     
    <script type="text/javascript">
        ko.applyBindings({
            people: [
                { firstName: 'Bert', lastName: 'Bertington' },
                { firstName: 'Charles', lastName: 'Charlesforth' },
                { firstName: 'Denise', lastName: 'Dentiste' }
            ]
        });
    </script>
    

    avalon v3实现如下:(data-each-[item]-[index],item, index是可选,名字任取,只要符合变量命名规则就行)

    <script type="text/javascript">
        require("avalon,ready", function($) {
            $.MVVM.render({
                people: [
                    { firstName: 'Bert', lastName: 'Bertington' },
                    { firstName: 'Charles', lastName: 'Charlesforth' },
                    { firstName: 'Denise', lastName: 'Dentiste' }
                ]
            });
    
        })
    
    </script> 
    <table>
        <thead>
            <tr><th>First name</th><th>Last name</th></tr>
        </thead>
        <tbody data-each-p="people">
            <tr>
                <td data-text="p.firstName"></td>
                <td data-text="p.lastName"></td>
            </tr>
        </tbody>
    </table>
    

    avalon v3的优势在于,它完全DSL,我们可以通过点号来查找VM中某一个可用的访问器或命令,作为数据绑定的值。而且实现起来很简单,不需要像knouckoutjs那样编写复杂的JSON编译器。复杂的东西就难维护,不易升级。有关数据绑定以后我与一系列教程介绍它的。

    在数据绑定中,我们借助于一种特殊的属性来指引MVVM干活,格式为data-binding-[param]-[param]。以“-”断开,第二个字符串为绑定器的名字,剩余的为它的参数。比如事件绑定,data-on-click。

    在avalon v3中,它提供了以下默认绑定器,可以通过$.ViewModel.bindings访问到。v3弥合了v2的伤口,完美支持事件绑定与事件代理。

    • data-text
    • data-html
    • data-class
    • data-css-[class]
    • data-attr
    • data-value
    • data-display
    • data-on-[event]
    • data-enable
    • data-disable
    • data-options
    • data-each-[item]-[index]
    • data-with-[value]-[key]
    • data-if
    • data-unless

    avalon v3会将这个属性的名字分解成绑定器与其他参数,再将它的值得到VM中对应的访问器与命令,最后把它们构建成一个叫DOM访问器的东西,作为双向绑定链的顶层,专门与DOM打交道。

    在jQuery时代,ID是我们命中元素最可靠的基点,以此为起点八爪鱼般处理周遭的节点。行为层上,我们通过事件绑定,几乎可以用根据代理一切事件。但jQuery是函数式编程,状态如果在连续在多个回调中使用时,它就要写在回调外面。当然我们可以缓存于某个节点上(data),在另一个回调中通过选择器得到那个节点再重新data出来。但整体上,jQuery代码都是以事件分割成一段段,中间夹杂着一些中间量与处理函数。它们是否能很好工作完全看编程人员的技术水平了。在MVVM中,数据绑定与元素是一体的,因此绝没有偏差。处理交互上,事件以命令的新身份登场, 回调被集合管理于VM,状态也被收笼于VM中,我们不再为如何组织代码伤脑筋,所有都有章而循,新手接力也易上手。MVVM减少对选择器的依赖,将数据与操作绑定在坚固的支点上。

    链接地址

    过几天写些教程,介绍如何用。完!

    机器瞎学/数据掩埋/模式混淆/人工智障/深度遗忘/神经掉线/计算机幻觉/专注单身二十五年
  • 相关阅读:
    练习
    圆形时钟
    鼠标经过改变颜色
    Java基础知识精华部分(个人总结)
    Java随机数总结
    java 获取随机数字的三种方法
    网站开发人员应该知道的61件事
    人人都该了解的十大算法
    String 字符串
    面向对象 JAVA代码
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/2814238.html
Copyright © 2020-2023  润新知