실행 컨텍스트
Lexical scope
closure
private
비동기
자바스크립트엔진은 스크립트를 실행하다가 함수를 만나면 실행컨텍스트를 생성한다.
이 실행컨텍스트에 함수가 실행될 때의 환경을 저장하며 이렇게 만들어진 실행컨텍스트는 자신만의 스코프(유효범위)를 갖게된다.
var x = 10
var y = 20
function print(){
var x = 5
console.log(x, y)
}
print() // 5,20
실행 컨텍스트 입장에서 살펴보자면
🚥 우선 자바스크립트가 실행되면 글로벌 오브젝트(GO)를 생성한다.
GO에는 전역 변수들과 함수들이 프로퍼티로 저장된다 ( x = 10, y = 20, print = function )
🚥 자바스크립트엔진이 print() 함수를 실행하게되면 새로운 실행컨텍스트가 생성된다. ( print 컨텍스트는 x = 5를 속성으로 저장한다. )
🚥 print 컨텍스트에 y 변수가 없으니 바로 상위 컨텍스트에서 y를 찾게된다.
🚥 만약에 GO에도 y가 선언되어있지 않다면 'y is not defined' 라는 컨퍼런스 에러가 발생한다.
즉, 변수를 참조할 땐 함수가 실행된 컨텍스트 내부를 먼저 살펴보고 없으면 상위 컨텍스트를 계속해서 찾는데 이것이 '스코프 체인'이다.
Lexical Scope는 함수와 변수의 Scope를 선언된 위치를 기준으로,
Dynamic Scope는 함수나 변수의 Scope를 호출된 시점을 기준으로 사용한다.
자바스크립트는 Lexical Scope를 사용하는 점을 유념해서 아래 코드를 살펴보자
var x = 1;
function print() {
console.log(x); // 1
}
function dummy() {
var x = 999;
print();
}
dummy();
만약 Dynamic Scope로 스코프를 정한다면 실행 중 동적으로 변하니까 x값을 999로 출력한다.
var x = 1;
function outer() {
var x = 2;
function inner() {
console.log(x)
}
inner();
}
outer();
function init() {
var name = "Mozilla";
function displayName() {
alert(name);
}
displayName();
}
init();
위에서본 설명들을 조합해서 코드를 살펴보자면
그럼 다음 예제도 같이 살펴보자
function makeFunc() {
var name = "Mozilla";
function displayName() {
console.log(name); // Mozilla
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
몇몇 프로그래밍 언어에서, 함수 안의 지역 변수 들은 그 함수가 처리되는 동안에만 존재한다.
따라서 makeFunc() 실행이 끝나면(displayName함수가 리턴되고 나면) name 변수에 더 이상 접근할 수 없다고 예상하는 것이 일반적이다.
그러나 위의 코드에선 이미 호출이 끝난 함수의 실행 컨텍스트에 접근해서
name 변수값을 받아와 출력할 수 있었다. 이게 어떻게 가능한 것일까?
자바스크립트는 함수를 리턴하고, 리턴하는 함수가 클로저를 형성한다.
클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다.
조금 더 쉽게 설명해보자면
Closure란 자신이 생성된 시점의 환경을 기억하는 함수 즉,
Closure는 자신의 외부 함수 호출이 종료되었음에도 그 외부함수의 변수 or 인자에 접근 할 수 있는 함수이다.
다시 예제를 살펴보자
function makeFunc() {
var name = "Mozilla";
function displayName() {
console.log(name); // Mozilla
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
자바스크립트는 클로저를 이용하여 프라이빗 메소드를 흉내내는 것이 가능하다. 프라이빗 메소드는 코드에 제한적인 접근만을 허용한다는 점 뿐만 아니라 전역 네임 스페이스를 관리하는 강력한 방법을 제공하여 불필요한 메소드가 공용 인터페이스를 혼란스럽게 만들지 않도록 한다.
var counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
};
})();
console.log(counter.value()); // logs 0
counter.increment();
counter.increment();
console.log(counter.value()); // logs 2
counter.decrement();
console.log(counter.value()); // logs 1
✨ 클로저 역할?
세개의 메서드는 클로저 환경의 값만 조작하기 때문에 공용 공간을 더럽히지 않고 private 변수나 함수에 외부에서 직접적으로 접근하여 조작할 수 없어 값을 보존할 수도 있다.
값이 보존되는 또다른 예제를 살펴보자
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
var counter1 = makeCounter();
var counter2 = makeCounter();
counter1.increment();
counter1.increment();
console.log(counter1.value()); /* 2 */
console.log(counter2.value()); /* 0 */
✨ 독립성 유지 방법?
각 클로저는 그들 고유의 클로저에서 privateCounter 변수의 다른 버전을 참조한다.
따라서 각 카운터가 호출될 때마다 하나의 클로저에서 변수 값을 변경해도 다른 클로저의 값에는 영향을 주지 않는다.
이런 방식으로 클로저를 사용하여 객체지향 프로그래밍의 정보 은닉과 캡슐화 같은 이점들을 얻을 수 있다.
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
✨ 비동기 작동에도 사용해
이처럼 자신만의 고유한 환경을 참조하면 다른 환경에서 값이 변해도 영향을 받지 않고 인자로 받은 값을 기억하고 있기 때문에 나중에 호출 되더라도
그 값을 유지한 채 작동된다.
출처:
https://medium.com/sjk5766/lexical-scope-closure-%EC%A0%95%EB%A6%AC-41f5d1c928e4 - 클로저
https://imki123.github.io/posts/33/ - 실행 컨텍스트
https://elvanov.com/2597 - 비동기
https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures - 클로저