带你读Backbone源码解读之Events实现

Backbone源码解读

Backbone在流行的前端框架中是最轻量级的一个,所有代码实现一共只有1831行1。从前端的入门再到Titanium,我虽然几回和Backbone打交道可是却对它的结构知之甚少,也促成了我想读它的代码的原始动力。这个系列的文章主要目的是分享Backbone框架中能够用于平常开发的实践参考,力求可以简明扼要的展示Backbone Modal, Controller和Sync这些核心内容,但愿可以对你们学习和使用Backbone有一些帮助。前端

这个系列的文章将包括如下3篇内容:后端

  • Backbone源码解读之Events实现
  • Backbone源码解读之Router, History实现
  • Backbone源码解读之Model, Collection, Sync实现

本文是它的第一篇《Backbone源码解读之Events实现》数组

Backbone Events实现

Backbone的Event是整个框架运转的齿轮。它的优美之处在于它是Backbone的一个基础方法,经过_.extend的方法Mixin到Backbone的每个模块中。前端框架

//Model
_.extend(Model.prototype, Events, {
    changed: null,

    //other Model prototype methods
    //...
}

Event的基础概念

事件的管理是一个绑定在Events这个命名空间中的_events对象来实现的。
它的结构是name: [callback functions]的key-callback_array键值对。架构

this._events = {
    change: [callback_on_change1, callback_on_change2, ....],
    ....
}

当事件发生的时候,event从这个对象中根据事件的名称取得回调函数数组,而后循环执行每一个回调函数,也就说明了为何屡次绑定会重复触发屡次事件。app

Event包括on, off, trigger三个基础方法,其他的全部方法均是对它们的扩展。框架

on(name, callback, context)

on接受3个参数,包括事件的名称,回调函数和回调函数执行的上下文环境。其中context是可选参数,若是你不是很熟悉JS的执行上下文环境能够暂时不用管它。异步

抛开全部Backbone的花哨的检查,执行on的操做本质就是向_events中name对应的回调函数数组[callback functions]中Push新的函数。
简单来讲代码实现就是这个样子:函数

Events.on = function(name, callback, context) {
    if (callback) {
      var handlers = events[name] || (events[name] = []);
      handlers.push({callback: callback, context: context, ctx: context || this});
      }
      return this;
};

至于你在看源代码的时候会长不少,那是由于一方面Backbone要处理关于_events以及_events[name]未初始化的两种特殊状况。另外一方面eventsApi,onApi这些方法是为了处理on时候你传入的不是一个string类型的名称和一个callback函数所作的条件处理。
例以下面两种方法都是合法的:学习

//传入一个名称,回调函数的对象
model.on({ 
    "change": on_change_callback,
    "remove": on_remove_callback
});  

//使用空格分割的多个事件名称绑定到同一个回调函数上
model.on("change remove", common_callback);

可是核心其实都是同一个绑定函数。

值得注意的一点是因为Backbone接受all做为name的参数,而且将回调函数保存在_events.all中,关于它的执行详细能够参考trigger。

off(name, callback, context)

与on不一样,off的3个参数都是可选的。

  • 若是没有任何参数的时候,off至关于把对应的_events对象总体清空。

    if (!name && !callback && !context) {
        this._events = void 0;
        return this;
    }
  • 若是有name参数可是没有具体清除哪一个callback的时候,则把_events[name]对应的内容所有清空。

    if (!callback && !context) {
        delete this._events[name];
        continue;
    }
  • 若是还有进一步详细的callback和context的状况下,则进入[callback functions]中进行检查,移除具体一个回调函数的条件很是严苛,必需要求上下文和函数与原来彻底一致,也就是说若是传入的callback或者context不是原有on对象的引用,而是复制的话,这里的off是无效的。

    var remaining = [];
    if(
        callback && callback !== handler.callback &&
        callback !== handler.callback._callback ||
        context && context !== handler.context
    ){
        //保留回调函数在数组中
    }

trigger(name)

trigger取出name对应的_events[name]以及_event.all中的callback函数。
须要注意的一点是,触发对应名称的callback和all的callback使用了不同的参数,all的参数中还包含了当前事件的名称。

//当绑定3个如下回调函数的时候Backbone会作以下优化处理,听说这样是能够提升执行效率的。    
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;
    }
};

最简单的Backbone事件使用

从使用上来说,对一个对象进行事件的on绑定。而后在同一个对象的其余函数执行过程当中,或者其余的对象中,触发该对象的trigger方法和对应的事件名称来执行其上绑定的callback函数。

其余辅助函数

接下来再看看Event中进一步定义了哪些其余辅助函数

once(name, callback, context)

效果相同与on,不过对应的callback函数仅执行一次。固然,你一样能够在once绑定的回调函数执行前手动经过off将其移除。

来看看Once的实现,因为添加了执行过程当中的移除方法,once在实际实行on的时候使用了以下匿名函数:

var once = _.once(function() {function(){
    self.off(name, once);
    callback.apply(this, arguments);
});
return this.on(name, once, context);

可是细心的你必定发现,保存在_event数组中的函数是once这个匿名函数了。可是用户并不知道Backbone的这些操做,在取消绑定时仍然会使用原来的回调函数来试图解除绑定。上面咱们也提到,必须使用彻底一致的函数才可以取消绑定,那么为何还可以成功呢?

这里Backbone作了一个小小的操做,不知道你有没有注意到上面off函数中有这样一行内容?

callback !== handler.callback._callback

既然callback是咱们传入的回调函数,那么哪里来的_callback这个属性呢?答案就在once里面。

var once = _.once(function() {...取消绑定,执行callback);
once._callback = callback;

也就是Backbone在返回以前悄悄的为once这个函数添加了一个_callback的属性,用来保存原来的回调函数,这样用户在传入原来的回调函数取消绑定的时候,off会检查函数时候有_callback这个属性和用户传入的函数匹配,一样能够取消绑定。

listenTo(obj, name, callback)、listenToOnce(obj, name, callback)和stopListening(obj, name, callback)

除了将对象自己expose给另外一个对象,让另外一个对象执行trigger方法触发该对象上绑定的event之外。Event还进一步提供了listenTo系列的方法,执行逻辑正好与on相反。
例若有以下要求,当B对象上发生事件b的时候,触发A对象的callbackOnBEvent函数。

// 使用on的状况下
B.on(“b”, A.callbackOnBEvent)

// 使用listenTo的状况下
A.listenTo(B, “b”, callbackOnEvent);

从实现上看,它门的区别就在于谁负责管理这个事件。第一个模型中,B就像是整个系统的master,负责事件到达的时候的分发,让不一样的对象(如A)执行对应的方法。第二个模型中,B更像是一个信息栈,A监听B上发生的事件,而且在对应事件到达的时候触发自身相应的回调函数。二者并没有好坏之分,可是从系统架构上来讲由于自己回调函数的上下文环境就是A,因此listenTo的方式可能会来的更加天然,并且由A本身来控制何时移除回调的执行,也可让代码的解耦程度更高。

超越Backbone

使用Event方法来处理异步请求让代码的可读性大大增长。若是你的单页面应用刚好使用了Backbone做为前端框架,将Event经过Backbone.Events这个变量暴露出来,你可使用相似Model扩展的方法

//Your object
_.extend(your_object.prototype, Backbone.Events, {
    //other prototype methods
    //...
}

这样你的Object也就具备了彼此绑定事件、触发事件的能力。

即使你的前端并无使用Backbone,因为Events并不依赖Backbone的其余部分实现,你彻底能够将它放到本身的代码lib中,做为一个基础方法来使用。

相似的方式你也能够常常在Node的后端看到

var util = require("util");
var events = require("events");

function MyStream() {
    events.EventEmitter.call(this);
}

util.inherits(MyStream, events.EventEmitter);

总之,我我的是很是推荐多多使用Event来替代层级的Callback结构。


  1. 根据2015年4月 稳定版本Backbone.js 1.1.2的注释版本。Master上的代码和注释版本稍有出入,哪位大神知道为何吗?? 

相关文章
相关标签/搜索