[Today I Learned] 12월 1주차 day1 + 1

suwoncityboyyy·2022년 12월 5일
0

scope(스코프)

스코프란 식별자에 대한 유효범위이다.
스코프 란 자바스크립트 엔진이 참조의 대상이 되는 식별자(identifier) 를 검색 할 때 사용하는 규칙의 집합이다.
즉, 어떤 변수를 사용하거나 함수를 호출하려고 할 때 해당하는 식별자로 사용하는데, 그 식별자를 검색하는 메커니즘이라고 이해하면 된다.

  • Scope A의 외부에서 선언한 변수는, A의 외부/내부 모두 접근 가능하다.
  • A의 내부에서 선언한 변수는 오직 A의 내부에서만 접근할 수 있다.

렉시컬스코프(Lexical)

프로그래머가 코드를 짤 때, 변수 및 함수/블록 스코프를 어디에 작성하였는가에 따라 정해지는 스코프를 렉시컬스코프 라고 한다.
"렉시컬(Lexical)" 이라는 명칭이 붙은 이유는 자바스크립트 컴파일러가 소스코드를 토큰(Token)으로 쪼개서 의미를 부여하는 렉싱(Lexing) 단계에 해당 스코프가 확정되기 때문이다.
다시 쉽게 말하면, 변수 혹은 함수/블록이 어디에 써있는가를 보고 그 스코프를 판단하면 된다.

스코프체인

현재 스코프에서 식별자를 검색할 때 상위 스코프를 연쇄적으로 찾아나가는 방식 을 말한다. 실행 컨텍스트를 배웠다면 생성될 때마다 LexicalEnvironment가 만들어지고 그 안에 outer 참조 값이 있다는 것을 알 것이다. 바로 이 outer 참조 값이 상위 스코프의 LexicalEnvironment를 가리키기 때문에 이를 통해 체인처럼 연결되는 것이다.

즉, 다음과 같은 과정으로 스코프 체인을 검색한다.

  1. 현재 실행 컨텍스트의 LexicalEnvironment의 EnvironmentRecord에서 식별자를 검색한다.
  2. 없으면 outer 참조 값으로 스코프 체인을 타고 올라가 상위 스코프의 EnvironmentRecord에서 식별자를 검색한다.
  3. 이를 outer 참조 값이 null 일 때까지 계속하고 찾지 못한다면 에러를 발생시킨다.

클로저(closure)

클로저란 함수가 속한 렉시컬 스코프를 기억하여 함수가 렉시컬 스코프 밖에서 실행될 때도 그 스코프에 접근할 수 있게 하는 기능을 말한다.

function outer() {
  var a = 2;
  function inner() {
    console.log(a);
  }
  return inner;
}

var func = outer();
func(); // 2

여기서 GC(Garbage Collector)가 outer()의 참조를 없앨 것 같지만, 내부함수인 inner()가 해당 스코프의 변수인 a를 참조하고 있기 때문에 없애지 않는다.
따라서 스코프 외부에서 inner()가 실행되도 해당 스코프를 기억하기 때문에 2를 출력하게 된다. 즉, 여기서 클로저는 inner()가 되며 func에 담겨 밖에서도 실행되고 렉시컬 스코프를 기억한다.

예제

클로저를 사용하는 대표적인 예제는 역시 "반복문 클로저"이다.

function func() {
  for (var i=1; i<5; i++) {
    setTimeout(function() { console.log(i); }, i*500);
  }
}
func(); // 5 5 5 5

코드의 의도한 바는 1부터 4까지 간격을 두고 출력하는 것이었지만 5가 4번 출력된다.
왜 이렇게 되는 것일까?
setTimeout() 을 반복문 안에서 돌리면 콜백함수가 계속해서 task queue에 쌓이게 되고,
반복문이 끝나고 나서 call stack으로 돌아와서 실행된다.
콜백함수는 클로저이기 때문에 상위 스코프에게 i 의 값을 물어보고, 상위 스코프인 func 의 스코프에선 i 가 5까지 증가했기 때문에 5가 4번 출력된다.

해결방안

위 문제를 해결하기 위해선 2가지 방법이 있다

  • 새로운 함수 스코프로 해결하기
function func() {
  for (var i=1; i<5; i++) {
    (function (j) { 
      setTimeout(function() { console.log(j); }, j*500);
    })(i);
  }
}
func(); // 1 2 3 4

setTimeout() 을 IIFE(Immediately Invoked Function Expression, 즉시실행함수 표현식)로 감싸게 되면, 새로운 스코프를 형성하고 나중에 콜백함수가 j 를 참조할 때 그 시점의 i 값을 갖기 때문에 원하는 결과를 얻을 수 있게 된다.

  • 블록 스코프로 해결하기
function func() {
  for (let i=1; i<5; i++) {
    setTimeout(function() { console.log(i); }, i*500);
  }
}
func(); // 1 2 3 4

함수 스코프가 아닌 블록 스코프를 갖는 let 을 사용하면 for 문 내의 새로운 스코프를 갖기 때문에 매 반복마다 새로운 i 가 선언되고 반복이 끝난 이후의 값으로 초기화가 된다.
따라서, setTimeout() 의 클로저인 콜백함수가 i 를 참조하기 위해 상위 스코프를 검색할 때 블록 스코프에서 매 반복마다 선언 및 초기화 된 i 를 참조하는 것이다.

profile
주니어 개발자 기술노트

0개의 댓글