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

相關文章
相關標籤/搜索