English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Analisi del meccanismo di binding e unbinding degli eventi in jQuery

引子

为什么jQuery能实现不传回调函数也能解绑事件?如下:

$("p").on("click",function(){
  alert("The paragraph was clicked.");
});
$("#box1").off("click");

事件绑定解绑机制

调用on函数的时候,将生成一份事件数据,结构如下:

{
  type: type,
  origType: origType,
  data: data,
  handler: handler,
  guid: guid,
  selector: selector,
  needsContext: needsContext,
  namespace: namespace
};

并将该数据加入到元素的缓存中。jQuery中每个元素都可以有一个缓存(只有有需要的时候才生成),其实就是该元素的一个属性。jQuery为每个元素的每种事件都建立一个队列,用来保存事件处理函数,所以可以对一个元素添加多个事件处理函数。缓存的结构如下:

"div#box":{ //元素
  "Jquery623873":{ //元素的缓存
    "events":{ 
      "click":[
       {  //元素click事件的事件数据
             type: type,
             origType: origType,
             data: data,
             handler: handler,
             guid: guid,
             selector: selector,
             needsContext: needsContext,
             namespace: namespace
               };
      ],
      "mousemove":[
       {
             type: type,
             origType: origType,
             data: data,
             handler: handler,
             guid: guid,
             selector: selector,
             needsContext: needsContext,
             namespace: namespace
               };
      ]
    };
  };
};

当要解绑事件的时候,如果没指定fn参数,jQuery就会从该元素的缓存里拿到要解绑的事件的处理函数队列,从里面拿出fn参数,然后调用removeEventListener进行解绑。

源代码

代码注释可能不太清楚,可以复制出来看

jQuery原型中的on,one,off方法:

事件绑定从这里开始

jQuery.fn.extend( {
  on: function( types, selector, data, fn ) {
    return on(this, types, selector, data, fn);
  },
  one: function(types, selector, data, fn) {
    return on(this, types, selector, data, fn, 1);
  },
  off: function(types, selector, fn) {
    // Il codice per il trattamento dei parametri viene omesso
    return this.each(function() {
      jQuery.event.remove(this, types, fn, selector);
    });
  };
});

La funzione on isolata per essere utilizzata da one e on:

function on(elem, types, selector, data, fn, one) {
  var origFn, type;
  // Il codice per il trattamento dei parametri viene omesso
  // Se l'associazione è stata effettuata utilizzando one, viene utilizzato un funzione proxy per il callback dell'evento corrente, la funzione proxy viene eseguita una sola volta
  // Qui viene utilizzato il modello di proxy
  if (one === 1) {   
    origFn = fn;
    fn = function(event) {
      // Può essere utilizzato un insieme vuoto, poiché l'evento contiene le informazioni
      jQuery().off(event);
      return origFn.apply(this, arguments);
    };
    // Usa lo stesso guid in modo che il chiamante possa rimuovere utilizzando origFn
    fn.guid = origFn.guid || (origFn.guid = jQuery.guid++);
  };
  /************************************************
  *** jQuery mette tutti gli elementi selezionati in un array e poi
  *** Per ogni elemento utilizza il metodo add dell'oggetto event per associare eventi
  *************************************************/
  return elem.each(function() {
    jQuery.event.add(this, types, fn, data, selector);
  });
};

Si può anche guardare il codice per la gestione dei parametri, che implementa la chiamata on("click",function(){}), in modo che on:function(types, selector, data, fn) non fallisca. È in realtà una judgment interna, se i parametri data, fn sono vuoti, assegna selector a fn 

L'oggetto event è un oggetto chiave per l'associazione degli eventi:

Qui si gestisce il lavoro di associare eventi agli elementi e aggiungere informazioni sugli eventi alla cache degli elementi:

jQuery.event = {
  add: function(elem, types, handler, data, selector) {
    var handleObjIn, eventHandle, tmp,
      events, t, handleObj,
      special, handlers, type, namespaces, origType,
      elemData = dataPriv.get(elem);  // Questa frase verifica se elem è stato cacheato, se non lo è, crea un cache e lo aggiunge all'elemento elem. Ha la forma: elem["jQuery310057655476080253721"] = {}
    // Non associare eventi a nodi senza dati o nodi testo/commento (ma consentire oggetti semplici)
    if (!elemData) {
      return;
    };
    // Gli utenti possono passare un oggetto di dati personalizzato per sostituire la funzione di callback dell'evento, mettendo la funzione di callback dell'evento nell'attributo handler di questo oggetto di dati
    if (handler.handler) {
      handleObjIn = handler;
      handler = handleObjIn.handler;
      selector = handleObjIn.selector;
    };
    // Ogni funzione di callback di evento genera un ID unico, che verrà utilizzato in seguito per cercare o rimuovere eventi
    if (!handler.guid) {
      handler.guid = jQuery.guid++;
    };
    // Se l'elemento viene associato per la prima volta un evento, viene inizializzata la struttura dei dati degli eventi dell'elemento e la funzione di callback principale (main)
    //Spiegazione: ogni elemento ha una funzione di callback principale, che serve come punto di ingresso per associare più eventi a quell'elemento
    if (!(events = elemData.events)) {
      events = elemData.events = {};
    };
    // Ecco il codice per l'inizializzazione della funzione di callback principale
    if (!(eventHandle = elemData.handle)) {
      eventHandle = elemData.handle = function(e) {
        // Scarta il secondo evento di jQuery.event.trigger() e
        // Quando un evento è chiamato dopo che la pagina è stata scaricata
        return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
          jQuery.event.dispatch.apply(elem, arguments) : undefined;
      };
    };
    // Gestione del binding dell'evento, considerando che potrebbero essere introdotti più eventi tramite spazi, qui si deve eseguire la gestione multi-evento
    types = (types || "").match(rnotwhite) || [""];
    t = types.length;
    while (t--) {
      tmp = rtypenamespace.exec(types[t]) || []; 
      type = origType = tmp[1];
      namespaces = (tmp[2] || "").split(".").sort();
      // *Deve* esserci un tipo, non attaccare gestori di namespace solo
      if (!type) {
        continue;
      };
      // Se l'evento cambia il suo tipo, utilizza i gestori evento speciale per il tipo modificato
      special = jQuery.event.special[ type ] || {};
      // Se selector è definito, determina il tipo API evento speciale, altrimenti il tipo fornito
      type = (selector ? special.delegateType : special.bindType) || type;
      // Update special based on newly reset type
      special = jQuery.event.special[ type ] || {};
      // 事件回调函数的数据对象
      handleObj = jQuery.extend( {
        type: type,
        origType: origType,
        data: data,
        handler: handler,
        guid: handler.guid,
        selector: selector,
        needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
        namespace: namespaces.join( "." )
      }, handleObjIn );
      // 当第一次绑定该类事件时,会初始化一个数组作为事件回调函数队列,每个元素的每一种事件有一个队列
      if ( !( handlers = events[ type ] ) ) {
        handlers = events[ type ] = [];
        handlers.delegateCount = 0;
        // Only use addEventListener if the special events handler returns false
        if ( !special.setup ||
          special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
          if ( elem.addEventListener ) {
            elem.addEventListener( type, eventHandle );
          };
        };
      };
      if ( special.add ) {
        special.add.call( elem, handleObj );
        if ( !handleObj.handler.guid ) {
          handleObj.handler.guid = handler.guid;
        };
      };
      // Add to the event callback function queue
      if ( selector ) {
        handlers.splice( handlers.delegateCount++, 0, handleObj );
      } else {
        handlers.push( handleObj );
      };
      // Keep track of which events have ever been used, for event optimization
      // 用于追踪哪些事件从未被使用,用于优化
      jQuery.event.global[ type ] = true;
    };
  };
};

Attenzione, gli oggetti e gli array vengono trasmessi come riferimenti! Ad esempio, il codice per salvare i dati degli eventi nella cache:

handlers = events[ type ] = [];
if ( selector ) {
  handlers.splice( handlers.delegateCount++, 0, handleObj );
} else {
  handlers.push( handleObj );
};

La modifica di handlers, events[ type ] cambia contemporaneamente.

dataPriv è l'oggetto che gestisce la cache:

Il suo lavoro è creare un attributo per l'elemento, che è un oggetto, e poi mettere le informazioni correlate a questo elemento all'interno di questo oggetto, memorizzarle. Così, quando si hanno bisogno di informazioni su questo oggetto, è sufficiente sapere che esiste per ottenerlo:

function Data() {
  this.expando = jQuery.expando + Data.uid++;
};
Data.uid = 1;
// Eliminare parte del codice non utilizzato
Data.prototype = {
  cache: function( owner ) {
    // Estraiamo la cache, la cache è una proprietà dell'oggetto di destinazione
    var value = owner[ this.expando ];
    // Se l'oggetto non è ancora stato memorizzato, creiamolo
    if ( !value ) {
      value = {};
      // Possiamo accettare dati per nodi non elementari nei browser moderni,
      // ma non dovremmo, vedere #8335.
      // Restituisce sempre un oggetto vuoto.
      if ( acceptData( owner ) ) {
        // Se è un nodo improbabile da stringificare o esaminare in un ciclo
        // utilizzare assegnazione semplice
        if ( owner.nodeType ) {
          owner[ this.expando ] = value;
        // Altrimenti, securizzalo in una proprietà non enumerabile
        // configurabile deve essere true per permettere alla proprietà di essere
        // Eliminato quando i dati vengono rimossi
        } else {
          Object.defineProperty(owner, this.expando, {
            value: value,
            configurable: true
          });
        };
      };
    };
    return value;
  },
  get: function(owner, key) {
    return key === undefined ?
      this.cache(owner) :
      // Usa sempre la chiave camelCase (gh-2257) nome a punteggio
      owner[this.expando] && owner[this.expando][jQuery.camelCase(key)];
  },
  remove: function(owner, key) {
    var i;
      cache = owner[this.expando];
    if (cache === undefined) {
      return;
    };
    if (key !== undefined) {
      // Supporta array o stringa separata da spazi di chiavi
      if (jQuery.isArray(key)) {
        // Se la chiave è un array di chiavi...
        // Impostiamo sempre le chiavi camelCase, quindi rimuovile.
        key = key.map(jQuery.camelCase);
      } else {
        key = jQuery.camelCase(key);
        // Se esiste una chiave con spazi, utilizzala.
        // Altrimenti, crea un array abbinando i caratteri non bianchi
        key = key in cache ?
          [key] :
          (key.match(rnotwhite) || []);
      };
      i = key.length;
      while (i--) {
        delete cache[key[i]];
      };
    };
    // Rimuovi l'expando se non ci sono più dati
    if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
      // Support: Chrome <=35 - 45
      // Webkit & Blink performance suffers when deleting properties
      // from DOM nodes, so set to undefined instead
      // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
      if ( owner.nodeType ) {
        owner[ this.expando ] = undefined;
      } else {
        delete owner[ this.expando ];
      };
    };
  },
  hasData: function( owner ) {
    var cache = owner[ this.expando ];
    return cache !== undefined && !jQuery.isEmptyObject( cache );
  };
};

Questo è tutto il contenuto dell'articolo, speriamo che sia utile per la tua apprendimento e che tu sostenga fortemente il tutorial urla.

Dichiarazione: il contenuto di questo articolo è stato tratto da Internet, è di proprietà del rispettivo proprietario, il contenuto è stato contribuito e caricato autonomamente dagli utenti di Internet, questo sito non detiene i diritti di proprietà, non è stato editato manualmente e non assume alcuna responsabilità legale. Se trovi contenuti sospetti di violazione del copyright, ti preghiamo di inviare una e-mail a: notice#oldtoolbag.com (al momento dell'invio dell'e-mail, sostituisci # con @) per segnalare il problema e fornire prove pertinenti. Una volta verificata, questo sito rimuoverà immediatamente il contenuto sospetto di violazione del copyright.

Ti potrebbe interessare