在这个大前端时代,各路MV*
框架如雨后春笋搬涌现出来,在infoQ上有一篇
12种JavaScript MVC框架之比较,胜出的是Ember.js
,固然这只是
Gordon L. Hempton的一家之言(Ember.js确实有其强大之处),关于孰强孰弱,你们确定有本身心中的No.1。关于到底有多少种前端MVC
框架,愚安我确定是不知道的,除了上面提到的12种之外,还有不少国内国外的MV*
框架,你们造轮子的热情也无比高涨,各类demo活跃在
各大技术社区。一时间有句调侃的话——前端MV*哪家强,不服写个TodoList
,这里有一个目前主流的MV*框架写的Todolist的example,叫作
Helping you select an MV* framework你们能够稍做了解。javascript
咱们知道,MV*框架的优点在于,在结构上其能够组织良好的结构化、模块化代码;在逻辑上,实现如下功能:前端
在诸多的此类框架中,笔者真正在生产环节使用过的聊聊无几,如,Angular,Backbone,Ember,React,其他的我就不敢多言了。
其中,React.js
使用的是一种叫作virtual dom
的概念,让我眼前一亮。Angular.js
采用一种预编译技术,将dom中的元素与Controler的scope
结合起来,而后采起脏轮训的方式监听两者的变化,实现模型数据与dom间的双向绑定,实时更新。而Ember.js
做为Ruby on Rails
框架开发团队的
又一力做,其野心能够从其类库的强大看出,单纯的Ember.js文件就有足足141kb,并且Ember在视图层提供了数据绑定的功能,能够轻松实现
页面数据与模型的数据绑定。java
而今天愚安要说的Backbone.js
相比以上三者,就显的弱小多了,其文件大小只有18kb(无依赖未压缩)。为了单纯的实现一个MVC结构,Backbone
并无像其余框架那样,花大力气加强本身的工具类库。其在操做dom和ajax上彻底依赖jQuery
,在工具类上彻底依赖underscore
。ajax
正是由于如此,Backbone的结构十分简洁清晰,易于扩展,因此Backbone得开源社区十分活跃,插件数量在全部MV*
框架中鹤立鸡群。因此,加上注释也只有1700
余行的Backbone是一个纯净的MVC框架。浏览器
打开Backbone的官网,咱们发现构成Backbone的模块只有Events
,Model
,Collection
,Router
,History
,Sync
,
View
,noConflict
几部分组成。前端框架
折叠后的Backbone关键代码以下:mvc
Backbone.VERSION = '1.1.2';//版本 Backbone.$ = $; //出让对Backbone命名空间的全部权 Backbone.noConflict = function() { }; //Events事件 var Events = Backbone.Events = { }; _.extend(Backbone, Events); //Model模型 var Model = Backbone.Model = function(attributes, options) { }; _.extend(Model.prototype, Events, { }); //Collection集合 var Collection = Backbone.Collection = function(models, options) { }; _.extend(Collection.prototype, Events, { }); //View视图 var View = Backbone.View = function(options) { }; _.extend(View.prototype, Events, { }); //sync同步方法 Backbone.sync = function(method, model, options) { }; //贴出只是为了佐证Backbone的ajax是使用jQuery的ajax,而不是像Angular.js那样实现本身的$http Backbone.ajax = function() { return Backbone.$.ajax.apply(Backbone.$, arguments); }; //Router路由 var Router = Backbone.Router = function(options) { }; _.extend(Router.prototype, Events, { }); //History浏览历史(window.history) var History = Backbone.History = function() { }; _.extend(History.prototype, Events, { }); Backbone.history = new History; //在underscore基础上实现的关键性的继承方法,这个也很关键 var extend = function(protoProps, staticProps) { }); Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
不得不认可Backbone的代码真的很是简洁清晰。app
经过上面的简单折叠代码,咱们能够看出,不论是Model
,Collection
,Router
,History
,History
,甚至是Backbone自己,
都或经过原型链或直接继承了Backboe.Events
。这也是在Backbone的代码编写顺序上Backboe.Events
会放在最前面的缘由。框架
那么Backboe.Events
到底作了些什么呢?仍是贴代码最有说服力:dom
var Events = Backbone.Events = { //绑定一个事件到`callback`回调函数上。经过 `"all"`能够绑定这个回调函数到全部事件上 on: function(name, callback, context) { }, //绑定一个仅会被触发一次的事件。在这个事件的回调函数被调用一次以后,这个回调函数将被移除 once: function(name, callback, context) { }, //移除一个或多个的事件回调 off: function(name, callback, context) { }, //触发一个或多个事件,调用对应的回调函数。 trigger: function(name) { }, //`on`和`once`的控制反转版本。告诉当前对象去监听另外一个对象的事件 listenTo: function(obj, name, callback) { }, listenToOnce: function(obj, name, callback) { }, //告诉当前对象中止对指定对象的指定事件的监听,或中止全部监听 stopListening: function(obj, name, callback) { } };
基于这样的一个Events对象的实现,Backbone能够轻松实现了不少功能,如在Model.set(key,value)
时触发一个change
事件,视图层在扑捉到
这个事件的时候,对dom作出相应的更新,这样就实现了Model层到View层的绑定。例如:
var View = Backbone.View.extend({ initialize:function(){ this.listenTo(this.model,'change:name',this.onNameChange); }, onNameChange:function(){ this.$('.name').text(this.model.get('name')); } template: '<span class="name"></span>' }); var m = new Backbone.Model({name:'Jack'}); var v = new View({model:m}); m.set('name','John');
那么,这里的set为何会触发change事件呢?具体实现我仍是贴一下源码和本身的中文注释:
Backbone.Model.prototype.set = function (key, val, options) { var attr, attrs, unset, changes, silent, changing, prev, current; if (key == null) return this; //格式化参数 if (typeof key === 'object') { attrs = key; options = val; } else { (attrs = {})[key] = val; } options || (options = {}); //执行当前对象的验证方法 if (!this._validate(attrs, options)) return false; //提取属性和可选项 unset = options.unset; silent = options.silent; changes = []; changing = this._changing; this._changing = true; //标记当前Model是否改变,并记录改变的属性及其变化先后的值 if (!changing) { this._previousAttributes = _.clone(this.attributes); this.changed = {}; } current = this.attributes, prev = this._previousAttributes; //若改变的属性为id,则同时改变当前对象的id if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; //遍历`set`的属性,更新或删除对应属性的当前值 for (attr in attrs) { val = attrs[attr]; if (!_.isEqual(current[attr], val)) changes.push(attr); if (!_.isEqual(prev[attr], val)) { this.changed[attr] = val; } else { delete this.changed[attr]; } unset ? delete current[attr] : current[attr] = val; } //若非沉默更新(传参时options.silent=true),触发change:attr事件 //attr为各个对应被set的属性的key,并传当前值到回调函数 if (!silent) { if (changes.length) this._pending = options; for (var i = 0, l = changes.length; i < l; i++) { this.trigger('change:' + changes[i], this, current[changes[i]], options); } } //change能够递归嵌套到change事件中 if (changing) return this; if (!silent) { while (this._pending) { options = this._pending; this._pending = false; this.trigger('change', this, options); } } this._pending = false; this._changing = false; return this; }
因此若想,监听到Model的属性变化,改变Model的属性值时,必须采用Model.set()
方法,而不能简单的使用Model.attributes[key] = value
。
其实这个是一个兼容的作法,咱们知道Backbone对低版本浏览器的支持很是好,若是不考虑这些的话,彻底可使用更高级的API
Object.observe(this.attributes, function(changes){ if (!silent) { if (changes.length) this._pending = options; for (var i = 0, l = changes.length; i < l; i++) { this.trigger('change:' + changes[i].name, this, current[changes[i].name], options); } }.bind(this));
固然,这只是愚安个人一点意淫,没有什么实际意义。
实际上,Backbone内部事件除了change
之外还有不少,这里简单列举一下:
上面咱们已经知道基于强大的Backbone.Events
,咱们能够轻松的实现model到view的绑定,反之呢?
Backbone没有相似Angular.js的预编译机制,也没有View-Model
的概念,从View到Model的绑定依赖于原生DOM事件的监听,完整双向绑定如:
var View = Backbone.View.extend({ initialize:function(){ this.listenTo(this.model,'change:name',this.onNameChange); }, onNameChange:function(){ this.$('.name').text(this.model.get('name')); } template: '<input type="text" class="name-input"><span class="name"></span>', events: {'input .name-input': '_changeName'}, _changeName: function(e){ var value = e.currentTarget.value; this.model.set('name', value); return false; } }); var m = new Backbone.Model({name:'Jack'}); var v = new View({model:m}); m.set('name','John');
须要注意的是View层的events字典,其实就是DOM事件,而不是Backbone.Events
。并且,纯净的Backbone这里用的是jQuery的jQuery
的on
方法
进行绑定的。
好的,愚安又贴了不少源码,和一些本身对文档的不成翻译,有没有干货,见仁见智了。另外,本人是很是推荐刚接触前端框架的童鞋,以Backbone
作为开始的。就像学习PHP的MVC框架,我很是推荐以codeigniter
做为开始的,没有强大的封装,但有着最基本纯净的MVC思想。
注:本文是以Backbone的1.1.2版本为基础的
引用: