본문 바로가기
📌 Front End/└ jQuery

[JQuery] 이벤트 처리 방법(event binding)

by 쫄리_ 2023. 3. 10.
728x90
반응형

jQuery를 사용할 때 이벤트 처리는 아주 빈번하게 사용하는 기능 가운데 하나입니다. 이 글에서는 jQuery 내부에서 이벤트를 어떻게 처리하는지 설명합니다.

먼저 jQuery에서 사용할 수 있는 다양한 이벤트 처리 방법을 살펴보고, jQuery의 이벤트 객체와 구조를 살펴보겠습니다. 마지막으로 이벤트를 처리하는 각 단계에서 실제 어떤 흐름을 거쳐 이벤트를 처리하는지 확인하겠습니다.

이 글에서 설명하는 내용은 jQuery 2.2.0을 기준으로 작성했습니다.


jQuery의 다양한 이벤트 처리 방법

일반적으로 이벤트 하나에 이벤트 핸들러 하나를 등록해 이벤트를 처리한다. 그러나 jQuery에서는 같은 이벤트를 여러 번 처리하거나 같은 이벤트 핸들러를 여러 이벤트에 등록해 이벤트를 처리할 수 있다.


네임스페이스의 사용

같은 이벤트를 여러 번 처리해야 할 때 jQuery에서는 이벤트를 네임스페이스로 구분할 수 있다. 다음 예와 같이 '이벤트 이름 + 마침표 + 네임스페이스' 형식으로 이벤트 이름 뒤에 네임스페이스를 덧붙인다. 예에서는 'click'이 이벤트 이름에 해당하고, 'naver'가 네임스페이스에 해당한다.

$(elem).on("click.naver", function(e) { ... });

네임스페이스는 이벤트 이름 뒤에 덧붙인 문자열을 기준으로 구분한다. 'click.naver1.naver2'와 같이 이벤트 이름 뒤에 마침표와 네임스페이스를 여러 개를 덧붙여도 첫 번째 마침표를 기준으로 한 개의 네임스페이스, 즉 'naver1.naver2'로 인식한다.

다음과 같은 형태로 네임스페이스를 지정해 이벤트 핸들러를 등록한 예를 살펴보자.

$(elem).on("click.naver1", handler);
$(elem).on("click.naver1.naver2", handler);

다음과 같이 이벤트를 제거하면 한 개 이벤트만 제거될 것이라 생각할 수 있다. 하지만 두 개 이벤트가 모두 제거된다. jQuery가 네임스페이스를 처리할 때 마침표를 기준으로 구분해 처리하기 때문이다.

$(elem).off("click.naver1");

다음 예와 같이 이벤트 핸들러를 제거하면 두 번째 이벤트만 제거된다.

$(elem).off("click.naver1.naver2");

네임스페이스를 두 개 이상 사용한다면 마침표를 기준으로 네임스페이스가 중복되지 않도록 다음과 같이 작성한다.

$(elem).off("click.naver1 ");
$(elem).off("click.naver1-naver2 ");

같은 이벤트 핸들러를 여러 이벤트에 등록

하나의 이벤트 핸들러를 여러 이벤트에 등록할 수 있다. 다음은 같은 이벤트 핸들러를 여러 이벤트에 등록해 처리하는 예다.

$(elem).on("click focus keydown", function(e) { ... });

이벤트 핸들러의 등록을 해제할 때도 다음 예와 같이 여러 이벤트를 지정해 해제할 수 있다.

$(elem).off("click focus keydown")

이벤트 핸들러를 지정하면 지정한 이벤트 핸들러만 등록이 해제된다. 이벤트 핸들러를 지정하지 않으면 지정한 이벤트에 등록된 모든 이벤트 핸들러의 등록이 해제된다.


trigger() 메서드

elem.trigger("click") 형식으로 사용하는 trigger() 메서드는 요소에 등록된 이벤트 핸들러만 실행시킨다. 실제 이벤트가 일어나는 것은 아니기 때문에 <a> 요소에 click 이벤트를 일어나게 해도 href 속성에 설정된 링크로 이동하지는 않는다.

이벤트 핸들러 함수에서 this 키워드

이벤트 핸들러 함수에서 this 키워드는 이벤트 핸들러가 등록된 DOM 요소를 가리킨다. jQuery 함수를 사용하려면 $(this)와 같이 감싸서 jQuery 객체로 만들어야 한다.

$("#some").on("click", function(e) {
       this;     // <== jQuery로 감싸지 않은 요소(예: <div id="some">...</div>)
       $(this);  // <== jQuery로 감싼 요소
});

jQuery의 이벤트 관련 파일

jQuery가 이벤트를 어떻게 처리하는지 알고 싶으면 jQuery 라이브러리의 소스 코드를 직접 보면 된다. 그러나 jQuery 사이트에서 다운로드해 사용하는 파일은 배포 버전으로 빌드된 파일이라 이벤트에 관련된 부분만 찾아 보기 어렵다. 이벤트에 관련된 부분을 쉽게 찾아 보려면 GitHub의 jQuery 저장소에서 관리하는 파일을 확인한다.

jQuery는 기능에 따라 파일을 분리해 저장소에서 관리한다. 일반적으로 jQuery 저장소에서 이벤트에 관련된 파일은 event.js 파일과 event 디렉터리에 있는 파일이다. jQuery를 빌드하면 이 파일의 코드가 하나로 합쳐져 다른 영역의 코드와 결합해 이벤트를 처리한다.


event.js 파일

event.js 파일에는 외부로 공개되는 메서드와 jQuery.event 객체, jQuery.Event 객체의 정보가 있다(jQuery.event 객체와 jQuery.Event 객체는 다른 객체다).

 

이벤트 관련 메서드

다음과 같이 jQuery에서 이벤트를 다룰 때 사용하는 이벤트 등록과 해제에 관련된 메서드가 정의돼 있다.

jQuery.fn.extend({  
    on: function(){},
    one: function(){},
    off: function(){}
});

 

jQuery.event 객체

jQuery의 이벤트 관련 메서드를 실행할 때 사용하는 함수와 속성이 jQuery.event 객체에 정의돼 있다.

jQuery.event = {  
    global: {},  // .on()으로 바인딩된 이벤트 종류를 담는 객체
    add: function( elem, types, handler, data, selector ) {},  // 이벤트 바인딩
    remove: function( elem, types, handler, selector, mappedTypes ) {},  // 이벤트 제거
    dispatch: function( event ) {},  // 이벤트 발생 시 실행
    handlers: function( event, handlers ) {},  // 바인딩된 모든 동일 이벤트에 대한 큐 처리
    props: Array,  // 키와 마우스 이벤트 속성 이름 배열
    fixHooks: {},  // 이벤트 훅 객체
    keyHooks: {},  // 키보드 이벤트 훅 객체
    mouseHooks: {},  // 마우스 이벤트 훅 객체
    fix: function( event ) {},  // 이벤트에 따른 훅 처리 및 네이티브 이벤트를 jQuery로 감싸는 함수
    special: {}  // special 이벤트 객체
}

 

jQuery.Event 객체

jQuery.Event 객체는 네이티브 이벤트 객체를 감싸는 jQuery 객체다. jQuery.Event 객체는 DOM3 이벤트를 기반으로 한다.

jQuery.Event = function( src, props ) { ... }  // jQuery 이벤트 생성자 함수  
jQuery.Event.prototype = {  
constructor: jQuery.Event,  // 생성자  
    isDefaultPrevented: returnFalse,  // event.preventDefault() 메서드 호출 여부
    isPropagationStopped: returnFalse,  // event.stopPropagation() 메서드 호출 여부
    isImmediatePropagationStopped: returnFalse,  // event.stopImmediatePropagation() 메서드 호출 여부

    preventDefault: function() {},  // 이벤트 기본 동작 중단 메서드
    stopPropagation: function() {},  // 이벤트 전파 중단 메서드
    stopImmediatePropagation: function() {}  // 이벤트 전파 및 핸들러 중단 메서드 
};

 

event/trigger.js 파일

jQuery 저장소의 event 디렉터리에 있는 trigger.js 파일에는 이벤트를 실행하는 메서드인 trigger() 메서드와 triggerHandler() 메서드가 다음과 같은 형식으로 정의돼 있다.

jQuery.extend( jQuery.event, {  
    trigger: function( event, data, elem, onlyHandlers ) {},
    simulate: function( type, elem, event ) {}
});

jQuery.fn.extend({  
    trigger: function( type, data ) {},
    triggerHandler: function( type, data ) {}
});

jQuery의 이벤트 실행 메서드는 on() 메서드로 등록한 이벤트 핸들러를 실행하는 메서드다. 이벤트 실행 메서드로는 네이티브 이벤트를 발생시킬 수 없다. 네이티브 이벤트를 발생시키려 할 때는 jquery-simulate 라이브러리를 사용할 수 있다. createEvent() 메서드(Internet Explorer 9 이하에서는 createEventObject() 메서드)로 네이티브 이벤트를 발생시킬 수도 있다.

trigger() 메서드와 triggerHandler() 메서드의 다른 점

trigger() 메서드와 triggerHandler() 메서드는 모두 이벤트를 실행하는 메서드지만 다음과 같은 점이 다르다.

  • triggerHandler() 메서드는 첫 번째 요소에서만 핸들러를 실행한다.
  • triggerHandler() 메서드로 실행하는 이벤트는 이벤트 버블링이 발생하지 않는다.
  • triggerHandler() 메서드의 실행 결과는 이벤트 객체를 반환하지 않고 이벤트 핸들러의 반환값을 반환한다. 그래서 메서드 체이닝이 일어나지 않는다.
  • triggerHandler() 메서드는 폼의 submit 이벤트와 같은 기본 동작을 사용할 수 없다.

 

alias.js 파일과 ajax.js 파일

jQuery 저장소의 event 디렉터리에 있는 alias.js 파일과 ajax.js 파일에는 jQuery에서 사용하는 이벤트 이름이 정의돼 있다.

 

event/alias.js 파일

alias.js 파일에 정의된 이벤트 이름은 다음과 같다. 이 이벤트를 jQuery.fn[이벤트 이름] 형식으로 확장한다. 확장된 이벤트 메서드를 파라미터 없이 호출하면 trigger() 메서드를 호출한다. 파라미터를 사용하면 on() 메서드를 호출한다.

blur focus focusin focusout load resize scroll unload click dblclick  
mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave  
change select submit keydown keypress keyup error contextmenu  
hover bind unbind delegate undelegate  

 

event/ajax.js 파일

ajax.js 파일에는 다음과 같이 AJAX에 관련된 이벤트가 정의돼 있다.

ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend  

이벤트 객체

이벤트 객체는 이벤트 처리에 사용하는 정보를 담고 있다. 이벤트 객체의 구조를 보면서 jQuery가 이벤트 데이터를 어떻게 관리하는지 살펴보겠다.

 

이벤트 정보 캐싱

사용자가 이벤트 핸들러를 등록한 이벤트에 대한 정보는 내부 데이터 객체인 'dataPriv'에 캐싱돼 이벤트 핸들러의 등록을 해제할 때까지 유지된다.

이벤트 핸들러가 등록될 때마다 이벤트 객체 데이터가 생성되는데, 객체의 'guid' 속성이 유일한 값을 갖도록 처리된다. 이 값은 jQuery.guid 속성의 값을 증가해 부여한다.

// 이벤트 객체가 생성될 때마다 유일한 키값을 설정한다.
jQuery.uid++;  

jQuery 3.0의 이벤트 객체 저장

jQuery 3.0에서 이벤트 객체는 dataPriv 객체가 아니라 요소의 속성값으로 저장된다. 예를 들어 <div id="test"></div> 요소에 이벤트 핸들러를 등록하면 Chrome 개발자 도구의 콘솔에서 다음과 같이 이벤트 객체의 속성을 확인할 수 있다.

속성 이름은 dataPriv 객체의 expando 값이 사용되며, jQuery.expando + Data.uid++ 형식으로 값이 구성된다. Chrome 개발자 도구의 콘솔에서 '요소를 참조하는 변수 이름.jQuery' 형식으로 명령어를 입력하면 expando 값을 갖는 속성이 자동으로 완성된다.

data() 메서드의 데이터 캐싱

data() 메서드에서 사용하는 캐시 데이터는 'dataUser' 변수에 저장된다.

요소와 관련된 내부 캐시의 데이터를 얻어 오거나 삭제하려면, 공개되지 않은 API인 _data() 메서드와 _removeData() 메서드를 사용한다.

// dataPriv 변수에 저장된 캐시의 데이터를 얻어올 수 있다.
jQuery._data(element);

// dataPrive 변수에 저장된 캐시를 제거할 수 있다.
// 그러나 이벤트 핸들러의 등록 상태는 그대로고 핸들러에 대한 내용만 삭제된다.
jQuery._removeData(element);  

jQuery는 데이터가 초기화될 때 데이터에 따라 유일한 값을 갖도록 처리된다. 내부 캐시 데이터가 생성될 때마다 다음과 같이 expando 값과 Data.uid 값으로 값을 부여한다.

// Unique for each copy of jQuery on the page
// expando? A property added to an object at run-time
jQuery.expando = "jQuery" + ( version + Math.random() ).replace( /\D/g, "" );

// Data 객체가 생성될 때마다 유일한 키값을 설정해 사용한다.
function Data() {  
     ...
     this.expando = jQuery.expando + Data.uid++;
}

jQuery 3.0에서는 _data() 메서드와 _removeData() 메서드를 외부에서 사용할 수 없도록 처리할 것으로 보인다. 다음과 같이 jQuery 3.0.0 Beta 1 버전에 관련된 내용이 주석으로 명시되어 있다.

var dataPriv = new Data();  
var dataUser = new Data();

//  Implementation Summary
//
...
//  4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)

이벤트 객체 구조

이벤트 핸들러를 요소에 등록하면 이벤트 객체가 생성된다. jQuery에서 생성하는 이벤트 객체의 구조는 다음과 같은 형태다.

// 이벤트 객체는 내부 캐시 데이터 변수 dataPriv에 저장된다.
// 키값은 다음과 같이 구성된다: element[ this.expando ] = Data.uid++
dataPriv.cache[ key ] = {  
    events: {
        focus: [{
            data: Variant,      // 사용자가 이벤트에 전달하려는 데이터
            guid: 1,        // 이벤트 핸들러의 유일한 키(jQuery.guid++)
            handler: USER_BINDED_FUNCTION,  // 사용자가 정의한 핸들러
            namespace: "",      // 네임스페이스
            needsContext: undefined,
            origType: "focus",
            selector: undefined,
            type: "focus"
        }, {
           guid: 2,
           ...
}],
        keydown: [{ 
            guid: 3,
            ...
        },
        {
            guid: 4,
            ...
        }]
    },

    // 네이티브 메서드인 addEventListener() 메서드로 실제 등록되는 공통 함수
    // 모든 이벤트에는 다음과 같은 함수가 등록된다.
    handle: function(e) {
        // Discard the second event of a jQuery.event.trigger() and
        // when an event is called after a page has unloaded
        return typeof jQuery !== "undefined" && 
        jQuery.event.triggered !== e.type ? 
            jQuery.event.dispatch.apply( elem, arguments ) :
            undefined;
     };
    }
};

events 데이터

이벤트 객체는 events 키값의 객체에 다시 이벤트 이름으로 구분된 키값을 갖는 배열 형태의 구조를 갖는다. 배열의 원소에 저장되는 객체는 등록된 이벤트 핸들러 1개에 대응되는 데이터다. 이 데이터는 사용자가 실제로 등록한 이벤트 핸들러와 이벤트 처리에 필요한 정보를 담는다.

만약 어떤 요소에 focus 이벤트의 이벤트 핸들러를 2개 등록하면 'focus' 키의 배열에 2개의 이벤트 객체가 추가된다.

handle() 함수

jQuery에서 이벤트 핸들러를 등록할 때 사용자가 등록한 이벤트 핸들러가 직접 등록되지는 않는다. addEventListener() 메서드로 실제 등록되는 함수는 'handle()' 함수다. 이후 이벤트가 발생할 때 handle() 함수로 사용자가 등록한 핸들러를 실행하고 관리한다.

같은 이벤트에 이벤트 핸들러가 여러 개 등록될 때에도 "events 데이터"에서 살펴본 것과 같이 이벤트 데이터를 큐에 저장하고, 요소에는 해당 이벤트로 이벤트 핸들러가 등록되는 최초 시점에만 handle() 함수를 바인딩한다. 즉, 같은 이벤트에 이벤트 핸들러가 여러 개 등록되더라도 실제로는 하나의 handle() 함수만이 핸들러로 설정됨을 의미한다.

handle() 함수는 이벤트가 발생하면 dispatch() 메서드를 실행한다. dispatch() 메서드는 네이티브 이벤트 객체를 jQuery.Event 객체로 감싸서 처리한다. 더 자세한 처리 과정은 "이벤트가 발생할 때"에서 설명하겠다.


jQuery 이벤트에서 기억할 점

jQuery에서 이벤트를 사용할 때 기억해야 할 점 몇 가지를 정리하면 다음과 같다.

  • 같은 이벤트에 이벤트 핸들러를 여러 번 등록해도 실제로는 제일 처음의 이벤트 핸들러만 DOM에 등록되고, 이후 등록되는 이벤트 핸들러는 내부 이벤트 객체의 배열에 추가된다.
  • DOM에는 사용자가 정의한 이벤트 핸들러가 아니라 jQuery의 'handle()' 함수가 등록된다.
  • 이벤트가 발생하면 이벤트 객체 캐시의 'handler' 속성에 저장된 함수가 실행되며, 이때 네이티브 이벤트 객체를 jQuery.Event 객체로 감싸 파라미터로 전달한다.

참고로, 'jQuery.event.global' 속성을 사용하면 on() 메서드로 이벤트 핸들러가 등록된 이벤트 종류를 확인할 수 있다. 문서에서 click 이벤트와 keydown 이벤트에 이벤트 핸들러가 등록됐을 때 다음 예와 같이 이벤트 종류를 확인한다면 { click: true, keydown: true }가 반환된다.

jQuery.event.global;  

jQuery 이벤트 확장

jQuery에서는 이벤트에 전달되는 이벤트 객체의 속성을 정의하거나 훅을 이용해 이벤트를 확장할 수 있다. jQuery의 이벤트 확장에 관한 더 자세한 내용은 "jQuery Event Extensions"를 참고한다.

props 속성

jQuery.event 객체의 props 속성은 다음과 같이 이벤트 핸들러에 전달되는 이벤트 객체의 속성 목록을 정의한다. 단, trigger() 메서드로 발생하는 이벤트는 제외된다.

jQuery.event.props = ( "altKey bubbles cancelable ctrlKey currentTarget detail eventPhase "  
    + "metaKey relatedTarget shiftKey target timeStamp view which" )
    .split(" ");

예를 들어 모든 event 객체에 'some'이라는 속성을 추가하려면 다음과 같이 props 속성값의 배열에 내용을 추가한다.

jQuery.events.props.push("some");  

이벤트 핸들러로 전달되는 event 객체에 some 속성을 사용하려면 다음과 같이 구현할 수 있다. some 속성의 값은 이벤트 훅으로 처리해야 한다.

elem.on("event", function(event) {  
event.some;  
});

이벤트 훅

이벤트 훅은 이벤트가 발생해 처리되는 특정 시점에 이벤트 객체에 전달되는 속성을 제어할 수 있다. 이벤트 훅을 사용하면 이벤트 속성을 확장할 수 있다.

이벤트 훅을 사용하는 기본 형식은 다음과 같다.

// extend or normalize the event object
jQuery.event.fixHooks["이벤트 이름"] = {  
    // 브라우저 이벤트 객체에서 jQuery 이벤트 객체로 복사돼야 하는 속성의 이름
    props: [],

    // 이벤트 객체를 생성한 후 실행되는 함수. 이벤트 객체에 전달되는 속성을 제어할 수 있다.
    filter: function(event, originalEvent) {}
};

예를 들어 drop 이벤트에 'dataTransfer' 속성을 추가하려면 다음과 같이 이벤트 훅을 사용해 구현할 수 있다.

jQuery.event.fixHooks.drop = {  
    props: [ "dataTransfer" ],
    filter: function(event, originalEvent) { ... }
};

jQuery는 키보드 이벤트와 마우스 이벤트에 이벤트 훅을 사용해 다음과 같은 속성을 이벤트 객체에 추가한다.

  • 키보드 이벤트 훅(jQuery.event.keyHooks): 'char charCode key keyCode' 속성을 키보드 이벤트 객체에 추가한다.
  • 마우스 이벤트 훅(jQuery.event.mouseHooks): 'button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement' 속성을 마우스 이벤트 객체에 추가한다.

simulate() 메서드

simulate() 메서드는 이벤트를 다른 이벤트처럼 작동하게 하는 메서드다. 예를 들어 'focus' 이벤트를 'focusin' 이벤트로 작동하게 할 때 사용한다.

simulate() 메서드를 사용하는 기본 형식은 다음과 같다.

jQuery.event.simulate: function( type, elem, event, bubble ) {  
    var e = jQuery.extend(new jQuery.Event(), event, {
            type: type,
            isSimulated: true,      // simulate 여부 값 속성 추가
            originalEvent: {}      // 네이티브 이벤트는 빈 객체로 설정 
        }
    );

    // 이벤트 버블링 여부에 따라 trigger() 메서드나 dispatch() 메서드를 호출
    if ( bubble ) {
        jQuery.event.trigger( e, null, elem );
    } else {
        jQuery.event.dispatch.call( elem, e );
    }
    ...
}

special 이벤트 훅

special 이벤트 훅은 이벤트의 동작을 특정 시점에 제어하기 위해 사용한다. 제어할 이벤트의 속성과 함수를 설정해 사용한다.

special 이벤트 훅을 사용하는 기본 형식은 다음과 같다.

jQuery.event.special["이벤트 이름"] = {  
    // trigger() 메서드로 실행되었을 때 이벤트 버블링 여부(기본값: false)
    noBubble : Boolean,

    // 해당 이벤트가 지정된 이벤트(일반적으로 DOM 이벤트)와 같이 처리되도록 한다.
    // "click"이라고 지정하면 해당 이벤트는 click 이벤트와 같이 처리
    bindType: String,
    delegateType: String,

    // 이벤트가 바인딩될 때 실행
    // 이벤트를 네이티브 메서드인 addEventListener() 메서드로 바인딩되게 하려면 'return false;' 필수
    setup: function( data: Object, namespaces, eventHandle: function ) { ... },

    // 이벤트가 제거(같은 이벤트가 여러 개 등록됐다면 마지막 이벤트)될 때 실행
    // 이벤트가 제거되게 하려면 'return false;' 필수.
    teardown: function() { ... },

    // 이벤트를 바인딩할 때마다 실행
    // on() 메서드로 등록될 때마다 실행됨
    add: function( handleObj ) { ... },

    // 이벤트를 제거할 때마다 실행
    // off() 메서드로 제거할 때 실행됨
    remove: function( handleObj ) { ... },

    // trigger() 메서드나 triggerHandler() 메서드로 이벤트가 발생할 때 실행
    // 이벤트 객체가 생성된 후 이벤트 핸들러가 실행되기 전에 호출되며, 'return false'면 원래의 핸들러를 실행하지 않는다.
    trigger: function( event: jQuery.Event, data: Object ) { ... },

    // jQuery.trigger() 메서드로 실행되었을 때, 영역 내의 모든 버블링 이벤트 핸들러가 실행된 후 실행
    // to create a default action for any custom event.
    _default: function( event: jQuery.Event, data: Object ) { ... },

    // 이벤트 핸들러가 있다면 이미 등록된 핸들러 대신 실행
    handle: function( event: jQuery.Event, data: Object ) { ... }
}

load 이벤트, focus 이벤트, blur 이벤트, click 이벤트, beforeunload 이벤트에 대한 special 이벤트 훅은 기본으로 정의돼 있다. special 이벤트 훅에 관한 더 자세한 내용은 "jQuery special events"를 참고한다.


jQuery의 이벤트 처리 과정

다음 그림은 위에서 설명한 jQuery의 이벤트 처리 과정을 도식으로 표현한 것이다.

그림 1 jQuery 이벤트 처리 과정

 

이벤트 처리과정

이벤트가 처리되는 과정에서 어떤 단계를 거치는지 살펴보겠다.

 

이벤트 핸들러를 등록할 때

jQuery에서 이벤트 핸들러를 등록할 때는 다음과 같이 on() 메서드를 사용한다.

$(element).on("event", handler);

on() 메서드로 이벤트 핸들러를 등록할 때는 다음 그림과 같은 단계로 이벤트가 처리된다.

그림 2 이벤트 핸들러 등록 과정

on() 메서드를 실행하면 jQuery.event 객체의 add() 함수를 호출한다.

return this.each( function() {  
    jQuery.event.add( this, types, fn, data, selector );
});

add() 함수는 다음과 같은 절차로 이벤트를 처리한다.

1) 캐시 데이터를 얻어온다.

dataPriv.get( elem );  

2) 요소에서 처리할 공통 이벤트 핸들러를 설정한다. 이 작업은 이벤트 핸들러를 처음 등록할 때만 실행한다.

elemData.handle = function(e) {  
    jQuery.event.dispatch.apply()
}

3) 이벤트 객체를 구성한다.

// 이벤트 정보 객체를 생성 후, 이벤트 객체의 "이벤트 이름" 키 값의 배열에 추가
// handleObj is passed to all event handlers
handleObj = jQuery.extend({  
    type: type,
    origType: origType,
    data: data,
    handler: handler,
    guid: handler.guid,  // jQuery.guid++ 증가해 handler에 유니크 값 부여
    selector: selector,
    needsContext: selector && 
jQuery.expr.match.needsContext.test( selector ),  
    namespace: namespaces.join(".")
}, handleObjIn );

4) 이벤트 핸들러가 처음 등록될 때 다음과 같은 작업을 실행한다.

  • 'special["event"].setup()' 함수로 special 이벤트 훅을 실행한다.
  • 요소에 네이티브 이벤트 핸들러를 등록한다.
if ( elem.addEventListener ) {  
    elem.addEventListener( type, eventHandle, false );
}

5) special 이벤트 훅을 실행한다(훅이 설정됐을 때만).

jQuery.event.special["event"].add();  

6) 핸들러 배열에 이벤트 핸들러를 추가한다. handlers 객체는 요소에 대한 내부 캐시 데이터(dataPriv)를 가리킨다.

handlers.push( handleObj );  

7) 어떤 이벤트가 사용됐는지 확인할 수 있게 이벤트 종류를 저장한다.

// Keep track of which events have ever been used, for event optimization
jQuery.event.global[ type ] = true;  

이벤트가 발생할 때

실제로 이벤트가 실제 발생하면 다음 단계를 거쳐 실행된다.

그림 3 이벤트 발생 시 처리 과정

이벤트가 일어나면 jQuery.event 객체의 dispatch() 함수를 실행한다.

eventHandle = elemData.handle = function( e ) {  
    // Discard the second event of a jQuery.event.trigger() and
    // when an event is called after a page has unloaded
    return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
        jQuery.event.dispatch.apply( elem, arguments ) : undefined;
};

dispatch() 함수는 다음과 같이 실행된다.

1) jQuery.event.fix(event) 함수를 실행해 이벤트 훅을 처리하고 네이티브 이벤트를 jQuery 객체로 감싼다.

event = new jQuery.Event( originalEvent );  

2) dataPriv.get( this, "events" ) 함수로 내부 캐시 데이터에 저장된 이벤트 핸들러를 가져온다.

3) jQuery.event.special["이벤트 이름"].preDispatch(event) 함수로 special 이벤트 훅을 실행한다.

4) 요소의 같은 이벤트에 등록된 모든 이벤트 핸들러를 큐에 넣는다.

handlerQueue = jQuery.event.handlers.call( this, event, handlers );  

5) 등록된 이벤트 핸들러만큼 반복하면서 이벤트 핸들러를 실행한다. 이벤트에 jQuery.special["이벤트 이름"].handle 함수가 있으면 원래 등록된 이벤트 핸들러 대신 special["이벤트 이름"].handle 함수를 실행한다.

6) jQuery.event.special["이벤트 이름"].postDispatch(event) 함수로 special 이벤트 훅을 실행한다.

7) return event.result;를 반환한다. result 속성은 'special.handle' 또는 등록된 이벤트 핸들러의 반환값이다.


trigger() 메서드를 실행할 때

trigger() 메서드와 triggerHandler() 메서드는 단순히 jQuery.event 객체의 trigger() 함수를 호출하는 역할만 수행한다.

element.trigger("event");  

trigger() 메서드를 실행하면 다음 그림과 같은 단계로 이벤트가 처리된다.

그림 4 trigger() 메서드 실행 과정

jQuery.event 객체의 trigger() 함수는 다음과 같은 절차로 이벤트를 처리한다.

1) 새로운 이벤트 객체를 생성한다.

new jQuery.Event( type, typeof event === "object" && event );  

2) special.trigger() 함수를 실행한다. 반환값이 'false'면 원래의 이벤트 핸들러를 실행하지 않는다.

3) 이벤트 전파 경로를 작성한다.

for ( ; cur; cur = cur ) {  
    eventPath.push( cur );
    tmp = cur;
}

4) 등록된 이벤트 핸들러를 실행한다.

  • jQuery 이벤트 핸들러를 실행한다.
  • 인라인 이벤트(onEvent)가 있다면 해당 이벤트 핸들러도 실행한다.

5) trigger() 메서드로 실행됐을 때는 영역 내의 모든 이벤트 버블링이 끝난 후 special["event"]._default 함수가 있는지 확인한다. 함수가 있다면 실행한다.

참고

trigger() 메서드를 이용한 이벤트 핸들러 실헹에 관한 더 자세한 내용은"Triggering Event Handlers"를 참고한다.


이벤트 핸들러의 등록을 해제할 때

등록된 이벤트 핸들러를 해제할 때는 다음과 같이 off() 메서드를 사용한다.

element.off("event");  

off() 메서드로 이벤트 핸들러의 등록을 해제할 때는 다음 그림과 같은 단계로 이벤트가 처리된다.

그림 5 이벤트 핸들러 제거 과정

off() 메서드를 실행하면 jQuery.event 객체의 remove() 함수를 호출한다. remove() 함수는 다음과 같은 절차로 이벤트를 처리한다.

1) elem.off("click mousedown keydown");와 같이 이벤트가 여러 개인 경우를 대비해 이벤트를 분리하고 이벤트 핸들러를 제거하는 반복문을 실행한다.

2) 이벤트 파라미터가 전달되지 않았을 때는 모든 이벤트 핸들러를 제거한다.

if ( !type ) {  
    for ( type in events ) {
        // jQuery.event.remove() 함수를 재귀적으로 호출한다.
        jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
    }
}

3) 단일 이벤트에 등록된 이벤트 핸들러를 처리하는 반복문을 실행한다.

4) 이벤트 핸들러 배열인 handlers.splice( j, 1 );에서 값을 제거하는 형태로 큐에서 이벤트 핸들러를 제거한다.

5) special["event"].remove() 함수가 있다면 함수를 실행한다.

6) special["event"].teardown() 함수를 실행한다.

7) jQuery.removeEvent( elem, type, elemData.handle ); 함수로 요소에 등록된 이벤트 핸들러를 실제로 제거하고, event 객체에서 해당 이벤트 관련 내용을 삭제한다.

delete events[ type ];  

8) 요소에 등록된 이벤트 핸들러가 모두 제거됐다면 저장된 이벤트 객체를 캐시에서 제거한다.

// Remove the expando if it's no longer used
if ( jQuery.isEmptyObject( events ) ) {  
    delete elemData.handle;
    dataPriv.remove( elem, "events" );
}

마치며

jQuery의 이벤트 객체와 이벤트 처리 방법을 간략하게 살펴봤다. 여러 가지 내용 가운데서도 다음 내용은 기억하기를 바란다.

  • 실제 DOM 요소에 등록되는 이벤트 핸들러는 사용자가 정의한 이벤트 핸들러가 아니라 jQuery의 이벤트 핸들러다.
  • 같은 이벤트에 이벤트 핸들러를 여러 번 등록해도 처음 한 번만 실제로 등록되고 나머지는 내부에서 큐 형태로 관리된다.
  • 요소에 설정되는 이벤트 정보는 내부 캐시에 저장된다.
  • special 이벤트 훅을 사용하면 특점 시점에 이벤트를 제어할 수 있다.

이 글이 jQuery의 이벤트 처리를 이해하고, jQuery 이벤트를 효율적으로 사용하는 데 도움이 되길 바란다. jQuery 이벤트에 관한 더 자세한 내용이 궁금하다면 "jQuery Event Basics"를 참고한다.

 


[jQuery 세부 분석 시리즈 보기]

"jQuery는 이벤트를 어떻게 처리하는가?"

"jQuery 애니메이션은 어떻게 작동하는가? - 기본 편"

"jQuery 애니메이션은 어떻게 작동하는가? - 심화 편"

"jQuery 애니메이션은 어떻게 작동하는가? - 응용 편"

728x90
반응형