스코프와 클로저
1. 스코프 바인딩과 체이닝
1.1 스코프 바인딩
스코프 바인딩은 변수나 함수가 특정 스코프에 연결되는 과정을 의미합니다. 자바스크립트에서는 변수가 선언된 위치에 따라 스코프가 결정됩니다. 이를 통해 변수의 유효 범위와 접근 가능성을 정의합니다. 자바스크립트는 코드가 작성된 위치에 따르는 렉시컬 스코프(lexical scope) 를 사용합니다.
렉시컬 환경
자바스크립트 엔진은 코드를 실행할 때, 스코프마다 렉시컬 환경을 생성합니다. 렉시컬 환경을 이용해 해당 스코프 내의 모든 변수와 함수를 기록하고 관리합니다.
- 환경 레코드(Environment Record): 현재 스코프에 선언된 변수와 함수의 정보를 저장합니다.
- 외부 환경 참조(Outer Environment Reference): 상위 스코프의 렉시컬 환경을 참조합니다. 이를 통해 중첩된 함수가 외부 함수의 변수를 참조할 수 있습니다.
1.2 스코프 체이닝
스코프 체이닝은 중첩된 스코프에서 변수를 찾는 과정을 의미합니다. 자바스크립트는 렉시컬 스코프를 사용하기 때문에, 변수를 찾을 때 가장 가까운 스코프부터 시작하여 상위 스코프로 이동하며 변수를 찾습니다. 이를 통해 중첩된 함수에서 외부 함수의 변수를 참조할 수 있습니다.
function outer() {
const y = 20; // outer 함수의 지역 변수
function inner() {
const z = 30; // inner 함수의 지역 변수
console.log(x, y, z);
}
inner();
}
outer();
function outer() {
const y = 20; // outer 함수의 지역 변수
function inner() {
const z = 30; // inner 함수의 지역 변수
console.log(x, y, z);
}
inner();
}
outer();
console.log(x, y, z)
에서 x를 찾을 때, inner 함수의 렉시컬 환경에서 x를 찾습니다. x를 찾지 못하면 outer 함수의 렉시컬 환경으로 이동하여 y를 찾고, y도 없으면 전역 스코프로 이동하여 x를 찾습니다.
이게 가능한 이유는 inner 함수가 outer 함수의 렉시컬 환경을 기억하고 있기 때문입니다. inner 함수는 outer 함수의 스코프에 접근할 수 있으며, outer 함수는 전역 스코프에 접근할 수 있습니다. 따라서 inner 함수는 전역 변수 x와 outer 함수의 지역 변수 y에 접근할 수 있습니다. 이렇게 스코프가 연결되어 있는 것처럼 동작하는 구조를 스코프 체이닝이라고 합니다.
2. 클로저
2.1 클로저란?
클로저(Clousre)는 함수와 그 함수가 선언된 렉시컬 환경의 조합 을 뜻합니다. 클로저는 함수가 생성될 때의 환경을 기억하고, 그 환경에 접근할 수 있는 기능을 제공합니다. 클로저를 사용하면 내부 함수가 외부 함수의 변수에 접근할 수 있게 되어 일종의 폐쇄된 공간 안의 데이터를 안전하게 다룰 수 있습니다.
반환된 함수인 inner는 outer 함수의 렉시컬 환경을 기억하고 있으며 outerVar에 접근할 수 있습니다. outer 함수가 종료되더라도 inner 함수는 outerVar에 접근할 수 있습니다. 이는 클로저의 특징입니다.
3.2 클로저의 활용
클로저는 다양한 상황에서 유용하게 사용됩니다. 특히, 데이터 은닉, 함수의 상태 유지, 이벤트 핸들러 등에서 많이 활용됩니다.
3.2.1 데이터 은닉
클로저를 사용하면 함수 내부의 변수를 외부에서 접근할 수 없도록 숨기고, 함수 외부에서 함수 내부의 변수에 접근할 수 있도록 할 수 있습니다. count
는 외부에서 직접 접근이 불가하며 클로저를 통해 접근할 수 있습니다. 이를 통해 변수와 메서드를 private하게 은닉하고, 캡슐화할 수 있습니다.
3.2.2 모듈 패턴
클로저를 사용하여 모듈 패턴을 구현할 수 있습니다. 모듈 패턴은 데이터와 메서드를 캡슐화하여 외부에서 접근할 수 없도록 하는 패턴입니다. 이를 통해 코드의 가독성을 높이고, 유지 보수성을 향상시킬 수 있습니다. 주로 즉시 실행 함수(IIFE)와 함께 사용됩니다. IIFE는 함수를 즉시 실행하여 클로저를 생성합니다.
ES6 모듈이 표준화되기전까지 이 패턴이 주로 사용되었습니다.
3.2.3 쓰로틀링
쓰로틀링(Throttling)은 함수 호출의 빈도를 제어하는 기술입니다. 자주 발생하는 이벤트(예: 스크롤, 리사이즈 등)에 대해 일정 시간 간격으로 함수를 실행하도록 제한합니다. 이를 통해 성능을 최적화하고 불필요한 함수 호출을 방지할 수 있습니다.
일정 시간 동안 한 번만 함수를 실행하도록 하는 쓰로틀링을 구현해보겠습니다. 다음과 같이 throttle
함수를 작성할 수 있습니다. 외부에서 접근 불가한 timerFlag를 이용하여 일정 시간 동안 함수가 실행되지 않도록 합니다.
위와 같이 쓰로틀링을 적용하여, 버튼 클릭 이벤트가 발생하더라도 2초에 한 번만 handleClick
함수가 실행되도록 할 수 있습니다. 버튼을 빠르게 클릭해도 2초 간격으로만 함수가 실행됩니다.
3.2.3 디바운싱
디바운싱(Debouncing)은 이벤트가 발생한 후 일정 시간 동안 추가적인 이벤트가 발생하지 않을 때만 함수를 실행하는 기술입니다. 주로 입력 필드에서 사용자가 입력을 마친 후에 API 요청을 보내는 경우에 유용합니다.
위와 같이 디바운싱을 적용하여, 버튼 클릭 이벤트가 발생하더라도 마지막 클릭 후 2초가 지나야 handleClick
함수가 실행되도록 할 수 있습니다. 사용자가 빠르게 클릭하더라도 마지막 클릭 후 2초가 지나야 함수가 실행됩니다.
이렇게 클로저를 활용하여 다양한 패턴을 구현할 수 있습니다. 클로저는 자바스크립트에서 매우 중요한 개념이며, 이를 이해하고 활용하는 것은 자바스크립트 개발에 큰 도움이 됩니다.
3.3 클로저의 단점
클로저는 매우 유용한 기능이지만, 몇 가지 단점도 존재합니다. 클로저를 사용할 때 주의해야 할 점은 다음과 같습니다.
- 메모리 누수: 클로저는 외부 함수의 변수를 참조하기 때문에, 외부 함수가 종료된 후에도 해당 변수가 메모리에 남아있게 됩니다. 이로 인해 메모리 누수가 발생할 수 있습니다. 따라서 클로저를 사용할 때는 불필요한 변수를 참조하지 않도록 주의해야 합니다.
- 성능 저하: 클로저는 함수가 생성될 때마다 새로운 렉시컬 환경을 생성합니다. 이로 인해 성능이 저하될 수 있습니다. 특히, 많은 양의 데이터를 처리하는 경우 성능에 영향을 줄 수 있습니다.
- 디버깅 어려움: 클로저는 함수가 생성될 때의 환경을 기억하기 때문에, 디버깅이 어려울 수 있습니다. 특히, 중첩된 함수가 많을 경우 어떤 변수를 참조하는지 파악하기 어려울 수 있습니다.