Giter Club home page Giter Club logo

mvc's People

Contributors

laughing-pic-zhu avatar

Stargazers

 avatar

Watchers

 avatar

mvc's Issues

简易版前端mvc设计

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设计指教。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.