backbone源码学习——事件

    既然是源码学习,那就先把源码看懂再说,看懂源码才能进一步的分析。因此,第一步咱们就是逐行读源码,可是在读以前,先交代点事情在前面。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就够了。性能

    咱们先从on开始吧

    举一个例子,监听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(on 的控制反转)

    这里有篇大神的文章对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,以表示屡次。

    上面的全部代码的组织都应该是先设计好这些基础的数据模型,而后围绕这些模型编写的。编写代码过程当中天然会发现数据模型的不足,进而再对数据模型进行补充,最终呈现出如今的代码形态。

   触发事件trigger

    上面咱们介绍了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;
  }
};

    解绑off

    先看一下使用方式,有点多,有点复杂,状况毕竟覆盖的多。

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

    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(绑定的回调函数触发一次后就会被移除)

    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

相关文章
相关标签/搜索