
렉시컬 스코핑
function outerFunc() { var x = 10; var innerFunc = function () { console.log(x); }; innerFunc(); } outerFunc();
- 어떻게 내부 함수에서 외부 함수의 변수에 접근할 수 있을까?
스코프 체인이 전역 스코프의전역 객체및 외부 및 내부 스코프의활성객체를 순차적으로 바인딩- 스코프 체인이 바인딩한 객체가 바로
렉시컬 스코프JS 엔진이 스코프 체인을 검색할 수 있기 때문에 내부 함수가 외부함수의 변수에 접근할 수 있음
function outerFunc() {
var x = 10;
var innerFunc = function () { console.log(x); };
return innerFunc;
}
/**
* 함수 outerFunc를 호출하면 내부 함수 innerFunc가 반환된다.
* 그리고 함수 outerFunc의 실행 컨텍스트는 소멸한다.
*/
var inner = outerFunc();
inner(); // 10
소스코드 분석
1. inner에 outerFunc 함수의 내부 함수인 innerFunc 함수를 return 받음
2. inner 함수를 호출 하면 innerFunc을 호출하는 것과 같으므로 10 출력
10이 출력 되는 이유
- outerFunc이 innerFunc을 return 하게되면 자기 역할 다 했으므로 outerFunc 스코프의 변수 x도 더 이상 외부에서 사용할 수 없을 것이라 예상 했고 그 예상 대로라면 10이 출력 되지 않았어야 했다.
- 하지만 JS의
스코프개념을 통해 10이 출력이 가능
클로저
- 외부 함수 밖에서 내부함수가 호출되더라도 외부함수의 지역 변수에 접근할 수 있는데 이러한 `함수
- 반환된 내부함수가
자신이 선언됐을 때의 환경(Lexical environment)인 스코프를 기억하여자신이 선언됐을 때의 환경(스코프) 밖에서 호출되어도그 환경(스코프)에 접근할 수 있는 함수- 선언 되었던 곳의 환경을 밖에서도 사용할 수 있다는 뜻
알아두면 좋은 것
- 클로저에 의해 참조 되는 외부함수의 변수를
자유변수라고 함- 클로저를 자유변수에 엮여있는 함수라고도 함
활성 객체(외부함수의 변수, 함수 선언 등의 정보)는내부 함수에 의해 참조되는 한유효하여 내부함수가스코프 체인을 통해 참조할 수 있음- 내부 함수는 외부함수 변수의 복사본을 참조하는 것이 아니라
실제 변수를 참조하고 있음
전역 변수
var counter = 0;
function increase() {
return ++counter;
}
console.log(increase());
console.log(increase());
console.log(increase());
지역 변수 사용지역 변수
function increase() {
var counter = 0;
return ++counter;
}
console.log(increase());
console.log(increase());
console.log(increase());
이전 상태를 기억해서 새로운 상태를 만들고 외부에서 상태를 변경할 수 없는 방법은 없을까? => 클로저클로저
var increase = (function() {
var counter = 0;
return function() {
return ++counter;
}
}());
console.log(increase());
console.log(increase());
console.log(increase());
변수의 값은 누군가에 의해 언제든지 변경될 수 있어 오류 발생의 근본적 원인이 될 수 있다. 상태 변경이나 가변(mutable) 데이터를 피하고 불변성(Immutability)을 지향하는 함수형 프로그래밍에서 부수 효과(Side effect)를
최대한 억제하여 오류를 피하고 프로그램의 안정성을 높이기 위해 클로저는 적극적으로 사용된다.
클로저 활용
// 함수를 인자로 전달받고 함수를 반환하는 고차 함수
// 이 함수가 반환하는 함수는 클로저로서 카운트 상태를 유지하기 위한 자유 변수 counter을 기억한다.
function makeCounter(predicate) {
// 카운트 상태를 유지하기 위한 자유 변수
var counter = 0;
// 클로저를 반환
return function () {
counter = predicate(counter);
return counter;
};
}
// 보조 함수
function increase(n) {
return ++n;
}
// 보조 함수
function decrease(n) {
return --n;
}
// 함수로 함수를 생성한다.
// makeCounter 함수는 보조 함수를 인자로 전달받아 함수를 반환한다
const increaser = makeCounter(increase);
console.log(increaser()); // 1
console.log(increaser()); // 2
// increaser 함수와는 별개의 독립된 렉시컬 환경을 갖기 때문에 카운터 상태가 연동하지 않는다.
const decreaser = makeCounter(decrease);
console.log(decreaser()); // -1
console.log(decreaser()); // -2
궁금한 점
- 자유 변수 참조시 실제 변수를 참조 => 실제 변수 자체가 각각 다른 것인가?
private 키워드를 흉내낼 수 있다.function Counter() {
var counter = 0
this.increase = function() {
return ++counter;
}
this.decrease = function() {
return --counter;
}
}
var controlCounter = new Counter();
console.log(controlCounter.increase()); //1
console.log(controlCounter.increase()); //2
console.log(controlCounter.decrease()); //1
console.log(controlCounter.counter); //undefined
객체의 프로퍼티 뿐만 아니라 렉시컬 환경의 counter 환경 변수도 참조할 수 있는 클로저private 키워드 처럼 사용 가능 var arr = [];
for (var i = 0; i < 5; i++) {
arr[i] = function () {
return i;
};
}
for (var j = 0; j < arr.length; j++) {
console.log(arr[j]());
}
클로저를 이용한 보완
var arr = [];
for (var i = 0; i < 5; i++){
arr[i] = (function (id) { // ②
return function () {
return id; // ③
};
}(i)); // ①
}
for (var j = 0; j < arr.length; j++) {
console.log(arr[j]());
}
클로저 로써 렉시컬 환경에 의해 매개변수 id를 참조할 수 있으므로 반복문을 돌때마다 다른 id를 참조전역으로 처리 되기 때문에 발생하는 문제로써 let 키워드를 사용하면 해결 된다.고차 함수 사용
const arr = new Array(5).fill();
arr.forEach((v, i, array) => array[i] = () => i);
arr.forEach(f => console.log(f()));