随着前端应用日趋复杂,现在如angular,vue的mvvm框架,基于virtual dom的react等前端库基本成为了各个公司的首选。而以当初最流行的头号大哥backbone为表明的mvc库基本退出了历史舞台。前端
在现现在人人都说mvvm/react多好,backbone多差的时代。笔者看别人文章,看的时候老是感受好像有点道理,看完以后如耳边风通常左耳朵进,右耳朵出。vue
so,痛定思痛以后,笔者定了个小目标,实现了份简易版的backbone库。以设计,实现的角度来对比其它类型库的差别。react
ok,废话很少说,上正菜。git
MVC即将前端应用抽象为Model,View,Control三大模块。View为用户视图,经过浏览器事件接受用户输入。Model为数据模型,他能够随时和后端同步数据。Control则是具体实现View派发的事件,计算并改变Model的数据。es6
UI能够被抽象为模版+数据,随着用户不断的触发浏览器提供的各类事件,交互不断的进行,Control接受了View指令改变着Model的数据,而View则随着Model的改变作出响应,最终展示在用户面前。github
本篇文章的思路来自于backbone,并屏弃了耦合的后端操做。早期MVC并无对Control作严格的划分,也许是数据的改变计算并不那么复杂,因此Control功能在View的事件内完成了,也就是说View模块里面耦合了Control的功能。web
但近几年flux的action,store的出现,View调用action,具体数据变化计算则在store内部实现,也算是把Control功能从View内部抽象出来了吧。后端
为对象提供对事件的处理和回调,内部实现了观察者(订阅者)模式,如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; };
无状态,实例化的时候能够对应多个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在backbone里被抽象为object类型的Model和array类型的Collection
承载着应用的状态,能够随时和后端保持同步。
内部实现了对数据变化的监听,一旦发生变化则通知观察者View发生变化。
监听数据的变化,对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); },
提供数组类型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); }
生产环境下须要在保留原生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; };
整篇文章基本是围绕着以下2点
view-model,collection-model的观察者模式的实现展开,期间view,model的销毁则取消与之有关联对象的关系,如view销毁时,注销掉与之关联的model的回调函数。
监听数据变化,并通知观察者做出响应,如model变化后触发trigger('change')
好了,文章草草写到这了,多谢各位看官,以上也是纯我的观点,有问题欢迎各位web前端mvc设计指教。