이벤트는 말 그대로 어떠한 사건을 의미하는데 사용자가 어떤 버튼을 클릭하거나 검색창에 글자를 입력했을 때 발생하고, 이벤트가 발생하면 누군가는 이를 감지하고 처리해줘야한다. 실제로 사용자가 어떤 홈페이지에 들어가서 버튼을 클릭하거나 한다면 브라우저가 이벤트를 감지해 이벤트 발생시 통지해준다. 이 과정을 통해 사용자와 웹페이지가 상호작용이 가능하다는 것을 알 수 있는 것이다.
보이는 것과 같이 버튼을 클릭했을 때, 이벤트가 발생하며 이벤트가 발생함과 동시에 함수가 실행된다. 이러한 함수를 이벤트 핸들러라고 하며 이벤트에 대응하는 방법이라고 할 수 있다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<button id="btn">클릭</button>
<script>
document.getElementById("btn").addEventListener("click", function () {
window.alert("click!");
});
</script>
</body>
</html>
1. 이벤트 루프와 동시성
자바스크립트는 싱글쓰레드 언어라고 알려져있는데 여기서 싱글쓰레드라는 것은 쓰레드가 하나뿐이라는 뜻이며 즉, 하나의 작업만을 처리(하나의 쓰레드 = 하나의 콜스텍 = 한번에 하나의 작업)할 수 있다는 것을 뜻한다. 하지만 실제로 사용자가 홈페이지에 접속하여 여러가지 이벤트를 발생시킬 수 있는데 멀티쓰레드처럼 여러가지 task가 동시에 처리되는 것처럼 느낄 수 있다. 이처럼 자바스크립트의 동시성을 지원하는 것을 이벤트 루프라고 하는데 이벤트 루프에 대해서 자세히 알아보자.
Javascript Engine
먼저 그림을 살펴보면 자바스크립트 엔진은 메모리 힙과 콜스택으로 이루어져있다. 가장 먼저 Memory Heap에 있는 코드들이 Call Stack에서 스택 방식으로 쌓이며 코드를 실행하게 되는데 이때는 동기 함수들은 그대로 실행되고, 비동기 함수들은 Web API로 처리하며 일을 분배하게 된다.
- Memory Heap : 동적으로 생성된 객체 인스턴스가 할당되는 영역
- Call Stack : 코드가 실행될 경우 하나씩 stack의 형태로 쌓이는 장소로 자바스크립트는 단 하나의 Call Stack을 사용하기 때문에 해당 task가 종료되기 전까지는 다른 어떤 task도 수행할 수 없다.
Web API
Call Stack에서 실행된 비동기 함수는 Web API에서 처리를 하게 되고 그동안 Call Stack은 나머지 동기 함수들을 처리하게 되는 것이다. Web API는 비동기 함수들을 처리하고 작업이 완료된 비동기 함수들을 Task Queue로 넘겨주게 된다.
Task Queue
task queue는 비동기 함수들을 보관하는 장소로 Event Loop에서 비동기 함수를 꺼내기 전까지는 계속 Queue 방식으로 보관하게 된다.
Event Loop
Event Loop는 Call Stack과 Task Queue의 상태를 계속 감시하며 Call Stack에 함수들이 존재하지 않는다면 Task Queue에 있는 비동기 함수들을 Call Stack에 밀어넣게 된다. 그 이후에 Call Stack에서 비동기 함수를 실행시키게 된다.
즉, 자바스크립트가 싱글쓰레드 언어지만 멀티쓰레드처럼 동작할 수 있는 이유는 브라우저에서 제공하는 이벤트 루프를 이용해 비동기 방식으로 동시성을 지원하기 때문이다. 이벤트 루프에서는 이벤트가 발생하면 호출되는 콜백 함수들을 task 큐에 전달하고, task 큐에 담겨있는 콜백 함수들을 콜스택으로 넘겨주는 역할을 한다.
2. 이벤트 종류
대표적인 이벤트에는 어떤게 있을까 차근차근 살펴보자. 나는 주로 UI 이벤트, 키보드이벤트, Form 이벤트를 사용했던 것 같다. 아직 클립보드 이벤트는 한번도 사용해본적이 없다.
1) UI Event
Event | Desc |
load | 웹페이지가 로드 완료되었을 때 |
unload | 웹페이지가 언로드될 때 |
error | 브라우저가 자바스크립트 오류를 만났거나 요청한 자원이 존재하지 않는 경우 |
scroll | 사용자가 페이지를 위아래로 스크롤할 때 |
select | 텍스트를 선택했을 때 |
resize | 브라우저 창의 크기를 조절했을 때 |
2) Keyboard Event
Event | Desc |
keydown | 키를 누르고 있을 때 |
keyup | 누르고 있던 키를 뗄 때 |
keypress | 키를 누르고 떼었을 때 |
3) Focus Event
Event | Desc |
focus / focusin | 요소가 포커스를 얻었을 때 |
blur / foucusout | 요소가 포커스를 잃었을 때 |
4) Form Event
Event | Desc |
input | input 또는 textarea 요소 값이나 contenteditable특성을 가진 요소 값이 변경되었을 때 |
change | select box, checkbox, radio button의 상태가 변경되었을 때 |
submit | form을 submit할 때 |
reset | reset 버튼을 클릭할 때 |
5) Clipboard Event
Event | Desc |
cut | 콘텐츠를 잘라내기 할 때 |
copy | 콘텐츠를 복사할 때 |
paste | 콘텐츠를 붙여넣기할 때 |
3. 이벤트 핸들러 등록
이벤트가 발생했을 때, 동작할 이벤트 핸들러를 등록하는 방법은 여러가지인데 하나씩 살펴보자.
1) 인라인 이벤트 핸들러 방식
인라인 이벤트 핸들러 방식은 HTML 요소의 "onclick"과 같은 속성을 통해 이벤트 핸들러를 직접 요소에 포함시키는 방식이다. 즉, 함수를 만들어 바로 html 코드에 삽입하는 방식이라고 생각하면 된다.
<body>
<button id="btn" onclick="clickHandler()" >클릭</button>
<script>
const clickHandler = () => {
alert("click!!!")
}
</script>
</body>
인라인 이벤트 핸들러 방식은 간단하고 쉽게 이벤트를 처리할 수는 있지만 코드의 가독성과 유지보수성 등을 저해하는 문제가 있고, HTML과 Javascript는 다른 영역이므로 분리하는 것이 좋아 되도록이면 사용하지 않는 것이 좋다고 한다.
2) 이벤트 핸들러 프로퍼티 방식
두번째는 이벤트 핸들러 프로퍼티 방식인데 인라인 이벤트 핸들러 방식처럼 HTML과 Javascript가 섞여버리는 문제를 해결할 수 있는 방식이다. 하지만 이벤트 핸들러 프로퍼티에 하나의 이벤트 핸들러만을 바인딩할 수 있다는 단점이 있다고 한다.
<body>
<button id="btn">클릭</button>
<script>
document.getElementById("btn").onclick() = function() {
window.alert("click!");
}
</script>
</body>
3) addEventListener 메소드 방식
마지막으로 addEventListener 함수 방식을 사용하는 방법인데 해당 방식은 이전 방식들에 비해 더 많은 장점을 가지며 가장 지향하는 방법이다. addEventListener 메서드를 사용하면 이벤트 버블링과 캡처링을 지원할 수 있다.
<body>
<button id="btn">클릭</button>
<script src="dist/main.js"></script>
<script>
document.getElementById("btn").addEventListener("click", function () {
window.alert("click!");
});
</script>
</body>
평소에 바닐라자바스크립트로 개발을 하면서 평소에 addEventListener 메서드를 정말 많이 사용했었는데 세가지 방법 중 가장 장점이 많아 지향하는 방향인 것 같다. 오늘도 자바스크립트를 공부하며 자바스크립트의 이벤트루프에 다시한번 복습할 수 있었는데 그래도 계속 보다보니 머릿속에 하나씩 들어오는 것 같다. 앞으로도 꾸준히 공부하며 더 탄탄한 개발자가 되도록.