标签:
Backbone源码浅读:
前言:
Backbone是早起的js前端MV*框架之一,是一个依赖于underscore和jquery的轻量级框架,虽然underscore中基于字符串拼接的模板引擎相比如今基于dom元素双向绑定的模板引擎已显得落伍,但backbone作为引领前端mv*开发模式的先驱之一,依然是麻雀虽小却设计精妙,了解其设计与结构对于想一探mv*框架的初学者来说仍会获益匪浅。
Backbone结构:
Backbone分为几个部分:其中最核心的是Event事件模块,提供了实现事件与观察者模式的基础;随后是Model与Collection,提供了数据(Model)层面的抽象;接着是View,提供了数据与表现的相互链接,其模板引擎依赖于underscore的template方法;随后的async模块一直是我对Backbone又爱又恨的地方,一方面在代码层面的实现绑架了前后端的通信方式(虽然可以override),但另一方面这里数据与通信的模式又具备了Flux的雏形。最后是Router与History。当然除此之外也有extend,noConflict这样的技术辅助函数。总体而言,Backbone的代码小巧,结构清晰,易读易懂,对于初学者切入mv*框架非常适合。
Event模块:
Event模块将暴露以下api:on,off,once,trigger,这4个是我们所熟悉的事件模块/观察者模式的基本api;还有就是listenTo,stopListening,listenToOnce,这是前3个api的反向控制(Ioc)版本。对于两者我们可以这样理解:前者是站着被观察者的角度,需要暴露的api;而后者是站在观察者的角度所需要的api。这样设计的好处是,观察者在调用api进行观察(调用listenTo或listenToOnce)时,在自身保留与观察事物的索引,于是在观察者被销毁时,可以方便的注销自身在被观察者上已注册的回调(通过调用stopListening),从而避免泄露。
在event模块中首先定义的的是eventsApi函数,这一函数的功能是调用传入的iteratee,并传入events,name,callback和opts。
Iteratee是一个执行实际功能的函数,events是一个用来挂载所有事件回调的object,name是事件名称,callback是回调函数,opts则是额外参数。
我们可以看到eventsApi的作用其实只是封装对name的多态性处理,name可以是含有以多个事件名为键的object,可以是空格分隔事件名的的字符串,或是单一事件名的字符串。
var eventsApi = function(iteratee, events, name, callback, opts) {
var i = 0, names;
if (name && typeof name === ‘object‘) {
if (callback !== void 0 && ‘context‘ in opts && opts.context === void 0) opts.context = callback;
for (names = _.keys(name); i < names.length ; i++) {
events = eventsApi(iteratee, events, names[i], name[names[i]], opts);
}
} else if (name && eventSplitter.test(name)) {
for (names = name.split(eventSplitter); i < names.length; i++) {
events = iteratee(events, names[i], callback, opts);
}
} else {
events = iteratee(events, name, callback, opts);
}
return events;
};
既然真正的核心是这些传入eventsApi的iteratee,那就让我们来看看这些iteratee以及如何使用它们形成最后的Api:
首先是onApi,这个函数的作用是通过传入的name,callback,在events对象上创建一个键为name(事件名)的数组,并将包含callback和context(回调函数触发时的上下文)以及listening对象的对象推入数组。这里的context和listening是options中取得的,listening(观察)对象中包含了反向控制时所需的信息,将在之后介绍。
var onApi = function(events, name, callback, options) {
if (callback) {
var handlers = events[name] || (events[name] = []);
var context = options.context, ctx = options.ctx, listening = options.listening;
if (listening) listening.count++;
handlers.push({ callback: callback, context: context, ctx: context || ctx, listening: listening });
}
return events;
};
onApi的进一步封装是internalOn。internalOn调用eventsApi并将onApi作为传入的iteratee。同时如果传入了listening对象,则将在被观察者上记录观察者和观察的信息。
var internalOn = function(obj, name, callback, context, listening) {
obj._events = eventsApi(onApi, obj._events || {}, name, callback, {
context: context,
ctx: obj,
listening: listening
});
if (listening) {
var listeners = obj._listeners || (obj._listeners = {});
listeners[listening.id] = listening;
}
return obj;
};
有了这些我们就能来实现on和listenTo了:
on只需简单的调用internalOn:
Events.on = function(name, callback, context) {
return internalOn(this, name, callback, context);
};
而listenTo需要额外处理的便是,建立这个listening对象。Listening对象包含被观察者obj,用于在观察者上记录观察的listeningTo对象,以及计数器count。同一对观察者与被观察者之间的listen对象会被重用,在onApi调用时这个count便会+1来起到计数的作用。同时我们看到观察者和被观察者都会通过_.uniqueId函数产生的唯一id来标识自身。
Events.listenTo = function(obj, name, callback) {
if (!obj) return this;
var id = obj._listenId || (obj._listenId = _.uniqueId(‘l‘));
var listeningTo = this._listeningTo || (this._listeningTo = {});
var listening = listeningTo[id];
if (!listening) {
var thisId = this._listenId || (this._listenId = _.uniqueId(‘l‘));
listening = listeningTo[id] = {obj: obj, objId: id, id: thisId, listeningTo: listeningTo, count: 0};
}
internalOn(obj, name, callback, this, listening);
return this;
};
我们已经知道了在被观察者上的事件回调是{eventKey1: [...], eventKey2: [...], ...}的格式,因此回调的移除便是通过便利这个_events对象来实现的,需要注意的是,移除侦听时name和callback都是可以缺省的,没有callback则移除_events[name]中包含的所有回调,如果连name都没有则移除所有侦听,这两者在例如观察者会被观察者整个销毁时非常实用。
var offApi = function(events, name, callback, options) {
if (!events) return;
var i = 0, listening;
var context = options.context, listeners = options.listeners;
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;
}
var names = name ? [name] : _.keys(events);
for (; i < names.length; i++) {
name = names[i];
var handlers = events[name];
if (!handlers) break;
var remaining = [];
for (var j = 0; j < handlers.length; j++) {
var handler = handlers[j];
if (
callback && callback !== handler.callback &&
callback !== handler.callback._callback ||
context && context !== handler.context
) {
remaining.push(handler);
} else {
listening = handler.listening;
if (listening && --listening.count === 0) {
delete listeners[listening.id];
delete listening.listeningTo[listening.objId];
}
}
}
if (remaining.length) {
events[name] = remaining;
} else {
delete events[name];
}
}
if (_.size(events)) return events;
};
这里的remaining数组避免了反复调用slice。同时我们注意到移除侦听时既要比对handler.callback也要比对handler.callback._callback,后者是因为once绑定侦听时使用了包裹过的函数,其_callback指向原函数。
off和stopListening的实现也水到渠成:
Events.off = function(name, callback, context) {
if (!this._events) return this; // 还没有被侦听,直接返回
this._events = eventsApi(offApi, this._events, name, callback, {
context: context,
listeners: this._listeners
});
return this;
};
Events.stopListening = function(obj, name, callback) {
var listeningTo = this._listeningTo;
if (!listeningTo) return this; // 还没有侦听,直接返回
var ids = obj ? [obj._listenId] : _.keys(listeningTo);
for (var i = 0; i < ids.length; i++) {
var listening = listeningTo[ids[i]];
if (!listening) break; //还没有对被观察者的侦听,直接返回
listening.obj.off(name, callback, this); // 调用被观察者的Events.off
}
if (_.isEmpty(listeningTo)) this._listeningTo = void 0;
return this;
};
接下来的iteratee是onceMap,它会把原本的callback包装为once,并在once上记录下原callback,以便方便移除侦听(外部只知道侦听了callback,无需了解这个once的存在)。 _.once所包裹生成的函数将保证原函数只会被调用一次,once调用时用offer(name, once)移除这个单次侦听。
var onceMap = function(map, name, callback, offer) {
if (callback) {
var once = map[name] = _.once(function() {
offer(name, once);
callback.apply(this, arguments);
});
once._callback = callback;
}
return map;
};
once和listenToOnce 便很直接了:
Events.once = function(name, callback, context) {
var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this));
return this.on(events, void 0, context);
};
Events.listenToOnce = function(obj, name, callback) {
var events = eventsApi(onceMap, {}, name, callback, _.bind(this.stopListening, this, obj));
return this.listenTo(obj, events);
};
最后的trigger也无需多言,值得注意的是triggerEvents 中判断了args的长度再调用call,是因为Function#apply的效率较低,在args长度可以预判的情况下尽量使用call,这是一个常见的小技巧。
var triggerApi = function(objEvents, name, cb, args) {
if (objEvents) {
var events = objEvents[name];
var allEvents = objEvents.all;
if (events && allEvents) allEvents = allEvents.slice();
if (events) triggerEvents(events, args);
if (allEvents) triggerEvents(allEvents, [name].concat(args));
}
return objEvents;
};
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;
}
};
Events.trigger = function(name) {
if (!this._events) return this;
var length = Math.max(0, arguments.length - 1);
var args = Array(length);
for (var i = 0; i < length; i++) args[i] = arguments[i + 1];
eventsApi(triggerApi, this._events, name, void 0, args);
return this;
};
最后就是在开头加上:
var Events = Backbone.Events = {}; //将Events挂到Backbone上
var eventSplitter = /\s+/; // 事件名可以用空格分隔
结尾加上:
// bind和unbind是on和off的别名
Events.bind = Events.on;
Events.unbind = Events.off;
// Backbone本身也挂载了Events的api。
_.extend(Backbone, Events);
这样Backbone的Events模块便完成了。
标签:
原文地址:http://www.cnblogs.com/benben77/p/4948022.html