스코프체인과 클로저

Bindung·2020년 5월 6일
0

javascript

목록 보기
2/5
post-thumbnail

들어가기전에

우리는 자바스크립트의 실행문맥중 렉시컬환경(LexicalEnvironment)에 대해서 알고 들어가야 할 것 같다.
그 이유는 자바스크립트의 규칙을 모르고는 너무 언어적감각으로 설명해야 할 것 같기 때문이다.

렉시컬환경(LexicalEnvironment)

자바스크립트 엔진이 실행을 위해 모아논 자원환경이다.
key-value 형식으로 되어져있으며, 유효범위에 있는 변수와 함수, 객체들을 모아논 곳이라고 생각하면 쉬울 듯 하다.

렉시컬환경 구성

  • 환경레코드
  • 외부렉시컬환경

자바스크립트는 이 렉시컬환경을 규칙으로 하고있다.
그로 인해서 렉시컬 스코프가 정해져있으며 동적스코프와 비교대상이 되어진다.

동적스코프와 렉시컬스코프

동적스코프

함수 호출을 기준으로 스코프가 정해진다.

렉시컬스코프

함수 선언을 기준으로 스코프가 정해진다.

동적스코프 렉시컬스코프 비교

var x = "global";

function outer() {
  var x = "local";
  inner();
}

function inner() {
  console.log(x);
}

outer();
inner();

동적스코프 결과

outer() //local
inner() //global

동적스코프같은 경우 호출 기준으로 정해지기에
호출대로 하면 outer은 local을 가져오게 되며
inner은 global을 가져오게된다.
호출하는 순간 바라보는 변수가 정해져버리기 때문이다.

렉시컬스코프 결과

outer() //global
inner() //global

렉시컬스코프는 선언 기준으로 정해지기에
선언을 하고 나서 이므로 호출하기 전부터 변수가 정해져버린다.
그래서 global이 두번다 결과값이 되는 것이다.

자바스크립트는 렉시컬스코프 규칙을 따르기에 글로벌이 두번 나타날 것이다.

스코프체인

스코프체인은 스코프간의 연결이라는 개념으로 생각하는게 좋을 것 같다.

스코프체인을 나타내는 소스

function outer(){
	var a = 1;
	function inner(){ 
		var b = 2;
		console.log(a+b);
	}
	inner();
}

outer() //3

위의 소스에서 console.log(a+b) 에서 3 이 나온다.
a 는 inner에 없지만 outera 의 값을 가져와서 더한다.
inner 의 스코프에서는 a는 없지만 outer 의 스코프에서 발견할 수 있다.
어떻게 이게 가능할까?
이것이 가능한 경우는 렉시컬스코프 환경 덕분이다.

렉시컬스코프 환경

자바스크립트에서는 렉시컬스코프 규칙을 가지고 있으며
렉시컬스코프가 나타날 수 있는 것은 위의 이미지를 보면 조금이나마 이해에 도움이 될 것이다.
내부함수에서 찾을 수 없는 변수이면 상단의 외부함수로간다.

렉시컬 스코프 환경으로 표현

// global environment
environmentRecord{
	a : 1,
	innerFunc : function
	//outer environment reference:null
}


// innerFunc environment
environmentRecord{
	b : 2
	//outer environment reference: global environment
}

이런식으로 환경이 엮여서 체인을 이루는 모습에서 스코프체인이라 붙여졌다.

클로저

렉시컬환경을 유의하면서 클로져내용도 보면 이해하는데 도움이 많이 될 것 같다.

클로저란?

클로저에 대한 정의는 많은 것 같다.
외부함수와 내부함수로 이야기도 할 수 있지만
내가 본 클로저의 정의는 함수와 함수의 둘러싼 환경이 가장 클로저의 정의에 가까운 것 같다.

함수와 그 안의 내부함수를 선언할시 결정되어진 함수의 스코프가 정해지면 바로 클로저가 되는 것이다.
그러면 외부함수, 내부함수선언하면 모두 클로저가 될까?
클로저가 된다는건 위에 말한 정의처럼 환경도 필요하다.

function outer() {
    var color = ‘red’;
    function inner() {
        console.log(color);
    }
    inner();
}
outer();

이러면 클로저일까?
클로저가 아니다. 외부함수 안에 선언한 내부함수가 실행된 것일 뿐이다.

그럼 다른식으로 표현한 소스를 보자.

var color = ‘red’;
Function outer(){
	var color = ‘blue’;
	function inner() {
		console.log(color);
	}
	return inner;
}

var closer = outer();
closer(); 

보기엔 그냥 위와 별반 차이없고 당연한 소스같다.
그치만 return 부분을 보게되면 조금 다르게 느껴질 것 같다.
실제로 outercloser에 담아서 실행시키면
return 안의 inner이 나온다. 결국 inner 을 부르는 나오는 것이 된다.
외부함수에서 내부함수를 실행하는 것이 아닌 inner 자체가 나오는 것이다.
그러면서 자연스레 outer 부분은 넘어가고 inner 이 튀어나오는 것 같지만 inner에서
colorblue 를 가지고 온다.

어떻게 이럴 수 있냐하면 위의 렉시컬스코프 환경을 생각해보면

// global environment
environmentRecord{
	color : red,
	outer : function
	//outer environment reference:null
}


// outer environment
environmentRecord{
	color : blue
	inner : function
	//outer environment reference: global environment
}

// inner environment
environmentRecord{
	//outer environment reference: outer environment
}

선언을 하면 변수가 바로 정해지기에 return을 하더라도 color를 부르는 것이다.
가져오는게 아닌 선언하자마자 colorblue인 것이다.
함수가 선언됐을 때의 환경을 보여주고 있다

유명한 반복문 클로저

function count() {
    var i;
    for (i = 1; i < 10; i += 1) {
        setTimeout(function timer() {
            console.log(i);
        }, i*1000);
    }
}
count();

가장 유명한 반목문 클로저이다.
이걸 만들때는 나는 i가 1,2,3,4,5,...,9 를 1초마다 한번씩 찍기를 원했지만 이상하게도
10이 9번 출력되었다.

setTimeout이 실행되기전에 i는 10이 되어져버리고 10이 9번찍히는 것이다.

이 문제를 해결하기 위해서 2가지 방법이 있다.
첫번째는 i가 i++가 될 때 하나씩 따로 저장해주는 방식
두번째는 ES6를 활용하여 블록스코프를 이용하는 방식이다.

첫번째 방식

function count() {
	var i;
	for (i = 1; i < 10; i += 1) {
		(function(j){
			setTimeout(function timer() {
				console.log(j);
			}, j*1000)
		})(i);
	}
}
count();

새로운 스코프를 추가하여서 i를 j에 따로 담는 방식이다.

두번째 방식

function count() {
	'use strict'
	for (let i = 1; i < 10; i += 1) {
		setTimeout(function timer() {
			console.log(i);
		}, i*1000)
	}
}
count();

블록 스코프를 이용하여서 해결하는 방법이다.

뒷이야기

클로저를 보기전에 렉시컬환경과 콜스택에 대해서 먼저 해야했나 싶지만
일단 개념만이라도 인지하고 렉시컬과 콜스택에 대해서 보면 조금더 깊게 이해되고 그냥 아는 것에서 뇌에 스며드는 단계가 될 것 같다.

profile
포기하지말고 천천히...

0개의 댓글