함수 정의 위치에 의해 상위 스코프가 정적으로 결정되는 것
const x = 1;
function foo() {
const x = 10;
bar();
}
function bar() {
console.log(x);
}
foo(); // 1
bar(); // 1
[[Environment]]
함수 정의가 평가되어 함수 객체가 생성될 때, 함수는 자신의 내부 슬롯 [[Environment]]
에 자신이 정의된 환경(상위 스코프의 참조)을 저장함
이때 해당 참조는 현재 실행 중인 실행 컨텍스트의 렉시컬 환경을 가리킴
=> [[Environment]]
에 저장된 현재 실행 중인 실행 컨텍스트의 렉시컬 환경 = 상위 스코프
함수는 존재하는 동안 내부 슬롯 [[Environment]]
를 통해 자신의 상위 스코프를 기억함
=> 외부 렉시컬 환경에 대한 참조에 [[Environment]]
에 저장된 렉시컬 환경의 참조가 할당됨
클로저: 일반적으로 생명 주기가 종료된 외부 함수(상위 스코프)의 변수를 참조할 수 있는 중첩 함수를 의미함
이론적으로는, 상위 스코프의 식별자를 참조하거나 식별자의 값을 변경할 수 있는 함수를 의미함 => 모든 함수는 사실 클로저임
const x = 1;
function outer() {
const x = 10;
const inner = function () {
console.log(x);
};
return inner;
}
const innerFunc = outer();
innerFunc(); // 10
outer
함수 실행 종료 시, outer
함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 제거되어도 렉시컬 환경은 유지됨outer
함수의 렉시컬 환경은 inner
함수의 [[Environment]]
에 의해 참조되고 있고, inner
함수는 innerFunc
에 의해 참조되고 있으므로 가비지 컬렉션의 대상이 아님=> 외부 함수가 소멸해도 반환된 중첩 함수는 외부 함수의 변수를 참조할 수 있음
function foo() {
const x = 1;
// 일반적으로 클로저로 보지 않음
function bar() {
const y = 2;
console.log(y);
}
return bar;
}
const bar = foo();
// 상위 스코프는 기억되지 않음
bar();
function foo() {
const x = 1;
// 클로저였지만 곧바로 소멸함
function bar() {
console.log(x);
}
bar();
}
foo();
function foo() {
const x = 1;
const y = 2;
// 클로저
function bar() {
console.log(x);
}
return bar;
}
const bar = foo();
// foo의 y는 기억되지 않음
bar();
cf) 자유 변수: 클로저가 참조하는 상위 스코프의 변수
const increase = (function () {
let num = 0;
return function () {
return ++num;
};
})();
console.log(increase()); // 1
console.log(increase()); // 2
const counter = (function () {
let num = 0;
return {
// 클로저인 메소드를 갖는 객체를 반환함
increase() {
return ++num;
},
decrease() {
return --num;
},
};
})();
console.log(counter.increase()); // 1
console.log(counter.decrease()); // 0
// 함수를 인수로 전달받고 함수를 반환하는 고차함수
function makeCounter(aux) {
// 자유 변수
let num = 0;
return function () {
num = aux(num);
return num;
};
}
function increase(n) {
return ++n;
}
function decrease(n) {
return --n;
}
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
function makeCounter() {
let num = 0;
return function (aux) {
num = aux(num);
return num;
};
}
function increase(n) {
return ++n;
}
function decrease(n) {
return --n;
}
const counter = makeCounter();
// 같은 상위 스코프를 참조함 (자유 변수를 공유함)
console.log(counter(increase)); // 1
console.log(counter(decrease)); // 0
캡슐화: 객체의 프로퍼티와 메소드를 하나로 묶는 것
정보 은닉: 객체의 특정 프로퍼티나 메소드를 감출 목적으로 캡슐화하는 것
JS는 접근 제한자를 제공하지 않으므로 객체의 모든 프로퍼티와 메소드는 기본적으로 public함
function Person(name, age) {
this.name = name;
const _age = age;
// 인스턴스 메소드
this.sayHi = function () {
console.log(`Hi, I'm ${this.name} and ${_age}`);
};
}
const me = new Person('J', 20);
me.sayHi(); // "Hi, I'm J and 20"
console.log(me.name); // 'J', public
console.log(me._age); // undefined, private
function Person(name, age) {
this.name = name;
const _age = age;
}
// 프로토타입 메소드
Person.protoType.sayHi = function () {
// Person 생성자 함수의 지역 변수 _age를 참조할 수 없음
console.log(`Hi, I'm ${this.name} and ${_age}`);
};
const Person = (function () {
let _age = 0;
// 클로저
function Person(name, age) {
this.name = name;
_age = age;
}
// 클로저
Person.prototype.sayHi = function () {
console.log(`Hi, I'm ${this.name} and ${_age}`);
};
return Person;
})();
const me = new Person('J', 20);
me.sayHi(); // "Hi, I'm J and 20"
const you = new Person('H', 30);
you.sayHi(); // "Hi, I'm H and 30"
// _age 값이 유지되지 않음
me.sayHi(); // "Hi, I'm J and 30"
=> JS는 정보 은닉을 완전하게 지원하지 않음. 최신 브라우저에서는 클래스에 private
필드를 정의할 수 있음
const funcs = [];
// 코드 블록이 반복 실행될 때마다 새로운 렉시컬 환경이 생성되어 식별자 값이 유지됨
for (let i = 0; i < 3; i++) {
funcs[i] = function () {
return i;
};
}
for (let j = 0; j < funcs.length; j++) {
console.log(funcs[j]()); // 0 1 2
}
// 배열에 추가된 함수들은 클로저임
const funcs = Array.from(new Array(3), (_, i) => () => i);
funcs.forEach(f => console.log(f())); // 0 1 2