Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

简易版前端mvc设计 #1

Open
laughing-pic-zhu opened this issue Apr 6, 2017 · 0 comments
Open

简易版前端mvc设计 #1

laughing-pic-zhu opened this issue Apr 6, 2017 · 0 comments

Comments

@laughing-pic-zhu
Copy link
Owner

laughing-pic-zhu commented Apr 6, 2017

web前端mvc库实现

前言

随着前端应用日趋复杂,如今如angular,vue的mvvm框架,基于virtual dom的react等前端库基本成为了各个公司的首选。而以当初最流行的头号大哥backbone为代表的mvc库基本退出了历史舞台。

在现如今人人都说mvvm/react多好,backbone多差的时代。笔者看别人文章,看的时候总是感觉好像有点道理,看完之后如耳边风一般左耳朵进,右耳朵出。

so,痛定思痛之后,笔者定了个小目标,实现了份简易版的backbone库。以设计,实现的角度来对比其它类型库的差异。

ok,废话不多说,上正菜。

思路整理

MVC即将前端应用抽象为Model,View,Control三大模块。View为用户视图,通过浏览器事件接受用户输入。Model为数据模型,他可以随时和后端同步数据。Control则是具体实现View派发的事件,计算并改变Model的数据。

UI可以被抽象为模版+数据,随着用户不断的触发浏览器提供的各种事件,交互不断的进行,Control接受了View指令改变着Model的数据,而View则随着Model的改变做出响应,最终展现在用户面前。

流程图:

mvc

模块划分

本篇文章的思路来自于backbone,并屏弃了耦合的后端操作。早期MVC并没有对Control做严格的划分,也许是数据的改变计算并不那么复杂,所以Control功能在View的事件内完成了,也就是说View模块里面耦合了Control的功能。

但近几年flux的action,store的出现,View调用action,具体数据变化计算则在store内部实现,也算是把Control功能从View内部抽象出来了吧。

Event模块

为对象提供对事件的处理和回调,内部实现了观察者(订阅者)模式,如view订阅了model的变化,model变化之后则通知view。

基本方法。

  • on函数通过event名,在object上绑定callback函数,将回调函数存储在数组里。

  • off函数移除在object上绑定的callback函数

    • 通过event名移除指定callback。如object.off("change", onChange)
    • 通过event名移除所有callback。如object.off("change")
    • 移除所有指定callback。如object.off(null, onChange);
    • 移除所有指定context的callback。如object.off(null, null, context);
    • 清空object所有的callback。如object.off()
  • trigger函数通过event名,找到object对应的数组,并触发所有数组内回调函数。

注意事项

其所有方法应该支持类似on(name,callback),on('name1 name2 name3',callback), on({name1:callback1,name2:callback2})

这时候则可以抽象内部公用方法。通过递归的方式,on({name1:callback1,name2:callback2})类型的和on('name1 name2 name3',callback)类型,最终转化为最基本的on(name,callback)类型。核心代码如下:

this.eventsApi = function (iteratee, name, callback, context) {
    let event;
    if (name && typeof name === 'object') {
      Object.keys(name).forEach(key=> {
        event = this.eventsApi(key, name[key], context);
      })
    } else if (SEPARATE.test(name)) {
      var keys = name.split(SEPARATE);
      keys.forEach(key=> {
        event = iteratee.call(this,key, name[key], context);
      });
    } else {
      event = iteratee.call(this,name, callback, context);
    }
    return event;
};

View模块

  • 无状态,实例化的时候可以对应多个model实例,并以观察者的身份观察这些model的变化,通过这些model数据,加上指定的模版渲染dom,展示UI。
  • 销毁的时候注销掉所有model的观察,取消与相关model之间的关联。
  • 实例化的时候通过事件委托注册浏览器事件

实现

  • _ensureElement,确保View有一层dom包裹,如果this.el这个dom不存在,则通过id,className,tagName创建一个dom并赋值于this.el。

  • listenTo,将model与view实例关联起来,并收集关联model,存储于listenTo数组内,内部实现则是调用model的on函数

  • stopListening,view销毁前调用,通过listenTo数组找到关联model,并取消view与这些model之间的观察者关系。

  • $,将dom的查找定位在 this.$el下

  • delegateEvents,事件委托,以{'click #toggle-all': 'choose'}为例,为在this.el子节点的id等于toggle-all的dom注册click事件choose函数。核心代码如下:

     delegateEvents: function (events) {
         var $el = this.$el;
         Object.keys(events).forEach(item=> {
           var arr = item.split(' ');
           if (arr.length === 2) {
             var event = arr[0];
             var dom = arr[1];
             $el.on(event + '.delegateEvents' + this.$id, dom, this[events[item]].bind(this));
           }
         })
     },
    
  • undelegateEvents,注销掉通过delegateEvents注册的dom事件

Model模块

Model在backbone里被抽象为object类型的Model和array类型的Collection

  • 承载着应用的状态,可以随时和后端保持同步。
  • 内部实现了对数据变化的监听,一旦发生变化则通知观察者View发生变化。

Model

监听数据的变化,对model的修改,删除之后调用对应的trigger函数,通知订阅了model变化的view。

  • set函数,改变model数据,并触发change事件

     set: function (obj) {
         this._changing = true;
         this.changed = obj;
         this._previousAttributes = Object.assign({}, this.attributes);
         this.attributes = Object.assign({}, this.attributes, obj);
         const keys = [];
         Object.keys(obj).forEach(key=> {
           keys.push(key);
           this.trigger('change:' + key, this);
         }, this);
     
         if (keys.length > 0) {
           this.trigger('change', this);
         }
         this._changing = false;
      },
    
  • destroy函数触发destroy事件

     destroy: function () {
        this.stopListening();
        this.trigger('destroy', this);
     },
    

Collection

提供数组类型models的push,unshift,pop,shift,remove,reset等功能。push,unshift实际调用add函数,pop,shift实际调用remove函数。

  • add函数支持任意索引插入指定数组,触发add事件。核心的代码如下:

     export const splice = (array, insert, at)=> {
       at = Math.max(0, Math.min(array.length, at));
       let len = insert.length;
       let tail = [];
       for (let i = at; i < array.length; i++) {
         tail.push(array[i]);
       }
       for (let i = 0; i < tail.length; i++) {
         array[i + at + len] = tail[i];
       }
       for (let i = 0; i < len; i++) {
         array[i + at] = insert[i];
       }
       return array;
     };
    
  • remove函数支持删除指定model,触发update事件。

  • _addReference,调用add方法新增model时,通过观察者模式增加该model与collection之间的关联,model的变化通知collection。核心代码如下:

     _addReference: function (model) {
     model.on('all',this._onModelEvent,this);
     }
    
  • _removeReference,调用remove,reset移除model时,取消该model与collection关联。核心代码如下:

      _removeReference: function(model) {
        if (this === model.collection) delete model.collection;
        model.off('all', this._onModelEvent, this);
      }
    

extend

生产环境下需要在保留原生View,Model类的功能情况下做一些业务拓展,这时候需要用到类的继承。

虽然es6支持extend继承,但这边我还是手写了一份。思路则是返回一个构造函数,该函数的原型为新的实例对象props,而props的原型对象则是父函数的原型(有点拗口,自己看代码理解)。
核心代码如下:

export const extend = function (props) {
  var parent = this;
  var child = function () {
    parent.apply(this, arguments);
  };
  child.prototype = Object.assign(Object.create(parent.prototype), props, { constructor: child });
  return child;
};

todomvc效果图

todomvc

源码

web前端mvc实现

小节

整篇文章基本是围绕着如下2点

  • view-model,collection-model的观察者模式的实现展开,期间view,model的销毁则取消与之有关联对象的关系,如view销毁时,注销掉与之关联的model的回调函数。
  • 监听数据变化,并通知观察者作出响应,如model变化后触发trigger('change')

好了,文章草草写到这了,多谢各位看官,以上也是纯个人观点,有问题欢迎各位web前端mvc设计指教。

@laughing-pic-zhu laughing-pic-zhu changed the title 简易的mvc设计 web前端mvc设计 Apr 18, 2017
@laughing-pic-zhu laughing-pic-zhu changed the title web前端mvc设计 简易版前端mvc设计 Apr 21, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant