프로그래머스 데브코스 1일차

최익·2023년 9월 21일
0
post-thumbnail

TIL 첫 시작

어제는 OT 였고, 오늘이 첫 강의 시작이었다.

오늘 배운 내용은 변수, 상수, 자료형, 메모리, 표현식, 연산자, 흐름제어, 배열/객체, 스코프, 클로저 대부분 완벽히는 아니지만 어느정도 아는 내용이었다.

그 중, 잘 몰랐던 클로저에 대해 부족한 내용은 코어 자바스크립트 책과 구글링을 통해 학습하였다.

클로저

클로저란 함수가 선언된 환경의 스코프를 기억하여 함수가 스코프 밖에서 실행될 때에도 기억한 스코프에 접근할 수 있게 만드는 문법이다.

function a(name) {
	const greet = 'hello, '; // 지역 스코프라 함수가 종료되면 메모리에서 사라짐.

	return function () {
		console.log(greet + name);
	};
};

const world = a("world");
const ik = a("ik");

world(); // hello, world
ik(); // hello, ik

전역 Lexical 환경에는 현재 각각 a:(function)world:(function), ik(function)이 들어있다.

a의 Lexical 환경에는 greet: ‘hello, ‘가 들어있고,
a의 블록안에 존재하는 익명함수 Lexical 환경에는 선언된 것이 없다.

이제 코드의 결과를 봐보자.

순서대로 hello, world와 hello, ik이 출력될 것이다.

분명 가바지 컬렉터에 의해 greet 변수는 world와 ik에 익명함수를 리턴 함과 동시에 사라져야하는데 말이다.

하지만 이 상황에선 클로저로 인해 함수가 선언된 환경의 스코프를 기억하여 world와 ik이 참조하는 greet는 각각 “world”와 “ik”으로 메모리에서는 사라졌지만 선언될 당시의 스코프를 기억하여 각각 hello, world와 hello, ik을 출력한 것이다.

위 코드의 Lexical 환경이 참조하는 순서는 아래와 같다.

💡 익명함수 Lexical 환경 →(참조) a Lexical 환경 →(참조) 전역 Lexical 환경

위 순서로 현재 스코프에 존재하지 않는 변수를 상위 스코프(Lexical 환경)에서 찾는다.


이어서 다음 코드의 결과에 대해서도 고민해보자.

function count() {
	let i = 0;
	for (i = 0; i < 5; i ++) {
		setTimeout(function () {
			console.log(i);
		}, i * 100);
	};
};

count();

처음에 setTimeout 안의 익명함수의 존재를 잊어버리고 당연하게 결과를 0, 1, 2, 3, 4가 나올 것이라 예상 하였지만, 애석 하게도 답은 5, 5, 5, 5, 5다.

클로저 중에서도 setTimeout을 사용한 부분이 가장 어려웠는데, 반복문의 상황에 대해 적어보겠다.

첫 번째 반복문 상황 → i의 값은 0이고, timer 함수가 작동하여 100ms 뒤, i의 값을 콘솔에 출력하도록 명령.

두 번째 반복문 상황 → i의 값은 1이고, timer 함수가 작동하여 100ms 뒤, i의 값을 콘솔에 출력하도록 명령.

세 번째 반복문 상황 → i의 값은 2이고, timer 함수가 작동하여 100ms 뒤, i의 값을 콘솔에 출력하도록 명령.

네 번째 반복문 상황 → i의 값은 3이고, timer 함수가 작동하여 100ms 뒤, i의 값을 콘솔에 출력하도록 명령.

마지막 반복문 상황 → i의 값은 4이고, timer 함수가 작동하여 100ms 뒤, i의 값을 콘솔에 출력하도록 명령.

💡 이 상황에서
  1. 각각의 setTimeout() 함수는 콜스택에 쌓임.
  2. 이어서 웹 API중 하나인 타이머가 생성되며, 익명함수는 Web APIs로 이동한다.
  3. setTimeout()이 완료되고 콜스택에서 제거됨.
  4. 설정한 타이머가 지난 후 익명함수는 콜백 큐로 이동
  5. 이 작업이 5번 반복되고, for 루프는 종료 → 콜백 큐에는 5개의 익명함수가 차례로 쌓인 상태
  6. 루프가 모두 종료되어 현재 콜스택이 비어있는 상태이므로, 이벤트 루프는 콜백큐의 익명함수를 하나씩 콜스택으로 올림.
  7. 익명함수 실행 → 여기서 익명함수들이 참조해야하는 i는 이미 for 루프가 모두 돌면서 5로 변화된 상태. 따라서 콜백 큐에 있는 익명함수들이 참조하는 i의 값은 5이기 때문에 차례로 콜백큐의 익명함수가 실행 되면서 5가 5번 찍히는 것이다.
💡 이를 해결하기 위해
  • IIFE(즉시 실행 함수)를 사용하여 setTimeout 부분을 괄호로 감싸 즉시 실행 함수 형태로 만들어주면 별도의 스코프가 생성되어 해결 가능하다.
    let i = 0;
    for (i = 0; i < 5; i++) {
    	(function(){
        	var innerI = i;
        	setTimeout(function timer() {
        		console.log(innerI);
        	}, i * 100);
        })();
    }
    // 0, 1, 2, 3, 4
  • 또는 let 키워드를 for문 의 () 안에 넣는 것이다. 하나의 블록스코프를 가지고 하나의 반복문 마다 새로운 i를 각각의 익명함수들이 자신들이 선언된 스코프의 i 값을 참조하게 만드는 것이다.
    for (let i = 0; i < 5; i++) {
    	setTimeout(function timer() {
    	  console.log(i);
      }, i * 100);
    }
    // 0, 1, 2, 3, 4

그렇다면 클로저는 언제 사용할까?

상태를 안전하게 변경하고 유지하기 위해 사용한다.

다시 말해, 상태가 의도치 않게 변경되지 않도록 상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해 사용한다.

느낀점

클로저에 대해서는 코어 자바스크립트 책을 보며 어느 정도 익혔다고 생각했는데 오산이었다. 개발 공부를 할 때 겉핥기식으로 공부하는 것이 아니라 깊게 파고들어야겠다고 생각했고, 오늘 JS에 대한 기본적인 내용의 강의를 들으며 더욱 기본에 충실해야겠다는 느낌을 받았다.

profile
https://choi-ik.tistory.com/ 👈🏻 여기로 블로그 이전했습니다 ㅎ

0개의 댓글