本文已同步在个人博客前端
在这个react和vue如日中天、jquery逐渐被你们抛弃的年代,我仍是想要来讲一说backbone。vue
16年6月初,在没有任何前端框架使用经验、js水平也较通常的状况下,被告知须要在几个工做日内搭建完成一个后台管理系统,没有页面设计稿、没有组件库,一切都是从零开始。当时面临两个选择,backbone和react。虽然我很但愿可以拿react来练手,可是考虑到学习成本和项目时间问题,leader仍是建议我使用backbone来完成,就这样,一直用到差很少如今。虽然到项目后期业务场景愈来愈复杂,backbone的这套技术栈体现出愈来愈多的问题,可是对于小型项目来讲,我仍是认为backbone是个不错的选择,并且学习成本低,上手极快~
backbone是个很是轻量的mvc库,本文将基于backbone的源码谈一谈其实现的核心部分,以及其中一些巧妙的思想和代码实现技巧。react
事件部分的核心逻辑其实比较简单,简化一下能够用以下的伪代码来表示:jquery
var events = { evt1: handlers1, evt2: handlers2, ... } //注册事件 function on(name, callback) { const handlers = events[name] || (events[name] = []); handles.push(callback); } //触发事件 function trigger(name) { if (!events[name]) { return; } const handlers = events[name]; for (let i = 0, len = handlers.length; i < len; i++) { handlers[i](); } } //解除绑定 function off(name) { delete events[name]; }
固然了,以上写法有不少细节的地方没有加入进来,好比上下文绑定、对多种传参方式的支持、触发事件时对事件处理器传参的处理等等。redux
咱们知道,对于MVC来讲,M(模型)的变化会反映在V(视图)上,其实是视图监听了模型的变化,再根据模型去更新自身的状态,这当中最重要的一个功能就是监听(listen)。该功能也是由Events部分实现的,包括:listenTo、stopListening等前端框架
listenTo和on相似,都是监听一个事件,只不过listenTo是监听其余对象的对应事件,而on是监听自身的对应事件。stopListening同理。好比:mvc
a.on('testevent', function(){ alert('1'); }); a.trigger('testevent');
若是其余对象但愿监听a的testevent事件呢?则能够经过listenTo来实现:app
b.listenTo(a, 'testevent', function() { alert('catch a\'s testevent'); })
其中第一个参数为要监听的对象,第二个参数为事件名称框架
当调用on方法的时候,会为对象自身建立一个_event属性;而调用listenTo方法时,会为监听对象建立_event属性,同时为了记录监听者,被监听对象还会建立一个_listeners属性:dom
a.on('testevent', handlers1);
a会变成:
{ _events: { testevent: [handlers1] }, on: function() { //... } ... }
当有其余对象监听a时,如:
b.listenTo(a, 'testevent', handlers2);
a会变成:
{ _events: { testevent: [handlers1, handlers2] }, _listeners: b, on: function() { //... } ... }
在事件机制的实现部分,除了核心逻辑以外,在对一些方法的使用上,也很考究。为了绑定函数执行的上下文,咱们常常会使用apply,call这些方法,而源码中屡次提到apply的执行效率要低一些,所以,有这样的实现:
// A difficult-to-believe, but optimized internal dispatch function for // triggering events. Tries to keep the usual cases speedy (most internal // Backbone events have 3 arguments). var triggerEvents = function(events, args) { var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; switch (args.length) { case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return; } };
有关为何call比apply的效率更高的解释能够参考这篇文章
model用于维护数据,其中最关键的是对数据的更新部分,即set
// Trigger all relevant attribute changes. if (!silent) { if (changes.length) this._pending = options; for (var i = 0; i < changes.length; i++) { this.trigger('change:' + changes[i], this, current[changes[i]], options); } } // You might be wondering why there's a `while` loop here. Changes can // be recursively nested within `"change"` events. 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;
每次set数据的时候,根据数据变化的部分,使用trigger方法触发相应的事件;在set数据时,若是不但愿触发change事件,能够设置silent为true。
这部分比较容易让人产生疑惑的是while循环部分,这个while循环有什么用呢?举个例子:
new Model.on("change", function() { console.log('model change'); }).set({ a: 1 });
以上代码是最简单的状况,监听change事件,当model变化时,打印出model change
在源码中,当第一次进入while后,紧接着this._pending被置为false,而事件触发回调函数也不会更改this._pending的值,所以再次判断时条件不成立,while内的代码段只会执行一次。
可是实际状况每每不是这么简单,如代码注释中所说,有可能会有嵌套的状况,好比:
new Model.on("change", function() { this.set({ b: 1 }) }).set({ a: 1 });
在这种状况下,第一次trigger触发change的回调函数中,又再次对model进行了更新操做,
this.set({ b: 1 })
每次set时,会更新this._pending为true,这样当set b后,就会再次进入while内,触发change事件。而若是没有使用while循环的话,对b属性更新的操做就没法触发change事件,致使其监听者到没法根据最新的数据更新自身状态。
View部分的实现比较简单,其中最主要的是events部分,一般在一个View中,都会绑定一些dom事件,好比:
{ 'click .preview-btn': 'preview', 'click .save-btn': 'save' }
主要有两点须要说明:
delegateEvents: function(events) { events || (events = _.result(this, 'events')); if (!events) return this; this.undelegateEvents(); for (var key in events) { var method = events[key]; if (!_.isFunction(method)) method = this[method]; if (!method) continue; var match = key.match(delegateEventSplitter); this.delegate(match[1], match[2], _.bind(method, this)); } return this; }
以上部分介绍了backbone中最核心部分的实现机制。能够看到其实现很是的简单,可是对于小型项目来讲,确实能够帮咱们作一些对数据的维护和管理工做,提升开发效率。可是随着业务逐渐复杂,会愈来愈发现,backbone所能作的实现有限,而对于数据维护部分也很是不方便,尤为是须要是对多个模块间的通讯和数据维护问题。后续我会结合在复杂业务中的使用谈一谈backbone的缺点,以及更优的框架能带来的便利。
说句题外话,虽然去年因为时间缘由选择了backbone,这一年基本没有在复杂业务场景中使用react技术栈,都是本身作个小demo练手。可是也正是由于有了使用backbone去写复杂业务的经历,在数据维护上和模块间通讯上很是麻烦,以及backbone渲染dom时直接所有更新的会致使的页面渲染性能问题,才更让我感受react + redux的美好。知其然,还需知其因此然啊~ ~ 否则我以为我可能会一直疑惑为何要用一套这么复杂的技术栈,异步请求这块写起来还那么麻烦。这么看,坏事也算是好事了吧~~