既然是源码学习,那就先把源码看懂再说,看懂源码才能进一步的分析。因此,第一步咱们就是逐行读源码,可是在读以前,先交代点事情在前面。javascript
首先,介绍一下我读源码的方法。html
读源码在读的过程当中会有一个问题,就是越读越懵逼。由于源码都写的很精简,会有超多复用的地方。从而,代码会被拆的很碎,读着读着就乱了,就不知道这些代码在作什么了。因此,我采用代入法去读,也能够说是举例子的方法。先知道如何使用backbone的事件,写一个使用的示例。而后,把示例中的变量代入到源码海量的形参和变量中去,这样在看源码的时候就没那么抽象了,也清楚每一步在作什么了。java
举例说明下代入法,好比咱们要监听model对象的一个change事件, 触发view对象的changeHandler方法,代码以下。api
model.on('change', view.changeHandler, view)
找到on方法的源码,model和change,changHandler全都代入到代码中去,代码以下数组
Events.on = function (name, callback, context) { // internalOn(model, 'change', changeHandler, undefined) return internalOn(this, name, callback, context); };
这样就是在使用代入法了,这里只是简单举个例子,可能看不出明显的效果,后续咱们读大量源码时候就会感受到它的优点了。 性能优化
其次,本人水平有限,源码解读不对的地方,还请多包涵,欢迎批评指正。 app
事情交代完了,正式开始读源码吧!函数
首先,总体了解下backbone事件部分有哪些方法的代码要看,请看下图。post
事件对外暴露的api中,用于绑定事件的有4个方法,可是因为bind是on的别名,因此实际只有3个方法用来绑定事件,它们分别是on,listenTo,once。用于解绑事件的api中,一样unbind也是别名,因此实际也是只要分析off,stopListening就能够了。触发事件只有trigger这一个api,毕竟触发操做一个api就够了。性能
举一个例子,监听model的chang方法,而后触发view的changHandler方法,代码以下。代码中还罗列了其余几种on方法的使用方式,为了方便后续讲解代码中的不少特殊处理。可是,主要仍是使用第一种用法来走通源码的大部分流程。
model.on('change',view.changeHandler, view) // 更多的用法 一、model.on("change:title change:author", ...); //当回调函数被绑定到特殊"all"事件时,任何事件的发生都会触发该回调函数,回调函数的第一个参数会传递该事件的名称。 //举个例子,将一个对象的全部事件代理到另外一对象: 二、proxy.on("all", function(eventName) { object.trigger(eventName); }); // 三、book.on({ "change:title": titleView.update, "change:author": authorPane.update, "destroy": bookView.remove });
将model和view代入源码中
Events.on = function (name, callback, context) { // internalOn(model, 'change', view.changeHandler, view) return internalOn(this, name, callback, context); };
internalOn,按照命名来解读是内部使用的on方法的意思,在其余地方还会复用。
var internalOn = function (obj, name, callback, context, listening) { // model._events = // eventsApi(onApi, model._events || {}, 'change', view.changeHandler, { // context: view, // ctx: model, // listening: undefined // }) obj._events = eventsApi(onApi, obj._events || {}, name, callback, { context: context, // undefined ctx: obj, // model listening: listening // undefined }); // undefined 不会执行 if (listening) { var listeners = obj._listeners || (obj._listeners = {}); listeners[listening.id] = listening; } return obj; };
internalOn 为model这个被监听的对象赋一个_events属性,并调用eventsApi方法对_events属性作了处理。因为on方法没有传入listening属性,if(listening)里面的代码放到后续分析,咱们先主要看一下这个eventsApi作了什么。
// model._events = // eventsApi(onApi, model._events || {}, 'change', view.changeHandler, { // context: view, // ctx: model, // listening: undefined // }) var eventsApi = function (iteratee, events, name, callback, opts) { var i = 0, names; if (name && typeof name === 'object') { // 处理上述更多用法中的第3种,传入包含事件名和对应回调函数的对象 // model.on({ // "change": view.changeHandler, // "change:author": authorPane.update, // "destroy": bookView.remove // }); //这里`void 0`表明undefined // 若是传入了callback,opts中的context属性为undefined,那么把callback赋值给opts.context if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback; // 遍历传入的事件对象 for (names = _.keys(name); i < names.length; i++) { // 将对象中的事件和事件回调分别取出 // 转换为 model.on('change',view.changeHandler, view)使用方式调用eventsApi函数的方式 // events = eventsApi(onApi, model._events, 'change', view.changeHandler, opts); // events = eventsApi(onApi, model._events, 'change:author', authorPane.update, opts); // events = eventsApi(onApi, model._events, 'destroy', bookView.remove, opts); // 而后去执行下面else if 或者 else那种状况的代码 events = eventsApi(iteratee, events, names[i], name[names[i]], opts); } } else if (name && eventSplitter.test(name)) { // 处理上述的第1种写法 // model.on("change change:author", ...); for (names = name.split(eventSplitter); i < names.length; i++) { // events = onApi(model._events, 'change', view.handler, opts) // events = onApi(model._events, 'change: author', view.handler, opts) events = iteratee(events, names[i], callback, opts); } } else { // 最简单的写法,上述最上面那种写法 // model.on('change',view.changeHandler, view) // events = onApi(model._events, 'change', view.changeHandler, opts); events = iteratee(events, name, callback, opts); } return events; };
经过看上面代码和注释,应该就能够得出一个结论,这个eventsApi方法就是用来处理各类不一样的用法传入的参数,最终统一的使用iteratee(model._events, 'change', view.changeHandler, opts);来处理model._events。
继续回到internalOn方法
obj._events = eventsApi(onApi, obj._events || {}, name, callback, { context: context, ctx: obj, listening: listening });
上面的代码就能够转化为
model._events = onApi(model._events, 'change', view.changeHandler,{ context: view, ctx: model, listening: undefined });
这里面又调用了onApi方法,咱们传入上面代码里的参数来看一下
var onApi = function (events, name, callback, options) { // view.changeHandler if (callback) { // 若是 model._events['change'] 不存在,执行 model._events['change'] = [] var handlers = events[name] || (events[name] = []); // context = view ctx = model listening = undefined var context = options.context, ctx = options.ctx, listening = options.listening; // listening为空,这里暂时没用到,后续其余方法涉及再讲解 if (listening) listening.count++; handlers.push({ callback: callback, context: context, ctx: context || ctx, listening: listening }); } return events; // model._events = { // change: [ // { callback: view.changeHandler, context: view, ctx: model, listening: undefined } // ] // } };
经过执行onApi最终获得了新的model._events对象,也就是on方法里面的代码为了绑定监听事件而设计的数据模型。
model._events = { change: [ { callback: view.changeHandler, context: view, ctx: model, listening: undefined } ] }
整个代码其实都是在围绕打造这个数据模型。暂且先把这个数据模型放在这里,咱们接着来看下一个绑定事件的方法listenTo。
这里有篇大神的文章对listenTo有一个很清晰的讲解,里面也涵盖了不少backbone事件部分心法方面的内容,文章写得很好,我就再也不赘述了,你们最好看一下。心法配着源代码看,才会真正有收货。
仍是先举一个用法例子,view对象监听model的change事件,触发view的changeHandler方法
view.listenTo(model,'change',view.changeHandler)
来看listenTo方法的源码
//执行view.listenTo(model,'change',changeHandler) Events.listenTo = function (obj, name, callback) { // obj=model if (!obj) return this; // model._listenId 不存在,执行 model._listenId = _.uniqueId('l') == 'l1' // model给本身添加一个属性_listenId,等于给本身打一个惟一标记 var id = obj._listenId || (obj._listenId = _.uniqueId('l')); // view._listeningTo不存在,执行 view._listeningTo = {} var listeningTo = this._listeningTo || (this._listeningTo = {}); // listening = view._listeningTo[model._listenId] // 这里设计的挺巧妙,语义化也很好,理解起来就是view对象正在监听着一个惟一id var listening = listeningTo[id]; // 若是 view._listeningTo[model._listenId] 正在监听的这个惟一id(key)对应的对象(value)不存在 if (!listening) { // view先给本身也打一个惟一标记 view._listenId = _.uniqueId('l') == 'l2' var thisId = this._listenId || (this._listenId = _.uniqueId('l')); // 把view本身和被监听对象的信息全都存起来,存到正在监听某id这个属性中去 listening = listeningTo[id] = { obj: obj, objId: id, id: thisId, listeningTo: listeningTo, count: 0 }; // 获得一个数据模型,这个数据模型在view的属性上面,_listeningTo // view._listeningTo: { // l2: { // obj: model, // objId: model._listenId, // id: view._listenId, // listeningTo: view._listeningTo, // count: 0 // } // } } // internalOn(obj, name, callback, this, listening); return this; };
这个方法在view和model上都添加了_listenId属性,用来添加惟一标识,惟一标识是经过underscore的uniqueId产生的,backbone是依赖了underscore的。不只添加了惟一标识,还在监听者身上添加了一个_listeningTo属性,保存了一个重要的数据模型。这个数据模型为何要这么设计,后续咱们就会知道。
view._listeningTo: { l2: { obj: model, objId: model._listenId, id: view._listenId, listeningTo: view._listeningTo, count: 0 } }
获得这个数据模型以后,执行了internalOn方法,这个方法在on方法中咱们分析过,不一样的是此次传入了listening,咱们看看会发生什么。
// Guard the `listening` argument from the public API. var internalOn = function (obj, name, callback, context, listening) { // model._events obj._events = eventsApi(onApi, obj._events || {}, name, callback, { context: context, // view ctx: obj, // model listening: listening //view._listeningTo[model._listenId] }); // listening存在 执行 if (listening) { // model._listeners不存在,执行model._listeners = {} var listeners = obj._listeners || (obj._listeners = {}); // model._listeners[view._listenId] = view._listeningTo[model._listenId] // model._listeners['l2'] = view._listeningTo['l1'] listeners[listening.id] = listening; } return obj; };
复用interOn方法,其实就是执行了和on方法相同的处理逻辑来绑定监听,为model这个被监听者绑定事件属性_events。
var onApi = function (events, name, callback, options) { if (callback) { // events = model._events var handlers = events[name] || (events[name] = []); var context = options.context, ctx = options.ctx, listening = options.listening; // view._listeningTo[model._listenId] // count ++ if (listening) listening.count++; // push的listening第一个数据中的count为1 handlers.push({ callback: callback, context: context, ctx: context || ctx, listening: listening }); } return events; };
因此,model._events会拿到和on方法大体相同的数据模型。先回顾一下on方法那里拿到的_events,以下。
model._events = { change: [ { callback: view.changeHandler, context: view, ctx: model, listening: undefined } ] }
可是因为listenTo方法这里的listening参数再也不为空,因此会获得下面的这种_events
model._listenId = 'l1' view._listenId = 'l2' model._events = { 'change': [ { callback: view.changeHandler, context: view, ctx: view || model, listening: view._listeningTo['l1'] } ] }
listening这个属性我理解为 “如今的监听状况:view正在监听向model(l1)”。不只是eventsApi里面,外面的if(listening)也由于listening的赋值能够执行了。执行的结果是为model也添加了一个属性_listeners(监听者),获得一个数据模型,以下。
model._listenId = 'l1' view._listenId = 'l2' model._listeners = { 'l2': view._listeningTo['l1'] }
这个监听者,语义化也是很好,很是好理解,“监听者view(l2):view正在监听着model(li)”。
那么这个listenTo方法执行下来,获得的全套数据模型以下。
{ view.listenTo(model, 'change', view.changeHandler) model._listenId = 'l1' view._listenId = 'l2' model._events = { 'change': [ { callback: view.changeHandler, context: view, ctx: view || model, listening: view._listeningTo['l1'] } ] } model._listeners = { 'l2': view._listeningTo['l1'] } view._listeningTo = { 'l1': { obj: model, objId: 'l1', id: 'l2', listeningTo: view._listeningTo, count: 1 } }
若是多写几个监听,就会在这个数据模型基础上扩展,好比
//添加model2 view.listenTo(model2, 'change', changeHandler) model2._listenId = 'l3' model2._listeners = { 'l2': view._listeningTo['l3'] } view._listeningTo = { 'l1': { obj: model, objId: 'l1', id: 'l2', listeningTo: view._listeningTo, count: 1 }, 'l3': { obj: model2, objId: 'l3', id: 'l2', listeningTo: view._listeningTo, count: 1 } } //添加view2 view2.listenTo(model, 'change', changeHandler) // 同理 model._listeners的key也会多一个
这个数据模型里面的count要多说几句。看了on和listenTo代码能够发现,同一个事件名能够屡次添加相同的callback。就好比view.listenTo(model, a),这个代码能够执行屡次,而后每一次都在model._events里面添加进去了。相应的,在model._listeners和view._listeningTo里面因为是key惟一的对象,多添加几回也只是会覆盖,体现不出屡次添加。因此,就在key里面的对象的count那里加1,以表示屡次。
上面的全部代码的组织都应该是先设计好这些基础的数据模型,而后围绕这些模型编写的。编写代码过程当中天然会发现数据模型的不足,进而再对数据模型进行补充,最终呈现出如今的代码形态。
上面咱们介绍了2种事件绑定方式on和listenTo,还差一种once没有解读。因为once比较特殊,本身绑定事件后,执行一次即本身解绑事件,因此放在后面解读。先看一下如何触发事件,更有助于理解数据模型的设计。
先看看怎么使用trigger
// 随便传了个1 2 model.trigger('change', 1, 2)
把绑定的数据模型拿来对照一下,方便理解。
// on model._events = { change: [ { callback: view.changeHandler, context: view, ctx: model, listening: undefined } ] } // listenTo model._events = { 'change': [ { callback: view.changeHandler, context: view, ctx: view || model, listening: view._listeningTo['l1'] } ] }
上trigger源码
Events.trigger = function (name) { if (!this._events) return this; var length = Math.max(0, arguments.length - 1); var args = Array(length); // 将name以外的传参所有存到args数组中 for (var i = 0; i < length; i++) args[i] = arguments[i + 1]; // name能够为事件对象形式、带空格字符串形式以及最简单的事件名字符串形式 // 事件对象这种形式是能够处理的,可是没人会这么传吧,毕竟是要去触发事件,搞这么复杂干啥 // 使用 eventsApi 方法扁平化,统一处理方式 // 传入triggerApi, model._events 'change' undefined [1, 2] eventsApi(triggerApi, this._events, name, void 0, args); return this; };
触发函数的入参和绑定事件的函数是一致的,由于都调用了eventsApi方法去统一处理方式。
eventsApi这个方法会把参数传给triggerApi方法去执行,主要的触发逻辑也是在triggerApi中,咱们就来看一下是怎么处理的。
//对trigger进行进一步处理,好比区分是否监听了all事件 var triggerApi = function (objEvents, name, callback, args) { // model._events if (objEvents) { // model._events.change var events = objEvents[name]; //处理对all事件进行监听的状况 var allEvents = objEvents.all; if (events && allEvents) allEvents = allEvents.slice(); // 若是绑定了这个事件,触发这个事件 if (events) triggerEvents(events, args); // 若是绑定了all事件,触发all事件 // 假设A对象监听了B对象的all事件,那么全部的B对象的事件都会被触发,而且会把传入的事件名做为第一个函数参数 if (allEvents) triggerEvents(allEvents, [name].concat(args)); } return objEvents; };
这里又调用了triggerEvents方法,在里面调用了callback方法,完成了触发。
/* 对事件进行触发,优先进行call调用,call调用比apply调用效率更高,因此优先进行call调用 这里的events参数,其实是回调函数列 */ var triggerEvents = function (events, args) { // a1 a2 a3是由于认为call最多调用3个参数,超过3个就使用apply,一种性能优化的考虑,优先进行call调用 var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; switch (args.length) { // 这里终于用到了ctx 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; //由于call调用的时候是须要将参数展开的,而apply调用的时候传入一个数组便可 default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return; } };
先看一下使用方式,有点多,有点复杂,状况毕竟覆盖的多。
Removes just the `onChange` callback. object.off("change", onChange); Removes all "change" callbacks. object.off("change"); Removes the `onChange` callback for all events. object.off(null, onChange); Removes all callbacks for `context` for all events. object.off(null, null, context); Removes all callbacks on `object`. object.off();
off方法源码以下
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; };
根据上面几种用法,context能够传也能够不传。eventsApi这个方法真是超高复用了,这里就不赘述了,直接看offApi干了什么吧。
先上offApi要对付的数据模型,有的放矢。
//on model._events = { change: [ { callback: view.changeHandler, context: view, ctx: model, listening: undefined } ] } // listenTo model._events = { 'change': [ { callback: changeHandler, context: view, ctx: view || model, listening: view._listeningTo['l1'] } ] } model._listeners = { 'l2': view._listeningTo['l1'] } view._listeningTo = { 'l1': { obj: model, objId: 'l1', id: 'l2', listeningTo: view._listeningTo, count: 1 } }
再来看offApi
var offApi = function (events, name, callback, options) { if (!events) return; var i = 0, listening; // context listeners = model._listeners var context = options.context, listeners = options.listeners; // model.off() if (!name && !callback && !context) { // 有listeners的状况,就是用listenTo绑定事件,处理model._listeners var ids = _.keys(listeners);//全部监听它的对应的属性 for (; i < ids.length; i++) { listening = listeners[ids[i]]; // 删除 model._listeners[id] delete listeners[listening.id]; // 删除 view._listeningTo[id] delete listening.listeningTo[listening.objId]; } //这个offApi最终是要返回events,return 等因而 model._events 置空了 return; } // 若是传入了name,好比change,返回['change'] // 没有name 获取model._events的键值,拿到全部的事件名 var names = name ? [name] : _.keys(events); for (; i < names.length; i++) { name = names[i]; // 遍历事件名,拿到事件对应的对象 var handlers = events[name]; //若是没有回调函数,直接break if (!handlers) break; // Replace events if there are any remaining. Otherwise, clean up. 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 ) { // callback传了,可是和绑定的对不上 或者 context传了,可是和绑定的对不上 // 就保留下来 remaining.push(handler); } else { // callback或者context传入了,而且callback或者context对的上 listening = handler.listening; // 那么就处理view._listeningTo[id] // 以前说了count值就表明屡次绑定,count-1就表明去掉一次,若是--count===0了就说明是最后一个了 // 就直接删掉属性值了 if (listening && --listening.count === 0) { delete listeners[listening.id]; delete listening.listeningTo[listening.objId]; } } } // Update tail event if the list has any events. Otherwise, clean up. if (remaining.length) { events[name] = remaining; } else { delete events[name]; } } return events; };
off删除仍是挺简单的,只要掌握了绑定事件构建的数据模型,针对不一样的用法理解不一样的判断处理,就能够很简单的看懂了。
stopListening也是用来解除绑定的,为何有了off还要有stopListening呢?看一下上面的off源码就能够发现,off的用法是做用在被监听的对象上一个一个的去解除监听。举个例子来讲,来个一目了然。
var view = { changeName :function(name){ //doing something } } model.on('change:name',view.changeName,view); model2.on('change:name',view.changeName,view); //view离开时,model如何解绑 model.off('change:name',view.changeName,view); model2.off('change:name',view.changeName,view);
有多个model的话,须要进行屡次的解绑操做。再来看看stopListening的解绑。
view.listenTo(model,'change:name',view.changeName); view.listenTo(model2,'change:name',view.changeName); //解绑 model.off('change:name',view.changeName) model2.off('change:name',view.changeName) //解绑 view.stopListening();
并不须要作更多的操做就能把view相关的监听事件所有给解绑。
了解了用法,咱们来看下源码是怎么实现的。
// view.listenTo(model, 'change:name', view.changeName); // view.listenTo(model2, 'change:name', view.changeName); // 至关于 model.on('change:name', view.changeName, view) // view.stopListening(); // view.stopListening(model, 'change:name', view.changeName) Events.stopListening = function (obj, name, callback) { // view._listeningTo var listeningTo = this._listeningTo; if (!listeningTo) return this; //若是没有指定obj,就解绑全部的对别的对象的事件监听,若是指定了obj,就解绑对应obj的 var ids = obj ? [obj._listenId] : _.keys(listeningTo); for (var i = 0; i < ids.length; i++) { var listening = listeningTo[ids[i]]; // 这里进行检查,若是压根就没有监听,实际上说明用这个函数是画蛇添足的,这里直接break就好(而不是continue) if (!listening) break; //这里直接用了off方法,并传递正确的this上下文(为监听者) // off用于解绑被监听者 // model.on('change:name', view.changeName, view) === view.listenTo(model2, 'change:name', view.changeName); // model.off listening.obj.off(name, callback, this); } return this; };
看源码可知,stopListening只是针对使用listenTo的监听者使用的,为的就是便捷的所有解绑监听者全部的监听,而没必要去屡次调用off。可是其实stopListening的底层仍然是使用的off方法去解绑被监听者的,这里也是体现了复用的思想。从源码中也能够看出为何数据模型中view.listeningTo中的对象要包含obj这个属性,在解绑这里被用到了。数据模型中的不少感受多余的不可理解的属性,都是在扩展功能的过程当中不断添加进去的,我是这么理解的。
once用法跟on很像,区别在于绑定的回调函数触发一次后就会被移除(注:只执行一次)。简单的说就是“下次再也不触发了,用这个方法”。
model.once('change', view.changeHandler, view)
Events.once = function (name, callback, context) { var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this)); if (typeof name === 'string' && context == null) callback = void 0; // this.on({'change': once_callback}, callback, context) return this.on(events, callback, context); };
看到这个方法很容易能够看到不一样之处。在eventsApi这个方法调用的时候,传入的events是一个空对象,而不是model._events。最后一个参数传入的也不是opts,而是绑定好上下文的off方法,上下文绑定的是model。
那么,在执行onceMap,返回的events是什么呢。
var onceMap = function (map, name, callback, offer) { if (callback) { // 自成一体,执行后自行解绑,不须要off等方法解绑 // once变量 和 map[name]均指向_.once(function(){}) var once = map[name] = _.once(function () { offer(name, once); callback.apply(this, arguments); }); //这个在解绑的时候有一个分辨效果 once._callback = callback; } return map; };
onceMap中的_.once方法返回的是一个只能执行一次的函数,这里直接使用了underscore。这个返回的函数指向事件对象的change属性,而且函数上添加了一个_callback属性,真正的传入的callback函数指向了_callback属性。_.once中的回调函数,解绑了once函数,而且执行了callback回调函数。返回的events以下
Events.once = function (name, callback, context) { var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this)); // events = { // change: once_callback // } // once_callback = _.once(function () { // 伪代码,只是表达个意思,触发事件以后解绑函数 // off(name, once); // callback.apply(this, arguments); // }); // once_callback._callback = callback if (typeof name === 'string' && context == null) callback = void 0; // this.on({'change': once_callback}, callback, context) return this.on(events, callback, context); };
事件变量传入到on方法,完成绑定。off解绑那里的_callback,呼应了off源码,经过检验_callback来判断如何作下一步操做。
if ( callback && callback !== handler.callback && callback !== handler.callback._callback || context && context !== handler.context ) {
on完成绑定以后,触发事件,执行相应的callback,off解除绑定,就是这样一波操做。
参考:《Backbone系列篇之Backbone.Events源码解析》 http://www.javashuo.com/article/p-daudqxbt-gq.html
《Backbone.js(1.1.2) API中文文档》 https://www.html.cn/doc/backbone/#Events-on