backbone源码解读(一篇全)

写在前面

backbone是我两年多前入门前端的时候接触到的第一个框架,当初被backbone的强大功能所吸引(当然的确比裸写js要好得多),虽然现在backbone并不算最主流的前端框架了,但是,它里面大量设计模式的灵活运用,以及令人赞叹的处理技巧,还是非常值得学习。个人认为,读懂老牌框架的源代码比会用流行框架的API要有用的多。

另外,backbone的源代码最近也改了许多(特别是针对ES6),所以有些老旧的分析,可能会和现在的源代码有些出入。

所以我写这一篇分析backbone的文章,供自己和大家一起学习,本文适合使用过backbone的朋友,笔者水平有限,而内容又实有点多,难免会出差错,欢迎大家在GitHub上指正

接下来,我们将通过一篇文章解析backbone,我们是按照源码的顺序来讲解的,这有利于大家边看源代码边解读,另外,我给源代码加了全部的中文注释和批注,请见这里,强烈建议大家边看源码边看解析,并且遇到我给出外链的地方,最好把外链的内容也看看(如果能够给大家帮助,欢迎给star鼓励~)

当然,这篇文章很长[为了避免文章有上没下,我还是整合到一篇文章中了]。

backbone宏观解读

backbone是很早期将MVC的思想带入前端的框架,现在MVC以及后来的MVVM这么火可以在一定程度上归功于backbone。关于前端MVC,我在自己的这篇文章中结合阮一峰老师的图示简单分析过,简单来讲就是Model层控制数据,View层通过发布订阅(在backbone中)来处理和用户的交互,Controller是控制器,在这里主要是指backbone的路由功能。这样的设计非常直接清晰,有利于前端工程化。

backbone中主要实现了Model、Collection、View、Router、History几大功能,前四种我们用的比较多,另外backbone基于发布-订阅模式自己实现了一套对象的事件系统Events,简单来说Events可以让对象拥有事件能力,其定义了比较丰富的API,并且如果你引入了backbone,这套事件系统还可以集成到自己的对象上,这是一个非常好的设计。

另外,源代码中所有的以_开头的方法,可以认为是私有方法,是没有必要直接使用的,也不建议用户覆盖。

backbone模块化处理、防止冲突和underscore混入

代码首先进行了区分使用环境(self或者是global,前者代表浏览器环境(self和window等价),后者代表node环境)和模块化处理操作,之后处理了在AMD和CommonJS加载规范下的引入方式,并且明确声明了对jQuery(或者Zepto)和underscore的依赖。

很遗憾的是,虽然backbone这样做了,但是backbone并不适合在node端直接使用,也不适合服务端渲染,另外还和ES6相处的不是很融洽,这个我们后面还会陆续提到原因。

backbone noConflict

backbone也向jQuery致敬,学习了它的处理冲突的方式:

var previousBackbone = root.Backbone;
//...
Backbone.noConflict = function() {
    root.Backbone = previousBackbone;
    return this;
};

这段代码的逻辑非常简单,我们可以通过以下方式使用:

var localBackbone = Backbone.noConflict();   
var model = localBackbone.Model.extend(...);
混入underscore的方法

backbone通过addUnderscoreMethods将一些underscore的实用方法混入到自己定义的几个类中(注:确切地说是可供构造调用的函数,我们下文也会用类这个简单明了的说法代替)。

这里面值得一提的是关于underscore的方法(underscore的源码解读请移步这里,fork from韩子迟),underscore的所有方法的参数序列都是固定的,也就是说第一个参数代表什么第二个参数代表什么,所有函数都是一致的,第一个参数一定代表目标对象,第二个参数一定代表作用函数(有的函数可能只有一个参数),在有三个参数的情况下,第三个参数代表上下文this,另外如果有第四个参数,第三个参数代表初始值或者默认值,第四个参数代表上下文。所以addMethod就是根据以上规定来使用的。

另外关于javascript中的this,我曾经写过博客在这里,有兴趣的可以看

混入方法的实现逻辑:

var addMethod = function(length, method, attribute) {
   
  //... 
};
var addUnderscoreMethods = function(Class, methods, attribute) {
   
    _.each(methods, function(length, method) {
   
      if (_[method]) Class.prototype[method] = addMethod(length, method, attribute);
    });
};
//之后使用:
var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0,
      omit: 0, chain: 1, isEmpty: 1};
//混入一些underscore中常用的方法
addUnderscoreMethods(Model, modelMethods, 'attributes');

backbone Events

backbone的Events是一个对象,其中的方法(on\listenTo\off\stopListening\once\listenToOnce\trigger)都是对象方法。

总体上,backbone的Events实现了监听/触发/解除对自己对象本身的事件,也可以让一个对象监听/解除监听另外一个对象的事件。

绑定对象自身的监听事件on

关于对象自身事件的绑定,这个比较简单,除了最基本的绑定之外(一个事件一个回调),backbone还支持以下两种方式的绑定:

//传统方式
model.on("change", common_callback);  

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

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

这用到了它定义的一个中间函数eventsApi,这个函数比较实用,可以根据判断使用的是哪种方式(实际上这个判断也比较简单,根据传入的是对象判断属于上述第二种方式,根据正则表达式判断是上述的第三种方式,否则就是传统的方式)。然后再进行递归或者循环或者直接处理。

在对象中存储事件实际上大概是下述形式:

events:{
    change:[事件一,事件二]
    move:[事件一,事件二,事件三]
}

而其中的事件实际上是一个整理好的对象,是如下形式:

{
   callback: callback, context: context, ctx: context || ctx, listening: listening}

这样在触发的时候,一个个调用就是了。

监听其他对象的事件listenTo

backbone还支持监听其他对象的事件,比如,B对象上面发生b事件的时候,通知A调用回调函数A.listenTo(B, “b”, callback);,而这也是backbone处理非常巧妙的地方,我们来看看它是怎么做的。

实际上,这和B监听自己的事件,并且在回调函数的时候把上下文变成A,是差不多的:B.on(“b”, callback, A);(on的第三个参数代表上下文)。

但是backbone还做了另外的事情,这里我们假设是A监听B的一个事件(比如change事件好了)。

首先A有一个A._listeningTo属性,这个属性是一个对象,存放着它监听的别的对象的信息A._listeningTo[id] = {obj: obj, objId: id, id: thisId, listeningTo: listeningTo, count: 0},这个id并不是数字,是每一个对象都有的唯一字符串,是通过_.uniqueId这个underscore方法生成的,这里的obj是B,objId是B的_listenId,id是A的_listenId,count是一个计数功能,而这个A._listeningTo[id]会被直接引用赋值到上面事件对象的listening属性中。

为什么要多listenTo?Inversion of Control

通过以上我们似乎有一个疑问,好像on就能把listenTo的功能搞定了,用一个listenTo纯属多余,并且许多其他的类库也是只有一个on方法。

首先,这里会引入一个概念:控制反转,所谓控制反转,就是原来这个是B对象来控制的事件我们现在交由A对象来控制,那现在假设A分别listenTo B、C、D三个对象,那么这个时候假设A不监听了,那么我们直接对A调用一个stopListening方法,则可以同时解除对B、C、D的监听(这里我讲的可能不是十分正确,这里另外推荐一个文章)。

另外,我们需要从backbone的设计初衷来看,backbone的重点是View、Model和Collection,实际上,backbone的View可以对应一个或者多个Collection,当然我们也可以让View直接对应Model,但问题是View也并不一定对应一个Model,可能对应多个Model,那么这个时候我们通过listenTo和stopListening可以非常方便的添加、解除监听。

//on的方式绑定
var view = {
    DoSomething :function(some){
       //...
    }
}
model.on('change:some',view.DoSomething,view);
model2.on('change:some',view.DoSomething,view);

//解绑,这个时候要做的事情比较多且乱
model.off('change:some',view.DoSomething,view);
model2.off('change:some',view.DoSomething,view);

//listenTo的方式绑定
view.listenTo(model,'change:some',view.DoSomething);
view.listenTo(model2,'change:some',view.DoSomething);

//解绑
view.stopListening();

另外,在实际使用中,listengTo的写法也的确更加符合用户的习惯.

以下是摘自backbone官方文档的一些解释,仅供参考:

The advantage of using this form, instead of other.on(event, callback, object), is that listenTo allows the object to keep track of the events, and they can be removed all at once later on. The callback will always be called with object as context.

解除绑定事件off、stopListening

与on不同,off的三个参数都是可选的

  • 如果没有任何参数,off相当于把对应的_events对象整体清空
  • 如果有name参数但是没有具体指定哪个callback的时候,则把这个name(事件)对应的回调队列全部清空
  • 如果还有进一步详细的callback和context,那么这个时候移除回调函数非常严格,必须要求上下文和原来函数完全一致

off的最终实现函数是offApi,这个函数算上注释有大概50行。

var offApi = function(events, name, callback, options) {
  //... 
}

这里面需要单独提一下,前面有这样的几行:

if (!name && !callback && !context) {
      var ids = _.keys(listeners);//所有监听它的对应的属性
      for (; i < ids.length; i++) {
        listening = listeners[ids[i]];
        delete listeners[listening.id];
        delete listening.listeningTo[listening.objId];
      }
      return;
}

这几行是做了一件什么事呢?
删除了所有的多对象监听事件记录,之后删除自身的监听事件。我们假设A监听了B的一个事件,这个时候A._listenTo中就会多一个条目,存储这个监听事件的信息,而这个时候B的B._listeners也会多一个条目,存储监听事件的信息,注意这两个条目都是按照id为键的键值对来存储,但是这个键是不一样的,值都指向同一个对象,这里删除对这个对象的引用,之后就可以被垃圾回收机制回收了。如果这个时候调用B.off(),那么这个时候,以上的两个条目都

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值