backbone源码学习——事件

    既然是源码学习,那就先把源码看懂再说,看懂源码才能进一步的分析。所以,第一步我们就是逐行读源码,但是在读之前,先交代点事情在前面。

    首先,介绍一下我读源码的方法。

    读源码在读的过程中会有一个问题,就是越读越懵逼。因为源码都写的很精简,会有超多复用的地方。从而,代码会被拆的很碎,读着读着就乱了,就不知道这些代码在做什么了。所以,我采用代入法去读,也可以说是举例子的方法。先知道如何使用backbone的事件,写一个使用的示例。然后,把示例中的变量代入到源码海量的形参和变量中去,这样在看源码的时候就没那么抽象了,也清楚每一步在做什么了。

    举例说明下代入法,比如我们要监听model对象的一个change事件, 触发view对象的changeHandler方法,代码如下。

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);
};

这样就是在使用代入法了,这里只是简单举个例子,可能看不出明显的效果,后续我们读大量源码时候就会感觉到它的优势了。 

    其次,本人水平有限,源码解读不对的地方,还请多包涵,欢迎批评指正。  

    事情交代完了,正式开始读源码吧!

    首先,整体了解下backbone事件部分有哪些方法的代码要看,请看下图。

    事件对外暴露的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)
// 更多的用法
1、model.on("change:title change:author", ...);
//当回调函数被绑定到特殊"all"事件时,任何事件的发生都会触发该回调函数,回调函数的第一个参数会传递该事件的名称。
//举个例子,将一个对象的所有事件代理到另一对象:
2、proxy.on("all", function(eventName) {
  object.trigger(eventName);
});
//
3、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源码解析》  https://juejin.im/post/584bc9942f301e00572544bd 

          《Backbone.js(1.1.2) API中文文档》 https://www.html.cn/doc/backbone/#Events-on

转载于:https://my.oschina.net/wangch5453/blog/3065259

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值