시작하며
우리는 이전 포스트 에서 this의 세부적인 움직임을 알아보았다.
이번 포스트에서는 -Scope Chain 을 통해 this의 올바른 참조법을 알아본다.
Closure가 어떻게 동작하는지 EC 와 LE을 통해 구체적으로 뜯어본다.
-reference
- http://hanmomhanda.github.io/2016/01/16/JavaScript-%EC%8B%9D%EB%B3%84%EC%9E%90-%EC%B0%BE%EA%B8%B0-%EB%8C%80%EB%AA%A8%ED%97%98/
흔히 스코프 체인이라고 하는 것은 ES3에서는 스펙에 정식으로 존재했던 용어인데, ES6에서는 스코프 체인이라는 용어는 존재하지 않는다. 정식 용어라고까지 할 수는 없겠지만, ES6에서는 스코프 체인 대신에 Lexical nesting이라는 표현을 쓴다. 체인 보다는 중첩에 더 의미를 두려는 것 같다. 어차피 정식 용어가 없으므로 여기서는 편의상 스코프 체인이라는 용어를 그대로 쓰기로 한다.
우선 예제를 먼저 보자.
var wow = "wow1"; // 전역변수
var testObject = {
thisMember: "test",
}
testObject.foo = function (a, b) {
var value1 = a;
var value2 = b;
var user = 'kim';
var wow = "wow2"; // foo함수의 지역변수
bar();
function bar() {
var result = this.value1 + this.value2;
console.log(result);
console.log(wow); // ->무슨 값이 나올까????
}
}
저번 예제와 달라진점이 하나 있다. wow 변수의 등장이다.
전역변수에 하나 foo 함수의 지역변수로 동일하게 wow를 지정하였다.
문제. 과연 bar 함수에서 콘솔로 찍으려는 wow는 wow1 이 될까, wow2가 될까?
답은 wow2 이다. 저번 포스트를 보고 wow1인지 wow2인지 헷갈린사람들도 있을 것이다.
Scope Chain 은 말그대로 Scope의 체인이다. 우리가 일반적으로 말하는 Scope는 무엇을 의미하는 것일까? 이전 포스트에서 봤던 Executon Context이다. 자바스크립트는 기본적으로 함수 블록을 스코프로 잡는다. 우리는 함수가 실행가능한 코드이며 Execution Context임을 알고있다.
이 두가지 사실로 유추해볼 수 있는건 Scope와 EC는 어느정도 일치한다는 것이다. (완전히 동일한 것은 아닐 것 같다... 이점은 좀더 공부해봐야겠다 )
barExecutionContext = {
LexicalEnvironment: [barLexicalEnvironment],
VariableEnvironment: [barLexicalEnvironment],
ThisBinding: [window]
}
barLexicalEnvironment = {
environment_record: {
DeclarativeEnvironmentRecord: {
result : NaN // === (undefined + undefined)
},
ObjectEnvironmentRecord : {
}
},
outer_environment_referance: fooExecutionContext
}
bar 함수의 EC이다.
중간에 추가적으로 검색한 포스트를 통해 알게 된것인데, VariableEnvironment 와 ObjectEnvironmentRecored는 with 문이 쓰일때 사용된다고 한다. 하지만 ES6와서는
with 문이 deprecated 되었으므로 신경쓰지 않아도 될것 같다.
outer _environment_reference는 함수가 중첩되었을 때, 자신을 감싸고 있는 함수블록을 바라본다고 생각하면 된다. foo 함수를 바라보기때문에, bar의 wow 변수는 this가 window 객체로 붙어있음에도 불구하고 foo 함수의 wow 를 먼저 찾게 되는 것이다.
이러한 점을 이용해서 foo 함수에 var that = this 를 통해 bar함수가 정확하게 this를 참조 할 수 있도록 만들 수 있다.
var testObject = {
thisMember: "test",
}
testObject.foo = function (a, b) {
var value1 = a;
var value2 = b;
var user = 'kim';
var that = this;
bar();
function bar() {
var result = that.value1 + that.value2;
console.log(result);
}
}
이런식으로 작성하면 bar 함수에서 that을 찾기 위해 outer 환경을 찾아보게 될것이며, foo 의 this( testObject ) 값을 가진 that을 바라보게 될것이다.
-Closure
클로저 또한 EC에 의해 쉽게 설명될 수 있다. 함수객체에는 [[Scope]] 라는 속성이 할당되어있다.
이 [[Scope]] 속성이 EC와 연관있다면 ... 예를 보면 이해해보자.
클로저는 함수의 스코프가 종료되었음에도 그 환경의 변수값을 이용할 수 있다는 것.
코드를 보자. ('use strict')가 적용된 상태이다. 우리가 관심있는 것은 emit()이다. emit을 실행시키면 콘솔에 3이 찍히는 이유는 무엇일까. 변수 emit이 받은 것은 emitResult의 함수이다. 즉 result를 모른다.
그림 1
그림 2
그림 1은 add 함수의 실행환경, 그림 13은 emit 함수의 실행환경이다.
add 함수가 실행될때에 따로 명시된 객체가 없기때문에 this는 null로 바인딩 되어있다.
연산을 끝낸후 add 함수 환경 record 에는 result가 저장된다.
그림 2는 emit 함수의 실행환경이다. 마찬가지로 실행될때 명시된 객체가 없기때문에 this는 null이다. emit의 근간이 되는 emitResult 함수는 애초에 add 함수의 내부함수로써 외부환경참조로 add의 실행환경을 삼고있다.
스코프체인 관점에서 봤을때, emitResult함수는 자신의 내부에서 result에 대한 값을 찾으려 든다. 그 다음에 찾게되는 것은 외부환경참조로 삼았던 add 스코프 내부이기 때문에 그 값을 사용할 수가 있는 것이다. add 함수는 이미 스코프가 종료되어 사라졌지만(실행환경의 종료) LexicalEnvironment가 남아서 emit 함수로 전해졌기때문에 참조할 수 있는 것이다.
다음 포스트에서는 함수가 호출되는 여러가지 상황을 살펴보고,
실제로 예제들을 보면서 어떻게 this가 바인딩 되는지 확인해보겠다.
댓글
댓글 쓰기