资讯专栏INFORMATION COLUMN

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

AndroidTraveler / 2900人阅读

摘要:接受个参数,包括事件的名称,回调函数和回调函数执行的上下文环境。保留回调函数在数组中取出对应的以及中的函数。当然,你同样可以在绑定的回调函数执行前手动通过将其移除。

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从这个对象中根据事件的名称取得回调函数数组,然后循环执行每个回调函数,也就说明了为什么多次绑定会重复触发多次事件。

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结构。

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85600.html

相关文章

  • Backbone 源码解读(一)

    1. 开场 1.1 MVC? MVC是一种GUI软件的一种架构模式。它的目的是将软件的数据层(Model)和视图(view)分开。Model连接数据库,实现数据的交互。用户不能直接和数据打交道,而是需要通过操作视图,然后通过controller对事件作出响应,最后才得以改变数据。最后数据改变,通过观察者模式更新view。(所以在这里需要用到设计模式中的观察者模式) 1.2 Smalltalk-80...

    Kosmos 评论0 收藏0
  • Backbone源码解读(二)

    摘要:以为例构造函数的内容构造函数的内部一般会做以下几个操作各种给内部对象设置属性。为什么呢源码做出了解释。在里面会调用用户传入的回调函数并触发事件表示已经同步了。整个的源码事实上就是这两组东西。 1. 开场 强烈建议一边看着源码一边读本文章,本文不贴大段代码。源码地址。在写backbone应用的时候,说实话,大部分的时间都是在写这三个模块的内容。关于这三个模块的分析网上随随便便就能找到一堆...

    Sleepy 评论0 收藏0
  • backbone源码解读

    摘要:个人认为,读懂老牌框架的源代码比会用流行框架的要有用的多。另外,源代码中所有的以开头的方法,可以认为是私有方法,是没有必要直接使用的,也不建议用户覆盖。 写在前面 backbone是我两年多前入门前端的时候接触到的第一个框架,当初被backbone的强大功能所吸引(当然的确比裸写js要好得多),虽然现在backbone并不算最主流的前端框架了,但是,它里面大量设计模式的灵活运用,以及令...

    Kross 评论0 收藏0
  • backbone源码解读

    摘要:个人认为,读懂老牌框架的源代码比会用流行框架的要有用的多。另外,源代码中所有的以开头的方法,可以认为是私有方法,是没有必要直接使用的,也不建议用户覆盖。 写在前面 backbone是我两年多前入门前端的时候接触到的第一个框架,当初被backbone的强大功能所吸引(当然的确比裸写js要好得多),虽然现在backbone并不算最主流的前端框架了,但是,它里面大量设计模式的灵活运用,以及令...

    wangxinarhat 评论0 收藏0
  • Backbone.js 源码阅读

    摘要:最近对主要对源码进行了阅读,分别解读了,,目录如下希望大家能互相交流 最近对backbone主要对源码进行了阅读,分别解读了Backbone.Model,Backbone.View , Backbone.Collection,Backbone.Event,目录如下: Backbone.View Backbone.Collection Backbone.Model Backbone....

    zlyBear 评论0 收藏0

发表评论

0条评论

AndroidTraveler

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<