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

Análisis del mecanismo de enlace y desvinculación de eventos de jquery

Introducción

¿Por qué jQuery puede desvincular eventos sin proporcionar una función de devolución de llamada? Véase a continuación:

$("p").on("click",function(){
  alert("El párrafo fue clickeado.");
});
$("#box1").off("click");

Mecanismo de enlace y desenlace de eventos

Al llamar a la función on, se genera un conjunto de datos del evento, con la siguiente estructura:

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

y agrega estos datos a la caché del elemento. En jQuery, cada elemento puede tener una caché (sólo se crea cuando es necesario), que es una propiedad del elemento. jQuery crea una cola para cada tipo de evento de cada elemento, para almacenar las funciones de manejo de eventos, por lo que se pueden agregar múltiples funciones de manejo de eventos a un elemento. La estructura de la caché es la siguiente:

"div#box":{ //elemento
  "Jquery623873:{ //caché del elemento
    "events":{ 
      "click":[
       {  //datos del evento del clic del elemento
             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) {
    //此处省略处理参数的代码
    return this.each(function() {
      jQuery.event.remove(this, types, fn, selector);
    });
  };
});

独立出来供one和on调用的on函数:

function on(elem, types, selector, data, fn, one) {
  var origFn, type;
  //此处省略处理参数的代码
  //是否是通过one绑定,如果是,则使用一个函数代理当前事件回调函数,代理函数只执行一次
  //这里使用了代理模式
  if (one === 1 ) {   
    origFn = fn;
    fn = function(event) {
      // 可以使用一个空集,因为事件包含了所需的信息
      jQuery().off(event);
      返回origFn.apply(this, arguments);
    };
    // 使用相同的guid,以便调用者可以使用origFn进行移除
    fn.guid = origFn.guid || (origFn.guid = jQuery.guid++ );
  };
  /************************************************
  *** jQuery将所有选择到的元素放入一个数组中,然后
  *** Para cada elemento, utiliza el método add del objeto evento para bindingar eventos
  *************************************************/
  return elem.each(function() {
    jQuery.event.add(this, types, fn, data, selector);
  });
};

También puedes ver el código que maneja los parámetros, que realiza la llamada on("click",function(){}), de modo que on: function(types, selector, data, fn) no deje de funcionar. Es básicamente una jugada interna, si los parámetros data, fn están vacíos, asigna selector a fn 

El objeto de evento es un objeto clave para la绑定 de eventos:

Aquí se maneja el trabajo de vincular eventos a elementos y agregar información de eventos a la caché del elemento:

jQuery.event = {
  add: function(elem, types, handler, data, selector) {
    var handleObjIn, eventHandle, tmp,
      events, t, handleObj,
      especial, handlers, type, namespaces, origType,
      elemData = dataPriv.get(elem);  //310057655476080253721"] = {}
    // No adjunte eventos a noData o texto/nodos de comentario (pero permite objetos simples)
    if (!elemData) {
      return;
    };
    //El usuario puede pasar un objeto de datos personalizado para reemplazar la función de retroalimentación del evento, colocando la función de retroalimentación del evento en el atributo handler de este objeto de datos
    if (handler.handler) {
      handleObjIn = handler;
      handler = handleObjIn.handler;
      selector = handleObjIn.selector;
    };
    //Cada función de retroalimentación del evento generará un id único, para encontrar/se usará en el momento de eliminar
    if (!handler.guid) {
      handler.guid = jQuery.guid++;
    };
    // Si el elemento se bindinga al evento por primera vez, inicializa la estructura de datos del evento del elemento y la función de retroalimentación principal (main)
    //Notas: Cada elemento tiene una función de retroalimentación principal, que actúa como punto de entrada para la asociación de múltiples eventos a ese elemento
    if (!( events = elemData.events )) {
      events = elemData.events = {};
    };
    //Aquí es el código para inicializar la función de retroalimentación principal
    if (!( eventHandle = elemData.handle )) {
      eventHandle = elemData.handle = function( e ) {
        // Descartar el segundo evento de jQuery.event.trigger() y
        // cuando se llama a un evento después de que la página se ha descargado
        return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type &&63;
          jQuery.event.dispatch.apply( elem, arguments ) : undefined;
      };
    };
    // Tratar la asociación de eventos, teniendo en cuenta que puede haber múltiples eventos introducidos mediante espacios en blanco, aquí se debe manejar múltiples eventos
    types = ( types || "" ).match( rnotwhite ) || [ "" ];
    t = types.length;
    while ( t-- ) {
      tmp = rtypenamespace.exec( types[ t ] ) || []; 
      type = origType = tmp[ 1 ]);
      namespaces = ( tmp[ 2 )] || "" ).split( "." ).sort();
      // Aquí *debe* ser un tipo, sin adjuntar espacio de nombres-sólo gestores
      if (!type) {
        continuar;
      };
      // Si el evento cambia su tipo, utilice los gestores de eventos especiales para el tipo cambiado
      special = jQuery.event.special[type] || {},
      // Si se define el selector, determine el tipo de api de evento especial, de lo contrario, el tipo dado
      type = (selector &63; special.delegateType : special.bindType) || type;
      // Actualice special basado en el nuevo tipo reajustado
      special = jQuery.event.special[type] || {},
      // objeto de datos de la función de devolución de llamada del evento
      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);
      // Si se une por primera vez este tipo de evento, se inicializará un array como cola de funciones de devolución de llamada de eventos, cada elemento tiene una cola para cada tipo de evento
      if (!(handlers = events[type])) {
        handlers = events[ type ] = [];
        handlers.delegateCount = 0;
        // Sólo utilice addEventListener si el manejador de eventos especiales devuelve 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;
        };
      };
      // 加入到事件回调函数队列
      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;
    };
  };
};

千万注意,对象和数组传的是引用!比如将事件数据保存到缓存的代码:

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

handlers的改变,events[ type ]会同时改变。

dataPriv就是管理缓存的对象:

其工作就是给元素创建一个属性,这个属性是一个对象,然后把与这个元素相关的信息放到这个对象里面,缓存起来。这样需要使用到这个对象的信息时,只要知道这个对象就可以拿到:

function Data() {
  this.expando = jQuery.expando + Data.uid++;
};
Data.uid = 1;
//删除部分没用到代码
Data.prototype = {
  cache: function( owner ) {
    // 取出缓存,可见缓存就是目标对象的一个属性
    var value = owner[ this.expando ];
    // 如果对象还没有缓存,则创建一个
    if ( !value ) {
      value = {};
      // We can accept data for non-element nodes in modern browsers,
      // but we should not, see #8335.
      // Always return an empty object.
      if ( acceptData( owner ) ) {
        // If it is a node unlikely to be stringify-ed or looped over
        // use plain assignment
        if ( owner.nodeType ) {
          owner[ this.expando ] = value;
        // De lo contrario, asegúrelo en un no-enumerable property
        // configurable must be true to allow the property to be
        // deleted when data is removed
        } else {
          Object.defineProperty( owner, this.expando, {
            value: value,
            configurable: true
          });
        };
      };
    };
    return value;
  },
  get: function( owner, key ) {
    return key === undefined & #63;
      this.cache( owner ) :
      // Always use camelCase key (gh-2257) camelCase naming
      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 ) {
      // Support array or space separated string of keys
      if ( jQuery.isArray( key ) ) {
        // If key is an array of keys...
        // We always set camelCase keys, so remove that.
        key = key.map( jQuery.camelCase );
      } else {
        key = jQuery.camelCase( key );
        // If a key with the spaces exists, use it.
        // Otherwise, create an array by matching non-whitespace
        key = key in cache & #63;
          [ key ] :
          ( key.match( rnotwhite ) || [] );
      };
      i = key.length;
      while ( i-- ) {
        eliminar cache[ key[ i ] ];
      };
    };
    // Eliminar el expando si no hay más datos
    if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
      // Soporte: Chrome <=35 - 45
      // El rendimiento de Webkit y Blink sufre cuando se eliminan propiedades
      // de nodos del DOM, por lo tanto, establecer en undefined en su lugar
      // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (restringido por error)
      if ( owner.nodeType ) {
        owner[ this.expando ] = undefined;
      } else {
        eliminar owner[ this.expando ];
      };
    };
  },
  hasData: function( owner ) {
    var cache = owner[ this.expando ];
    devolver cache !== undefined && !jQuery.isEmptyObject( cache );
  };
};

Esto es todo el contenido del artículo, espero que sea útil para su aprendizaje y que todos apoyen a la tutorial de grito.

Declaración: El contenido de este artículo se obtiene de la red, es propiedad del autor original, el contenido se contribuye y carga de manera autónoma por los usuarios de Internet, este sitio no posee los derechos de propiedad, no se ha editado artificialmente y no asume la responsabilidad de las responsabilidades legales relacionadas. Si encuentra contenido sospechoso de infracción de derechos de autor, le invitamos a enviar un correo electrónico a: notice#oldtoolbag.com (al enviar un correo electrónico, reemplace # con @ para denunciar y proporcione evidencia. Si se verifica, este sitio eliminará inmediatamente el contenido sospechoso de infracción de derechos de autor.)

Te gustará