JavaScript

10. Javascript DOM Event Loop

char1ey 2022. 11. 11. 08:41
목차
1. Event Loop

  1.1. setTimeout과 setInterval
    1.1.1 clearInterval
  1.2. Event Loop
2. setInterval 메서드 활용하기

 


1. Event Loop

 

Javascript의 환경은 힙과 콜스택으로 코드를 한 줄씩 실행 나간다. 이를 싱글스레드라고 한다.

 

하지만 Javascript가 작동하는 것을 보다보면, 싱글스레드 방식으로는 실행되지 않을 방식으로 로직이 구현되기도 한다.

 

이는 브라우저가 Js에서 제공하는 환경외의 것들을 지원해주기 떄문이다(Web APIs).

 

프로그래밍을 할 때, 모든 요소를 우리가 통제하기 위해서는 코드는 어떤 규칙에 의해 구현되는지 알아볼 필요가 있다. 몇가지 메서드로 이를 이해해보자.

 

 

1.1 setTimeout과 setInterval

 

setTimeout과 setInterval은 사용법이 동일하다.

window.setTimeout(callback, 1000)
window.setInterval(callback, 1000)
//callback:function → 실행할 함수
//1000:number → 시간

둘다 window 객체안에 속한 메서드이다. DOM은 document객체로, 서로 다른 객체이므로 헷갈리지 말자. 

 

window를 생략해도 무방하며, 첫번째 인자값에는 함수, 두번째 인자값에는 밀리세컨즈 단위를 적어준다.

2번째 인자인 밀리세컨즈를 충족시키면, 함수를 실행시키는 코드다.

(개념을 좀 더 알아보면 이해하겠지만, 밀리세컨즈는 작동하기 위해 기다리는 최소시간이다.)

 

코드를 살펴봤으니 한 번 실행해보자.

console.log("hello world!")

setTimeout(function(){
	console.log(1)
}, 5000)

setTimeout(function(){
	console.log(2)
}, 1000)

// "hello world!"
// 2
// 1

"hello world!"가 찍히고, 1초뒤에 2, 5초뒤에 1이 찍히는 것을 확인할 수 있다.

 

즉, 2번째 인자값을 충족한후에 실행이 된다.

 

다음은 setInterval을 실행해보자.

console.log("hello world!")

setInterval(function(){
	console.log(1)
}, 1000)
//"hello world!"
//1
//1
//1
//1
//...

이 메서드는 두 번째 인자값을 충족할 때마다 실행이된다.

 

잘 생각해보면 콜스택은 먼저 들어온 함수가 실행이 늦어지면, 다음을 불러와 실행을 시키면서 쌓이는 데, 이 메서드는 그런방식으로는 설명이 불가한 점이 많다. for문으로 무수히 많은 숫자를 출력하는 중이라면 어떻게 실행될까?

console.log(1)

window.setTimeout(function(){
    console.log(2)
}, 1000)

console.log(3)

for(let i = 0; i < 1000000; i++){
    console.log(i)
}
//1
//3
//1
//....
// 999995
// 999996
// 999997
// 999998
// 999999
//2

분명 setTimeout이 먼저 들어갔는데, for문이 끝나고 나서야 2가 출력되는것을 확인할 수 있다.

 

두 메서드는 함수들처럼 변수에 담을 수 있다.

 

1.1.1 clearInterval

Interval에서 빠져 나올 수 있는 메서드이며 인자값으로 Interval을 넣어야한다.

let num = 0

const timeId = setInterval(function(){
    console.log(num++)
    if (num === 5) clearInterval(timdId) // 백그라운드의 setInterval 삭제
},1000)

 

1.2 Event Loop

 

싱글 스레드는 한 동작이 끝나야 다음 동작을 할 수 있다. 이것을 동기라고하며, 반대로 멀티스레드는 여러 작업을 하는데 이를 비동기라고 한다. setTimeout이나 setInterval은 비동기 코드로 인식하여 Web APIs를 처리해주는 백그라운드로 넘어가고 그곳에서 해석을 하며, 해석한 것을 테스크 큐로 넘겨 잠시 대기시킨후 콜스택이 비면 콜스택으로 보내 출력시킨다.

 

크롬 브라우저의 경우를 그림으로 나타내면 다음과 같다.


<그림 1>


위의 화살표들 처럼 데이터들이 옮겨 다니는데, 테스크 큐에서 콜스택으로 넘기는 과정을 이벤트 루프라고 한다.

이때, setTimeout은 queue에 들어가있는데, 두 번째 인자값의 조건을 충족시키더라도, callstack이 비어야 넘어갈 수 있다.

 

let alpha

setTimeout(function(){
    alpha = "abc"
    console.log(2)
}, 0)

console.log(alpha) // undefined

위의 작동방식을 이해하면 이 코드가 이해가 가능하다, alpha를 선언하고, setTimeout을 Web APIs 백그라운드로 보낸후 console.log(alpha)를 출력해 undefined를 출력하고. alpha에 "abc"가 할당된다. console.log를 다시 찍어보면 "abc"가 출력된다.

 

하지만, Js는 여전히 싱글스레드 이기때문에 코드를 작성할 때는 비동기를 동기처럼 작성해야한다.

이렇게 작성하기 위해서는 setInterval안에 다시 setInterval을 넣고 이를 반복하다보면, callback hell에 빠지게 된다.

 

콜백지옥의 해결방법은 promise를 이용하거나, async/await이라는 것을 사용해 해결할 수 있지만, 작동방식을 이해하려면 우선 callback을 제대로 이해해야 하므로 다음에 알아보도록하자.

 

 


2. setInterval 메서드 활용하기

 

웹페이지에서 자동으로 화면이 넘어가는 걸 볼 수 있다. setInterval로 이를 구현할 수 있다.

<ul id="visual">
    <li class="a on">1</li>
    <li class="b">2</li>
    <li class="c">3</li>
    <li class="d">4</li>
    <li class="e">5</li>
</ul>

<script src="./index.js"></script>
* {
    margin: 0;
    padding: 0;
}

ul, li {
    list-style: none;
}

#visual {
    position: relative;
}

#visual > li {
    position: absolute;
    width: 100%;
    height: 500px;
    display: none;
}

#visual > li.on {
    display: block;
}

#visual > li.a {
    background: red;
}

#visual > li.b {
    background: yellow;
}

#visual > li.c {
    background: green;
}

#visual > li.d {
    background: silver;
}

#visual > li.e {
    background: blue;
}

<그림 2>


on이 붙은 li만 출력이 되는 것을 볼 수 있다. 이 숨겨져 있는 li들을 모두 출력하기 위해선 class="on"을 이리저리 옮겨주어야한다. 이를 구현해보도록 하자.

 

우선, JS로 각 li를 선택하는 선택자를 선언하자.

const a = document.querySelector('.a')
const b = document.querySelector('.b')
const c = document.querySelector('.c')
const d = document.querySelector('.d')
const e = document.querySelector('.e')

 

정해진 순서대로 on을 붙이기 위해서는 배열에 담아 각 인덱스마다 "on"을 붙이는 코드를 작성하면 될 거같다.

const arr = [a, b, c, d, e] //배열에 담고
const elements = document.querySelectorAll("#visual > li") // 모든 li를 선택
let count = 0

etInterval(function(){
    for(let i = 0; i < elements.length; i++){
        if(count === 0) {
            a.classList.add('on')
        } else if(count === 1) {
            b.classList.add('on')
        } else if( count === 2) {
            c.classList.add('on')
        } else if( count === 3) {
            d.classList.add('on')
        } else if( count === 4) {
            e.classList.add('on')
        }
		if(++count == 5) count = 0 // 배열안의 데이터는 5개이므로 5마다 반복
    }, 1000)

이렇게 진행하면 한가지 문제가 발생한다. e까지 가서 화면이 멈춘다. 바로 모든 태그에 "on"을 넣었기 때문이다.

 

그렇다면, on을 받았다가 지워주면 된다.

    if(count === 0) {
        a.classList.add('on')
        b.classList.remove('on')
        c.classList.remove('on')
        d.classList.remove('on')
        e.classList.remove('on')
    } else if(count === 1) {
        a.classList.remove('on')
        b.classList.add('on')
        c.classList.remove('on')
        d.classList.remove('on')
        e.classList.remove('on')
    } else if( count === 2) {
        a.classList.remove('on')
        b.classList.remove('on')
        c.classList.add('on')
        d.classList.remove('on')
        e.classList.remove('on')
    } else if( count === 3) {
        a.classList.remove('on')
        b.classList.remove('on')
        c.classList.remove('on')
        d.classList.add('on')
        e.classList.remove('on')
    } else if( count === 4) {
        a.classList.remove('on')
        b.classList.remove('on')
        c.classList.remove('on')
        d.classList.remove('on')
        e.classList.add('on')
    }

위와 같이 고쳐준다. 하지만 너무 복잡해보이고 규칙성을 찾을 수 있었다. 이를 for문으로 해결하자.

 

setInterval(function(){
    for(let i = 0; i < elements.length; i++){
        if(i === count) {
            elements[i].classList.add("on")
        } else {
            elements[i].classList.remove("on")
        }
    }
	if(++count == 5) count = 0
}, 1000)

이렇게 짜주면 시간이 지남에 따라 출력되는 화면이 바뀜을 볼 수 있다.