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

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":[
       {  //요소 클릭 이벤트의 이벤트 데이터
             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) {
      // event에 정보가 포함되어 있기 때문에 빈 집합을 사용할 수 있습니다.
      jQuery().off(event);
      return origFn.apply(this, arguments);
    };
    // 동일한 guid를 사용하여 호출자가 origFn을 사용하여 제거할 수 있도록 합니다.
    fn.guid = origFn.guid || (origFn.guid = jQuery.guid);++ );
  }
  /************************************************
  *** jQuery는 모든 선택된 요소를 배열에 담아두고, 그 다음
  *** 각 요소에 이벤트 객체의 add 메서드를 사용하여 이벤트를 바인딩합니다.
  *************************************************/
  return elem.each(function() {
    jQuery.event.add(this, types, fn, data, selector);
  });
}

파라미터를 처리하는 코드도 확인할 수 있습니다. on("click", function() {})와 같은 호출을 구현합니다. on: function(types, selector, data, fn)도 오류 없이 작동합니다. 실제로는 내부에서 data, fn 파라미터가 비어 있을 때 selector를 fn에 할당하는 것입니다. 

이벤트 객체는 이벤트 바인딩의 핵심 객체입니다:

이곳에서 이벤트를 요소에 바인딩하고 요소 캐시에 이벤트 정보를 추가하는 작업을 처리합니다:

jQuery.event = {
  add: function(elem, types, handler, data, selector) {
    var handleObjIn, eventHandle, tmp,
      이벤트, t, handleObj,
      특별한 핸들러, 핸들러, 타입, 네임스페이스, 원래 타입,
      elemData = dataPriv.get(elem);  //이 문장은 elem이 캐시되어 있는지 확인하고, 캐시되지 않았다면 elem 요소에 캐시를 추가합니다. 형식은 elem["jQuery"]와 같습니다.310057655476080253721"] = {};
    // noData나 텍스트에 이벤트를 연결하지 마세요./비주얼 노드(하지만 평범한 객체를 허용)
    if (!elemData) {
      return;
    }
    //사용자는 이벤트 콜백 함수 대신 사용자 정의 데이터 객체를 입력할 수 있으며, 이벤트 콜백 함수를 이 데이터 객체의 handler 속성에 배치할 수 있습니다.
    if (handler.handler) {
      handleObjIn = handler;
      handler = handleObjIn.handler;
      selector = handleObjIn.selector;
    }
    //각 이벤트 콜백 함수는 유일한 ID를 생성합니다. 이후 find에 사용됩니다./remove할 때 사용됩니다.
    if (!handler.guid) {
      handler.guid = jQuery.guid++;
    }
    // 요소가 첫 번째 이벤트를 바인딩할 때, 요소의 이벤트 데이터 구조와 메인 콜백 함수(main)을 초기화합니다.
    //설명: 각 요소에 메인 콜백 함수가 있으며, 여러 이벤트를 이 요소에 바인딩할 때 콜백의 진입점으로 사용됩니다
    if ( !( events = elemData.events ) ) {
      events = elemData.events = {};
    }
    //여기가 메인 콜백 함수를 초기화하는 코드입니다
    if ( !( eventHandle = elemData.handle ) ) {
      eventHandle = elemData.handle = function( e ) {
        // jQuery.event.trigger()의 두 번째 이벤트를 버리고
        // 페이지가 로드되지 않은 후 이벤트가 호출되면
        return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
          jQuery.event.dispatch.apply( elem, arguments ) : undefined;
      };
    }
    // 이벤트 바인딩을 처리할 때, 여러 이벤트가 공백으로 구분되어 입력될 수 있으므로, 여러 이벤트 처리를 수행합니다
    types = ( types || "" ).match( rnotwhite ) || [ "" ];
    t = types.length;
    while ( t-- ) {
      tmp = rtypenamespace.exec( types[ t ] ) || []; 
      type = origType = tmp[ 1 ]);
      namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
      // There *절대로* 타입이어야 하며, 네임스페이스를 연결하지 않습니다-제어자만
      if ( !type ) {
        계속;
      }
      // 이벤트가 타입을 변경하면 변경된 타입의 특별한 이벤트 핸들러를 사용합니다
      special = jQuery.event.special[type] || {};
      // 선택자가 정의되어 있으면 특별한 이벤트 api 타입을 결정합니다. 그렇지 않으면 주어진 타입을 사용합니다
      type = (selector ? special.delegateType : special.bindType) || type;
      // Update special based on newly reset type
      special = jQuery.event.special[type] || {};
      // Data object of event callback function
      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 this is the first time to bind this type of event, an array will be initialized as the event callback function queue, and each element of each event has a queue
      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;
        }
      }
      // 이벤트 콜백 함수 퀸에 추가합니다
      if ( selector ) {
        handlers.splice( handlers.delegateCount++, 0, handleObj );
      } else {
        handlers.push( handleObj );
      }
      // 사용된 적 있는 이벤트를 추적하여 이벤트 최적화에 사용합니다
      // 이벤트를 최적화하기 위해 사용되지 않은 이벤트를 추적하는 데 사용됩니다
      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( 소유자 ) {
    // 캐시를 꺼내서, 캐시는 목표 객체의 하나의 속성입니다
    var 값 = 소유자[ this.expando ];
    // 객체가 캐시되지 않았다면, 하나를 생성하십시오
    if ( !값 ) {
      값 = {};
      // 비 소유자 데이터를 받아들이면 됩니다-현대 브라우저의 요소 노드들에 대해
      // 하지만 우리는 그렇지 않아야 합니다, see #8335.
      // 빈 객체를 항상 반환하십시오.
      if ( acceptData( 소유자 ) ) {
        // 문자열로 변환할 가능성이 낮은 노드라면-거나 반복됩니다
        // 플랫 assign 사용하십시오
        if ( owner.nodeType ) {
          소유자[ this.expando ] = 값;
        // 그렇지 않으면 그것을 비활성화 상태로 보안하게 하십시오-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 ?
      this.cache( owner ) :
      // Always use camelCase key (gh-2257) 驼峰命名
      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 ?
          [ key ] :
          ( key.match( rnotwhite ) || [] );
      }
      i = key.length;
      while ( i-- ) {
        delete cache[ key[ i ] ];
      }
    }
    // 데이터가 더 이상 없을 경우 expando를 제거합니다
    if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
      // 지원: Chrome <=35 - 45
      // 프로퍼티를 제거할 때 Webkit & Blink 성능이 저하됩니다
      // 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 );
  }
};

이것이 본 문서의 전체 내용입니다. 이를 통해 여러분의 학습에 도움이 되길 바라며,呐喊 튜토리얼에 많은 지지를 부탁드립니다.

명시: 본 내용은 인터넷에서 수집되었으며, 저작권은 원 저작자에게 있으며, 인터넷 사용자가 자발적으로 기여하고 업로드한 내용입니다. 이 사이트는 저작권을 소유하지 않으며, 인공 편집 처리를 하지 않았으며, 관련 법적 책임을 부담하지 않습니다. 저작권 침해 내용이 있을 경우, notice#w로 이메일을 보내 주시기 바랍니다.3codebox.com에 (이메일 보내기 시, #을 @으로 변경하여) 신고를 해 주시고 관련 증거를 제공해 주시면, 사실이 확인되면 이 사이트는 즉시 저작권 침해 내용을 삭제할 것입니다.

추천 합니다