본문 바로가기
{Javascript}

01.11.23{개발일기}onerror와 addEventListener 둘 중 어느것이 더 나은가?

by Davey 2023. 1. 11.
728x90

AddEventListener와 onerror 용도 차이 고민 계기


image 태그에 src 속성값을 불러 올 때 404 에러 예외처리가 필요했고, 대체 이미지를 설정하는 과정에서 error이벤트 리스너를 통해 대체 이미지 주소로 src 속성값 핸들링이 필요했습니다. MDN으로 error event handler를 검색해보았을 때 크게 2가지 방식이 정의 되어 있음을 확인할 수 있었습니다. 첫번째 방식은 Element.addEventListener의 첫번째 파라미터 값에 'error'라는 이벤트를 넣고 콜백함수를 실행하는 방식이었습니다.

element.addEventListener('error', function() { /* do stuff here*/ });


다른 하나는 onerror 라는 이벤트 핸들러 속성에 함수를 할당해 설정하는 방식이었습니다. 이에 2가지중 어느 방법이 코드의 확장성 및 대중적인 면에서 더 나은지 궁금하게 되었습니다.

element.onerror = function () { /*do stuff here */ };
element.onerror = () => { /*do stuff here */ };



addEventListener의 역사


처음엔 stackoverflow에 관련내용을 검색했을때, addEventListener의 역사부터 접하게 되었습니다.

 

출처: https://stackoverflow.com/questions/6348494/addeventlistener-vs-onclick

 

 

EventTarget 인스턴스 메소드 중 하나인 addEventListener는 인터넷 익스플로우 버전 9까지는 아래와 같은 형식으로 작성해야 했음을 알게 되었습니다.

element.attachEvent('error', myFunctionReference);

 

 

하지만 버전10부터는 다른 모든 브라우저를 포함하여 addEventListener를 사용하게 되었고, addEventListener의 두번째로 들어갈 수 있는 인수들중 useCapture가 attachEvent()에는 없는 특화된 기능임을 알게 되었습니다.

출처: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

 

var myFunctionReference = function() { /* do stuff here*/ }


element.attachEvent('onclick', myFunctionReference);
element.addEventListener('click', myFunctionReference , false); 


/* false는 useCapture라는 불리언타입의 인수값이다 */


버블링 단계는 알고 있었지만, 버블링 단계 이전의 캡쳐링 단계 및 타겟 단계에 관해서는 명확하게 설명되어 있지 않았습니다. 이에 관련 내용을 더욱 찾아 보게 되었습니다.

 

이벤트리스너 실행시점 및 DOM Event Architecture와 Event Flow


그렇다면 이러한 이벤트 리스너의 실행시점은 언제일까요? 결론부터 말하자면 이벤트 버블링 단계에서 실행됩니다. W3C 표준 DOM 이벤트 관련 문서를 참조해보면 크게 3가지 단계로 흐름이 나뉘어져 있음을 확인할 수 있습니다.

 

첫번째는 캡처링 단계입니다. 이벤트가 최상위 조상요소에서 하위 타겟요소까지 전파되는 단계입니다. 두번째는 타겟 단계인데, 이벤트가 실제 타깃요소에 전달되는 단계입니다. 마지막으로 세번째 단계는 버블링 단계입니다. 이벤트가 상위요소로 전파되는 단계입니다. 앞서 addEventListener의 useCapture는 이벤트 버블링 전 단계인 캡쳐 단계에서 이벤트리스너를 실행합니다. 캡쳐 단계에서 이벤트 리스너가 실행되는 것은 거의 드문 일임을 모던 자바스크립트 튜토리얼 문서에서 언급하고 있습니다.

https://www.w3.org/TR/DOM-Level-3-Events/#event-flow



Event Handler들의 특징 및 차이


DOM 이벤트 아키텍쳐라니, 다소 주제로부터 멀리 돌아온것 같나요? 이벤트 리스너는 DOM 이벤트 흐름을 핸들링하는 도구이므로 이벤트 흐름 내부 동작원리와 매우 깊은 상관관계가 있습니다. 이에 본론에 들어가기에 앞서 먼저 언급하게 되었습니다. 다시 본론으로 돌아와서, AddEventListener와 'onevent' 속성을 활용한 2가지 방식의 리스너 중 어느것을 사용하는게 더 좋을까요? MDN에서 Event handgling 에 관한 문서를 참조하면 이에 대한 해답을 얻을 수 있습니다.

 

출처: https://developer.mozilla.org/en-US/docs/Web/Events/Event_handlers

 

먼저 onevent 프로퍼티 부터 살펴볼까요?



onevent 프로퍼티는 on접두사 뒤에 이벤트 이름을 붙여 타깃단계의 요소에게 이벤트 객체를 할당할 수 있습니다. 하지만 하나의 요소에서 발생하는 모든 이벤트들은 반드시 하나의 이벤트 리스너만 할당 될 수 있다는 것이 특징입니다. 도대체 이것은 무엇을 의미할까요?

 

 

MDN문서에 따르면, onevent 프로퍼티는 하나의 이벤트 리스너만 특정 이벤트에 할당 되기 때문에 중복해서 이벤트리스너를 할당하면 맨 마지막 이벤트 리스너만 실행된다는 의미입니다.

코드를 통해 좀 더 이해하기 쉽게 예시를 보여드리겠습니다. 우선 click이라는 이벤트가 button 요소에서 발생한다고 가정해봅시다. 예시 코드를 보면, click이벤트에 2개의 이벤트 리스너가 할당되어 있는데, 실재로 동작하는 이벤트 리스너는 맨마지막 changeBtnColor만 실행된다는 것입니다. 마치 덮어씌워지는 개념과 유사합니다. 만약 수백 수천개의 파일에서 onclick을 사용하면 어떻게 될까요? 이벤트 리스너는 맨 마지막으로 평가되는 js파일에서 최종적으로 실행될 것입니다. 하지만 개발자의 의도와 다르게 동작할 위험성이 높고, 다른 팀원이 작성한 onclick 코드가 여러분이 작성한 onclick 코드를 덮어씌울 수도 있습니다. 이렇게 된다면, 구현한 기능이 제대로 동작하지 않을 가능성도 고려해야합니다.

 

const button = document.querySelector('.btn')

const changeBtnBorder = () => { button.style.border = '1px solid #fff'}
const changeBtnColor = () => { button.style.backgroundColor = '#000000'}

button.onclick = changeBtnBorder /* Not Working*/
button.onclick = changeBtnColor  /* Working */

/* 함수에 소괄호를 붙이지 않고 할당하는 이뉴는 객체로 이벤트 리스너에 함수를 넘겨주기 때문이다.*/

 

앞서 MDN 문서에 'only one event handler can be assigned for every event in an element' 부분 중 'only one event handler .. for every event' 구문이 해석하는데 중의적 해석을 하게 되었습니다. 첫째는 ”하나의 요소는 여러가지 이벤트들 중 단 한개의 이벤트 객체만 이벤트리스너를 달 수 있는데, 이때 붙일 수 있는 개수가 한개다“라는 해석이었습니다. 다른 하나는 “하나의 요소에는 모든 이벤트에 리스너를 붙일 수 있는데, 개수는 오로지 단 한개만 가능하다”는 의미였습니다.

지금 생각해보면 속성명이 다르기 때문에 서로 다른 이벤트 리스너끼리는 서로 영향을 주지 않음을 예측할 수 있었지만, 당시엔 궁금했습니다. 이에 아래와 같이 코드를 작성해 브라우저에서 실행하고 확인했습니다. 이를통해 서로 다른 이벤트 리스너끼리는 연속하여 선언해도 덮어씌워지지 않고 각각 따로 동작함을 확인 가능했습니다.

 

/* 하나의 요소에 두가지 이벤트 리스너를 선언하고 그 중 onclick 리스너는 중복 할당하는 경우*/
const samplebtn = $.querySelector('.sample-btn')

function changeBackgroundColor() { 
  samplebtn.style.backgroundColor = '#FF0000'
};
function changeFontColor() { 
  samplebtn.style.color = '#800000'
};
function changeFontSize() {
  samplebtn.style.fontSize = '3rem'
};

samplebtn.onclick = changeBackgroundColor;  /* Not Working*/
samplebtn.onclick = changeFontSize;         /* Working */
samplebtn.onmouseover = changeFontColor;    /* Working */

 

그렇다면 addEventListener는 어떨까요?



addEventListener는 이벤트마다 여러개의 이벤트 리스너를 붙일 수 있음을 문서를 통해 확인할 수 있습니다. removeEventLister()도 있다는걸 알게 되었습니다. 상황에 따라 특정요소에 이벤트 리스너를 항상 붙일 필요가 없다면, 이벤트 리스너를 실행한 후 종료되기 전에 지워 주는것도 효율성을 높이는데 좋아보였습니다.


 

 

아래 코드를 작성하여 브라우저에서 확인해 보았을 때에도 각각의 이벤트가 발생할 때, 이벤트 리스너가 모두 동시에 실행됨을 확인할 수 있었습니다.

const samplebtn = $.querySelector('.sample-btn')

function changeBackgroundColor() { 
  samplebtn.style.backgroundColor = '#FF0000'
};
function changeFontColor() { 
  samplebtn.style.color = '#800000'
};
function changeFontSize() {
  samplebtn.style.fontSize = '3rem'
};

samplebtn.addEventListener('click', changeBackgroundColor); /* Working */
samplebtn.addEventListener('click', changeFontSize);        /* Working */
samplebtn.addEventListener('mouseover', changeFontColor);   /* Working */

 

이벤트 버블링의 장점


논외로 타겟 요소에만 이벤트 리스너가 실행되는게 좋을 것 같지만, 실재 이벤트 버블링을 활용해 더욱 다양한 javascript 기능 구현이 가능해졌음을 알게 되었습니다. 모던 자바스크립트 튜토리얼 문서를 참조해보니 focus 이벤트만 예외적으로 버블링이 안되고 나머지 이벤트들은 모두 이벤트 버블링이 발생함도 알게 되었습니다.

 

결론


결론적으로 addEventListener가 onevent 프로퍼티 보다 하나의 이벤트에 원하는 기능을 다양하게 붙일 수 있는 것이 큰 장점이었습니다. 다른 팀원과 함께 협업 할 때도 동일한 요소에 addEventListener를 붙여 다른 팀원의 코드가 덮어 씌워지지 않는 점도 협업하는 면에 더욱 강력해보였습니다. 비록 onevent 프로퍼티가 코드량은 2배나 줄일 수 있지만 코드의 확장성과 협업면에서 addEventListener가 압승임을 알게 되었습니다.




728x90

댓글