ToDoMVC-maria

本文详细剖析了Maria MVC框架的实现方式,介绍了其如何遵循Smalltalk-80 MVC原型,重点讲解了Model、View与Controller之间的交互机制及代码实现细节。

Framework ToDoMVC comparision 首篇。

  • ToDoMVC
    • maria
    • Backbone
    • Knockout
    • AngularJS
    • vanlillajs

源码链接
下文有错,欢迎拍砖

TL;DR(直接看结论)

maria

maria在大量的mv*框架中是最遵循Smalltalk-80 MVC原型的。(mvc最初出现在smalltalk语言中)
引用一段《javascript design pattern》的一段话,"which is faithful to MVCs origins - Models are models, Views are views and Controllers are nothing but controllers."

346581-20151021171409849-2043891314.png

引图 - 深入可阅读
scaling-isomorphic-javascript-code

index.html
<html>
<link rel="stylesheet" href="bower_components/todomvc-common/base.css"/>
<!--
//lots of scripts loaded
-->
<script>
    maria.on(window, 'load', function() {
        var model = new checkit.TodosModel();
        var view = new checkit.TodosAppView(model);
        document.body.appendChild(view.build());
});
</script>
</html>

从上图得知Model层在被修改后Fires Events 传递给view层。
所以在index.html中有了这段代码

var view=new checkit.TodosAppView(model);//view 监听model的修改

这里采用了 pub/sub模式(发布-订阅),多个view可以监听相同的model,model的修改会同时推送给所有订阅它的views。
346581-20151021171447958-1520268295.png

上图显示了 我修改了todoModel的_done属性,同时推送给view和view2。

重新回到index.html中的这段代码中,我发现没有生成todosController和关联view和controller,这令我刚开始十分困惑,后来想到controller应该是默认从命名关联的。于是我翻了下源码,

maria.View.prototype.getController = function() {
    if (!this._controller) {
        //这里如果发现view没有关联controller,尝试关联默认的controller
        this.setController(this.getDefaultController());
    }
    return this._controller;
};

maria.View.subclass = function(namespace, name, options) {
    //...
    // 默认的controller和view是相同的前缀
    var controllerConstructorName = options.controllerConstructorName || name.replace(/(View|)$/, 'Controller');
    //...
    if (!Object.prototype.hasOwnProperty.call(properties, 'getDefaultControllerConstructor')) {
        properties.getDefaultControllerConstructor = function() {
             //返回view对应的默认controller构造函数
             //后续代码会new一个controller出来
            return controllerConstructor || namespace[controllerConstructorName];
        };
    }
    //...
};

下面随遍找了一个view层的代码。

TodosInputView.js
maria.ElementView.subclass(checkit, 'TodosInputView', {
    uiActions: {
        'keyup    .content': 'onKeyupInput'   ,
        'keypress .content': 'onKeypressInput'
    },
    properties: {
        getInputValue: function() {
            return this.find('.content').value;
        },
        clearInput: function() {
            this.find('.content').value = '';
        }
    }
});

上面代码中的

uiActions{
    //controller层有队以ing的onKeyupInput方法
    'keyup    .content': 'onKeyupInput',
    'keypress .content': 'onKeypressInput'
}

对应了第一图的View -> Controller (pass call),view委托DOM事件给controller,并暴露出一些接口供controller调用。

 properties: {
        //下面的接口会在controller层里调用
        getInputValue: function() {
            return this.find('.content').value;
        },
        clearInput: function() {
            this.find('.content').value = '';
        }
    }

贴上对应的controller层代码

TodosInputController.js
maria.Controller.subclass(checkit, 'TodosInputController', {
    properties: {
        onKeyupInput: function() {//...},
        onKeypressInput: function(evt) {
            if (evt.keyCode == 13) {
                var view = this.getView();
                var value = view.getInputValue();
                if (!checkit.isBlank(value)) {
                    var todo = new checkit.TodoModel();
                    todo.setContent(value);
                    this.getModel().add(todo);
                    view.clearInput();
                }
            }
        }
    }
});

从上面的代码可以看出,controller处理view层传递过来的event,但没有主动监听DOM事件(view层自己监听)。仔细看onKeypressInput方法:

if (evt.keyCode == 13) {
    var view = this.getView();
    var value = view.getInputValue();//从view层暴露的接口检测
    if (!checkit.isBlank(value)) {
        //生成新的model并添加到model集合里
        var todo = new checkit.TodoModel();
        todo.setContent(value);
        this.getModel().add(todo);//这里有疑点
        view.clearInput();//通过view的接口修改view的显示
    }

controller层的代码显示了controller只负责业务逻辑处理,并不负责DOM事件监听和DOM元素修改。(Append,delete and etc)
不过这里我有个疑点this.getModel().add(todo);执行这段代码后,并会添加上一个todo标签。
346581-20151021171524380-616811446.png
-> 346581-20151021171547145-1312874345.png


然而我在对应的model代码中并没有发现add的方法,猜测是内置函数。于是在maria源码中翻到下面一段

//类似的方法还有maria.SetModel.protoype.delete
maria.SetModel.prototype.add = function() {
    var added = [];
    for (var i = 0, ilen = arguments.length; i < ilen; i++) {
        var argument = arguments[i];
        if (hormigas.ObjectSet.prototype.add.call(this, argument)) {
            added.push(argument);
            //...省略了大量伪事件委托和监听
        }
    }
    var modified = added.length > 0;
    if (modified) {
        this.dispatchEvent({type: 'change', addedTargets: added, deletedTargets[]});
    }
    return modified;
};

其中这一段

if (modified) {
    //model集合在修改后发布对应的时间给订阅它view层
    this.dispatchEvent({type: 'change', addedTargets: added, deletedTargets[]});
}

对应了最开始的var view = new checkit.TodosAppView(model);建立的pub/sub模型。然后我的疑惑点还是没有消除,model是否也参与DOM的crud?答案当然是:没有!!我又翻到了view的一段源码

//类似的方法还有maria.SetView.prototype.handleAdd
maria.SetView.prototype.handleDelete = function(evt) {
    var childModels = evt.deletedTargets;
    for (var i = 0, ilen = childModels.length; i < ilen; i++) {
        var childModel = childModels[i];
        var childViews = this.childNodes;
        for (var j = 0, jlen = childViews.length; j < jlen; j++) {
            var childView = childViews[j];
            if (childView.getModel() === childModel) {
                this.removeChild(childView);
                childView.destroy();
                break;
            }
        }
    }
};

上面的代码显示了DOM的remove操作,整合了下model的代码,可以看出model层的任务包括了数据模型的crud而DOM的crud由view自己操作,不过是由model派发的指令决定具体操作。

贴上一段model的代码

TodosModel.js
maria.SetModel.subclass(checkit, 'TodosModel', {
    properties: {
        isAllUndone: function() {
            return this.every(function(todo) {
                       return !todo.isDone();
                   });
        },
        //...
        deleteDone: function() {
            var doneTodos = [];
            this.forEach(function(todo) {
                if (todo.isDone()) {
                    doneTodos.push(todo);
                }
            });
            this['delete'].apply(this, doneTodos);
        }
    }
});

其中deleteDone函数中的 this['delete'].apply(this, doneTodos);在进行内部model collections crud后触发

maria.SetModel.protoype.delete=function(){
    //...
    var modified = deleted.length > 0;
        if (modified) {//推送需要delete的model元素,同步UI和Model
            this.dispatchEvent({type: 'change', addedTargets: [], deletedTargets: deleted});
        }
}

推送事件给view层进行DOM crud

maria.SetView.prototype.handleDelete = function(evt) {
    var childModels = evt.deletedTargets;
    for (var i = 0, ilen = childModels.length; i < ilen; i++) {
        //...
        //这里进行真正的DOM操作
        this.removeChild(childView);   
    }
};

Review

  • View

    • 自己监听DOM事件,然后事件的处理由对应的Controller进行
    • 开放数据接口给Controller
    • 底层DOM crud
  • Controller

    • 处理View指派的DOM事件
    • 操作Model
    • 并不参与到DOM的crud
  • Model

    • 处理Controller的操作
    • 然后发送对应的event给View进行DOM的crud

转载于:https://www.cnblogs.com/thatgeek/p/4898404.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值