[JavaScript] 실행 컨텍스트와 클로저

미래·2023년 6월 10일
0

CS

목록 보기
5/6

실행 컨텍스트는 쉽게 말해 자바스크립트의 실행 환경이다. 실행할 코드에 제공할 환경 정보들을 모아놓은 객체로, 자바스크립트의 동적 언어로서의 성격을 가장 잘 파악할 수 있는 개념이다.

자바스크립트에서는 처음 코드를 실행할 때 모든 것을 포함하는 전역 컨텍스트를 만드는데, 이는 모든 것을 관리하는 환경으로 페이지가 종료될 때까지 유지된다. 함수 컨텍스트는 함수를 호출할 때마다 생기는 컨텍스트다. 모든 자바스크립트 코드는 실행 컨텍스트의 내부에서 실행된다. 컨텍스트는 콜 스택에 쌓인다.

컨텍스트의 원칙
1. 전역 컨텍스트 생성 후, 함수 실행 시마다 함수 컨텍스트가 생긴다(선언 시가 아님을 기억!).
2. 컨텍스트 생성 시 컨텍스트 안에 변수객체(arguments, variable), scope chain, this가 생성된다.
3. 컨텍스트 생성 후 함수가 실행되는데, 사용되는 변수들은 변수 객체 안에서 값을 찾는다. 없다면 스코프 체인을 따라 올라가며 찾는다.
4. 함수 실행이 마무리되면 해당 컨텍스트는 사라진다(클로저 제외). 전역 컨텍스트는 페이지가 종료되면 사라진다.

다음의 코드를 예시로 보자.

var name = 'mirae';
function write(word) {
	console.log(word + ' ' + name);
}
function say() {
	var name = 'taeo';
    console.log(name);
write('hello');\
}
say();

해당 코드의 결과는 lexical scoping에 의해 taeo, hello mirae라는 텍스트를 리턴한다. 이 코드를 위의 컨텍스트 원칙에 따라 실행해보자.


전역 컨텍스트

전역 컨텍스트가 생성된 후, 두 번째 원칙에 따라 변수객체, scope chain, this가 생성된다. 전역 컨텍스트는 arguments(함수의 인자)가 없고, virable은 스코프의 변수들로 name, write, say가 있다.

scope chain은 자기 자신인 전역 변수객체다. this는 따로 설정되어 있지 않으면 window다. 이 this를 따로 바꾸는 방법이 바로 new를 호출하는 것이다.

이것을 이렇게 표현해볼 수 있다.

'전역 컨텍스트': {
	변수객체: {
	    arguments: null,
    	variable: ['name', 'write', 'say'],
    },
	scopeChain: ['전역 변수객체'],
	this: window,
}

코드가 위에서부터 실행되면, write와 say는 호이스팅 때문에 선언과 동시에 대입이 된다. 그 후 variable의 name에 'mirae'가 대입된다.

variable: [{ name: 'mirae' }, { write: Function }, { say: Function }]  

함수 컨텍스트

say();를 하는 순간 새로운 컨텍스트인 say 함수 컨텍스트가 생긴다. 전역 컨텍스트는 그대로 있다. arguments는 없고, variable은 name뿐이다. scope chain은 say 변수객체와 상위의 전역 변수객체다. this는 따로 설정한 것이 없기 떄문에 window다.

'say 컨텍스트': {
	변수객체: {
    	argumnets: null,
        variable: ['name'], //초기화 후 [{ name: 'taeo'}]가 됨
	},
	scopeChain: ['say 변수객체', '전역 변수객체'],
    this: window,
}

say를 호출하면 variable의 name에 'taeo'를 대입한다. 그리고 console.log(name);의 변수는 say의 컨텍스트 안에서 찾아 'taeo'를 콘솔에 출력한다. 그다음의 write('hello')는 say 컨텍스트에서 찾을 수 없기에 scope chain을 따라 올라가 상위 변수객체에서 찾는다. 즉, 전역 변수객체의 variable에서 write라는 함수를 찾아 호출한다.

이렇게 되면 write의 함수 컨텍스트가 생긴다. arguments는 word = 'hello'고, scope chain은 write 스코프와 전역 스포크다. 여기서 중요한 것은 lexical scoping에 따라 write 함수의 스코프 체인이 선언 시에 이미 정해져 있다는 것이다. 따라서 say 스코프는 write 컨텍스트의 scope chain이 아니다. variable은 없고, this는 window다.

'wirte 컨텍스트': {
	변수객체: {
    	argument: [{ word: 'hello' }],
        variable: null,
    },
    scopeChain: ['write 변수객체', ' 전역 변수객체'],
    this: window,
}

say 컨텍스트는 아직 종료되지 않았다. write 함수 안에 console.log(word + ' ' + name);이 있는데, word와 name 변수는 write 컨텍스트에서 찾으면 된다. word는 arguments에서 찾을 수 있고, name은 scope chain을 따라 가다보면 mirae라고 되어 있다. 그렇기에 hello mirae가 되는 것. write 컨텍스트는 say 컨텍스트와 관련이 없기 때문이다.

write의 함수 종료 후 write 컨텍스트가 사라지고, say 함수의 실행이 마무리된다. 따라서 say 컨텍스트도 사라지며, 마지막에 전역 컨텍스트도 사라진다.


클로저

비공개 변수를 가질 수 있는 환경에 있는 함수가 바로 클로저다. 비공개 변수는 클로저 함수 내부에 생성한 변수가 아니고, 매개변수도 아닌 변수를 의미한다. 클로저를 말할 때는 스코프/컨텍스트/비공개 변수와 함수의 관계를 항상 같이 말해주어야 한다.

var makeClosure = function() {
	var name = 'mirae';
    return function() {
    	console.log(name);
    }
};
var closure = makeClosure(); // function() { console. log(name); }
closure(); // 'mirae';

closure 함수 안에 console.log(name)이 있는데, name은 closure의 매개변수도 아니고, 함수 내부에서 생성한 변수도 아니다. 이런 것이 바로 비공개 변수다. function() { console. log(name); }은 name 변수나, name 변수가 있는 스코프에 대해 클로저라고 부를 수 있다.

전역 컨텍스트 생성 후 makeClosure 함수 호출 시 makeClosure 컨텍스트도 만들어진다. 다만 closure = makeClosure() 상황에서 function을 리턴하는데, 이 function 선언 시의 scope chain은 lexical scoping을 따라서 ['makeClosure 변수객체', '전역 변수객체']를 포함한다.

closure를 호출할 때의 컨텍스트

'closure 컨텍스트': {
	변수객체: {
    	arguments: null,
        variable: null,
    scopeChain: ['closure 변수객체', 'makeClosure 변수객체', '전역 변수객체']
    this: window,
}

따라서 closure 함수에서 scope chain을 통해 makeClosure의 name 변수에 접근이 가능하다.

즉!

외부함수가 종료된 후에도 선언될 당시의 렉시컬 환경을 기억하여 스코프 체인에 의해 외부함수의 변수에 접근할 수 있는 내부함수
가 바로 클로저다!

이런 방식으로 비공개 변수를 만들어 활용이 가능하다. 비공개 변수이기 때문에 다른 사람이 조작할 걱정 없이, 프로그램 사용자는 작성자가 공개한 메소드만 사용해야 한다. 자바스크립트에서 사용자를 통제하기 위한 기본적인 방법이 바로 클로저다. 단점으로는 성능 문제와 메모리 문제가 발생할 수 있다는 것이 있다.

profile
여전히 에디터, 새롭게 개발자

0개의 댓글