卤煮在大概一年前写过backbone的源码分析,里面讲的是对一些backbone框架的方法的讲解。这几天重新看了几遍backbone的源码,才发现之前对于它的理解不够深入,只关注了它的一些部分的细节和实现技巧。忽略了它的设计思想,而卤煮认为,一套库或者框架最值得借鉴的地方正好是它的设计思想。也巧,最近卤煮在读《设计模式与实践》这本书,所以温故知新,学以致用,打算写一篇博客算作这个系列的补充,以免将来忘记了代码时可以作为参考。
观察者模式
即使不用读源码,也知道backbone使用了观察者模式。因为它本身就是一套mvc框架,而mvc框架核心的就是m(模型)->v(视图)->c(控制器)的交互过程,观察者模式是驱动它们的核心模式之一。我们首先来回顾一下观察者模式的具体定义是如何的:
//定义对象之间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都将得到通知
观察者模式也可以称之为发布-订阅者模式,它需要一些订阅对象,通常它们会自己处理不同的逻辑,简单来说它们就是一些方法。这些方法需要被缓存起来,也就是说需要一个缓存对象缓存他们,等待需要的时候调用。然后我们还需要一个发布者,它负责发布通知,告诉程序,应该去读我们之前缓存起来的订阅对象了。概括起来,我们可以用几个关键字来概括这个模式: 一对多的关系-订阅-缓存订阅对象(通常是一个数组对象)-发布,以下是一个简单观察者模式:
//定义对象之间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都将得到通知
var Observer = (function() {
//缓存对象
var cache = []
, id = 0;
//添加订阅对象
function _listener(n, fn) {
var list = {
name: n,
bookid: ++id,
callback: fn
}
cache.push(list);
}
//发布消息
function _trigger() {
var name = Array.prototype.shift.call(arguments);
for (var i = 0; i < cache.length; i++) {
if (cache[i].name === name) {
cache[i].callback.apply(this, arguments);
}
}
}
//撤销监听
function _remove(fn) {
for (var i = 0; i < cache.length; i++) {
if (cache[i].callback === fn) {
cache.splice(1, 1);
break;
}
}
} return {
on: _listener,
trigger: _trigger,
remove: _remove
}
})(); function sayHello(h) {
console.log('hello :' + h);
}
function sayBonjour(h) {
console.log('bonjour :' + h);
}
function sayAligaduo(h) {
console.log('aligaduo: ' + h);
}
//订阅行为
Observer.on('say', sayHello);
Observer.on('say', sayBonjour);
Observer.on('say', sayAligaduo);
//发布事件
Observer.trigger('say', 'xiaoyi');
//取消订阅
Observer.remove(sayAligaduo);
Observer.trigger('say', 'xiaoyi222');
现在,我们来看一看backbone中的观察者模式是怎么样的。在backbone中,事件模块(Events)是核心模块之一,它提供了很多事件交互的方法,其中最常用也是最重要的是on,和trigger方法。其实,这个两个方法相当于设计模式中的订阅(on),发布(trigger),它们一个负责把方法存入对象,一个负责从对象中取出并执行函数。下面我们来看看具体的源码设计:
on和trigger分别负责订阅和发布功能,缓存对象这里指的是this._events,由于Events对象会extend到不同的模块中,所以this的指向是会更改的。因此,每一个this.events都代表着不同模块中的缓存对象。我们可以看到,trigger并不是真正的发布者,里面的triggerEvents才是真正的发布者,这里也使用了外观者模式,将正在的发布者(triggerEvents)隐藏了起来,接口的外观是trigger。backbone的Events是一个典型的观察者,它的实现几乎和示范代码一样,我们很容易一眼就将它认出来。
那么该模式在库中的具体应用场景呢?Events模块应用到的地方非常之多,我们这里举一个最常用的例子:使用过bakcbone框架的人都清楚,backbone和angluar的区别之一是,backbone需要自行实现数据和视图绑定,也就是说在VIEW初始化的时候,我们需要绑定对应model的关系,下面是一个VIEW初始化的时候需要处理的逻辑。代码表现如下:
此处,model作为订阅者订阅了一个名为change的方法,它指向的是view的render函数,它的意思可以翻译成model对view的一段对话:“喂,view老兄,把你的电话号码(change)给我吧,如果有需要的时候我会打(trigger)你的电话(change)告诉你接下来怎么做(render)”。接下来view老兄只需要需要静静的等待model打(trigger)它的电话(change)就行了,下面是model打电话的行为,源码如下:
以上trigger的调用是在set函数里面,而set函数在api中说明了,是在设置属性时用到的。因此,我们可以说,在为model,set一段属性时,它会触发绑定在该model上的change方法,change方法是在某个视图初始化的时候订阅到的,所以,一旦change事件发布,对应的订阅对象就会去响应,从而实现界面的刷新。当然,在这之前需要对属性做diff操作,以确保它们变化了。