7. Scope and Closure
with 하루
내 질문에 대한 내용 정리
Q1.
스코프와 스코프 체인은 언제 결정되는 것 일까요?
- 상위 스코프(외부 렉시컬 환경에 대한 참조)는 함수 정의가 평가되는 시점에 함수가 정의된 위치에 의해 결정된다.
Q2.
자바스크립트에서 지역 스코프를 가지는 키워드들은 무엇이 있을까요? (ex. function 등)
- 전역 스코프
- function, class
- if, for, while, try / catch
📌 주의) 객체 리터럴은 지역 스코프를 가지지 않는다. (코드 블록이 아니기 때문)
Q3.
내부 함수는 무조건 클로저라고 할 수 있을까요?
- 자바스크립트의 모든 함수는 상위 스코프를 기억하므로 이론적으로 모든 함수는 클로저이다.
- 하지만 일반적으로 모든 함수를 클로저라고 하지는 않는다.
- 클로저는 내부 함수가 상위 스코프의 식별자를 참조하고 있고 내부 함수가 외부 함수보다 더 오래 유지되는 경우에 한정하는 것이 일반적이다.
// 예외 케이스 1
// 외부 함수보다 내부 함수의 생명 주기가 짧은 경우
function foo() {
const x = 1;
function bar() {
console.log(x);
}
bar();
}
foo();
// 예외 케이스 2
// 상위 스코프의 식별자를 참조하지 않는 경우
function foo() {
const x = 1;
function bar() {
const z = 3;
console.log(z);
}
return bar;
}
const bar = foo();
bar();
Q4.
클로저는 상태가 의도치 않게 변경되지 않도록 은닉하는데 자주 사용됩니다. 하지만 일부는 클로저를 잘못 사용하게 되는 경우에 메모리 누수가 일어날 수 있어 사용을 지양한다고 합니다. 클로저를 사용할 때 유의할 점과 메모리 관리를 어떻게 하면 좋을지 이야기를 나눠봐요 😊
클로저는 외부 함수의 지역변수를 사용하기 때문에 메모리를 소모하도록 만든다. 이 때문에 필요성이 사라진 시점에는 참조 카운트를 0으로 만들어 GC가 수거하도록 해야한다.
클로저의 식별자에 null을 할당하여 참조 카운트를 0으로 만들어 메모리를 해제할 수 있다.
let foo = (function () {
const x = 1;
const bar = function () {
console.log(x);
};
return bar;
})();
foo();
foo = null;
+) 클로저는 상위 스코프의 일부 식별자만 필요한 경우에도 상위 스코프를 모두 기억해야 하므로 불필요한 메모리가 사용된다?
A. 모던 자바스크립트 엔진은 최적화가 잘 되어 있어 클로저가 참조하고 있지 않는 식별자는 기억하지 앟는다. 즉, 상위 스코프의 식별자 중에서 기억해야 할 식별자만 기억한다.
하루 질문에 대한 내용 정리
Q1
전역변수를 사용하면 어디서든지 사용할 수 있어 편하게 느껴지기도 합니다. 이렇게 전역 스코프 또는 전역 네임스페이스에 모든 변수를 선언하는 것은 왜 안티패턴일까요?
- 전역 변수는 모든 스코프에서 접근이 가능하기 때문에 어디서 값을 변경시켰는지 파악하기 어렵다.
- 전역 실행 컨택스트는 프로그램이 종료될 때까지 생존해 있기 때문에 GC의 대상이 되지 않는다. 즉, 생명 주기가 길다.
- 스코프 체인의 종점에 위치하기 때문에 변수를 검색할 때 전역 변수가 가장 마지막에 검색된다. 즉, 전역 변수의 검색 속도가 가장 느리다.
- 코드가 길어지고 프로그램이 복잡해지면 전역에 어떤 변수를 선언했는지 기억하기 어렵다.
- 변수 이름을 짓기가 어려워진다. 특히, 공통으로 많이 사용되는 변수명 (ex, num, name 등)
Q2
전통적으로 함수 레벨 스코프를 지원(var)해오던 자바스크립트에서 블록 레벨 스코프를 지원(const, let)하게 된 이유는 무엇일까요?
1번 질문에서 살펴본 것 처럼 스코프를 크게 가져가는 것보다는 작게 가져가는 것이 이점이 많기 때문이라고 생각한다. 이 때문에 자바스크립트에 블록 레벨 스코프가 적용되었다고 생각한다. 하지만 기존 var 키워드가 함수레벨 스코프에서 블록 레벨 스코프가 적용되도록 바뀐다면 이전에 작성된 프로그램이 정상 동작하지 않을 수 있으므로 블록 레벨 스코프를 적용하기 위해 let과 const 키워드가 등장했다고 생각한다.
Q3
동적스코프와 정적 스코프(렉시컬 스코프) 중 어떤 상위 스코프 결정 방식이 클린 코드를 작성하고 개발자가 코드를 수월하게 읽는데 도움이 될까요?
- 대부분의 언어들은 렉시컬 스코프를 사용한다고 한다.
- 동적 스코프는 프로그램 런타임 중 실행 컨텍스트에 따라 달라진다.
- 동적 스코프는 함수를 호출한 쪽에서 유연하게 함수를 재활용할 수 있다.
- 동적 스코프는 개발자가 의도하지 않은 결과를 가질 위험성이 있다.
- 동적 스코프를 확인하기 위해서는 호출 순서를 따라가야하지만 렉시컬 스코프는 정의된 위치만 확인하면 되므로 가독성이 좋다고 생각한다.
기타 인사이트
- for 문의 변수 선언문에 let 키워드를 사용한 for 문은 코드 블록이 반복해서 실행될 때마다 코드 블록을 위한 새로운 렉시컬 환경을 생성한다. 이는 for 문의 변수 선언 문 및 for 문의 코드 블록 내에서 선언된 지역 변수 등의 값을 유지하기 위함이다. for 문이 끝나고 for 문이 생성한 렉시컬 환경에 대한 참조가 없다면 가비지 컬렉션에 의해 렉시컬 환경은 사라진다.