저번 포스팅에서 우리는 자바스크립트가 프로그램을 평가하고 실행하는데 있어서 어떻게 동작하는지에 대해 살펴보았습니다.
이번 포스팅에서도 이어서 풀어보도록 하겠습니다.
this
값- 식별자의 결정: 유효 범위 체인
- 가비지 컬렉션
this
값에 대하여함수가 호출되어 실행될 때 this
의 값이 결정됩니다. 이 값은 함수가 호출되었을 때 그 함수가 속해있던 객체의 참조이며 실행 문맥에서의 ThisBinding
컴포넌트가 참조하는 객체가 됩니다.
예를 들어 아래와 같은 코드가 있다고 가정해 보겠습니다.
const jane = {
name: "Jane",
sayHello: function() {
console.log(`Hello! ${this.name}.`);
}
};
이 코드를 아래와 같이 실행해보면 예상했던 결과값이 나옵니다.
jane.sayHello(); // → Hello! Jane.
위의 코드에서는 함수를 jain.sayHello
라는 이름으로 참조하여 실행하고 있으며 jain.sayHello
가 속해있는 객체Object는 jain이라는 변수가 됩니다.
다시 말해서 sayHello
메서드가 호출되는 실행 문맥의 디스 바인딩 컴포넌트가 가리키는 객체는 jain
이 됩니다. 따라서 this
값은 jain
을 가리키므로 this.name
은 "Jane"
이 됩니다. jain.sayHello
는 함수의 참조이므로 이 값을 다른 객체 프로퍼티에 대입하여 사용할 수 있습니다.
const alex = { name: "Alex" };
alex.sayHello = jain.sayHello;
alex.sayHello(); // → Hello! Alex
실행 문맥의 ThisBindingComponent
가 가리키는 객체가 jain
에서 alex
가 되었습니다. 따라서 this
는 alex
객체를 가리키고 this.name
은 alex
가 되는 것입니다.
여기에서 알 수 있는 것은 함수는 객체에 묶여 있는 것이 아니라 객체 자체가 함수를 참조한다는 사실입니다.
일반적으로는 여러개의 객체가 함수 하나를 가리키는데 호출될 때의 상황에 따라서 this
가 가리키는 값은 바뀝니다.
실행 문맥의 thisBindingComponent
가 가리키는 객체가 무엇인지에 따라 this
가 가리키는 객체가 바뀝니다.
- 코드 최상위 레벨의
this
코드의 최상위 레벨이 있는this
는 전역 객체를 가리킵니다. 실행 문맥이 초기화 될 때 그 안의ThisBindingComponent
가 전역 환경을 가리키도록 초기화 됩니다.console.log(this); // Window
- 이벤트 처리기 안에 있는
this
이벤트 처리기 안에 있는this
는 이벤트가 발생한 요소 객체(이벤트 처리기가 등록된 객체)를 가리킵니다.
- 생성자 함수 내부의
this
사용자가 정의한 생성자 함수 안에 있는this
는 그 생성자로 생성한 객체를 가리킵니다.
- 생성자의
prototype
메서드 안에 있는this
생성자의prototype
메서드 안에 있는this
는 해당 생성자로 생성한 객체를 가리킵니다.
- 직접 호출한 함수 안에 있는
this
함수를 최상위 레벨 코드에서 호출하면 함수 안에 있는this
가 전역 객체를 가리키게 되고 이것은f();
코드 앞에 객체가 없기 때문에thisBindingComponent
가 전역 객체를 가리킵니다.function f() { console.log(this); } // 함수 f를 호출할 때 함수 앞에 아무것도 붙이지 않으면, // 함수 f가 속한 실행 문맥의 thisBindingComponent가 globalObject를 가리킵니다. f(); // Window
그러나 함수 앞에 어떤 객체를 붙여서 호출하게 되면
thisBindingComponent
가 해당 객체를 가리키게 됩니다.
- 마지막으로
apply
와call
메서드로 호출한 함수 안에 있는this
입니다.
함수 객체의apply
와call
메서드를 사용하면 함수를 호출할 때this
가 가리키는 객체를 바꿀 수 있습니다.
그 함수 객체가 실행되는 실행 문맥의thisBindingComponent
가 가리키는 객체를 명시적으로 설정할 수 있게 됩니다.
몇가지 용어에 대해 정의해 보겠습니다.
- 속박 변수 : 함수의 인수와 지역변수를 나타내는 말입니다.
- 자유 변수 : 속박변수 이외의 변수
- 닫힌 함수 : 속박 변수만 포함한 함수
- 열린 함수 : 자유 변수를 가지고 있는 함수
예를 들어,
const a = "A";
function myFunction() {
const b = "B";
function f() {
const c = "C";
console.log(a+b+c);
}
f();
}
myFunction();
위와 같은 코드가 있다면 c
는 속박 변수, a
와 b
가 자유 변수가 됩니다. 또한 myFunction
은 닫힌 함수, f
는 열린 함수가 됩니다.
자 그렇다면 자바스크립트에서는 이 변수들이 선언된 위치를 어떻게 확인하고 찾아내고 계산할까요?
- 속박 변수
c
의 의사코드g_LexicalEnvironment: { DeclarativeEnvironmentRecord: { c: "C" }, OuterLexicalEnvironmentReference: f_LexicalEnvironment }
변수 c
는 함수 f
안에서 선언된 속박 변수이므로 함수 f
의 선언적 환경 레코드 안에서 찾을 수 있습니다.
- 자유 변수
b
의 의사코드f_LexicalEnvironment: { DeclarativeEnvironmentRecord: { c: "C" }, OuterLexicalEnvironmentReference: myFunction_LexicalEnvironment } // ... 아래로 연결 myFunction_LexicalEnvironment: { DeclarativeEnvironmentRecord: { b: "B" }, OuterLexicalEnvironmentReference: global_LexicalEnvironment }
변수 b
는 함수 myFunction
안에서 선언되었으며 함수 f
가 속한 선언적 환경 레코드에서는 찾을 수 없습니다. 그래서 실행 문맥 속에 있는 외부 렉시컬 환경 참조를 따라 함수 f
를 호출한 함수인 myFunction
이 속한 실행 문맥의 선언적 환경 레코드를 검색합니다. 변수 b
는 myFunction
에 속해있으므로 식별자를 이것으로 결정하게 됩니다.
함수myFunction
이 호출되면 그 안의 환경 레코드에 변수 b
가 프로퍼티로 추가되고 이후에 함수 f
의 선언문이 평가되어 환경 레코드가 생성됩니다.
이 때, 함수 f
의 객체가 myFunction
의 렉시컬 환경을 참조합니다.
이 참조를 통해 f
안에서 변수 b
를 사용할 수 있게 되는데, 이러한 과정을 거쳐 함수 f
를 실행하는 시점에 변수 b
의 위치를 외부 렉시컬 환경 참조를 따라 검색할 수 있게 됩니다.
- 자유변수
a
f_LexicalEnvironment: { DeclarativeEnvironmentRecord: { c: "C" }, OuterLexicalEnvironmentReference: myFunction_LexicalEnvironment } // ... 아래로 연결 myFunction_LexicalEnvironment: { DeclarativeEnvironmentRecord: { b: "B" }, OuterLexicalEnvironmentReference: global_LexicalEnvironment } // ... 아래로 연결 global_LexicalEnvironment: { ObjectEnvironmentRecord: { bindObject: { a: "A" } }, OuterLexicalEnvironmentReference: null }
변수 a
는 함수 f
의 바깥에서 선언된 자유변수입니다. 이러한 경우 a
를 f
의 선언적 환경 레코드 안에서 찾을 수 없기 때문에 실행 문맥 속에 있는 외부 렉시컬 환경 참조를 따라 함수 f
를 호출한 함수인 myFunction
의 선언적 환경 레코드를 검색합니다. 그러나 이 안에서도 찾을 수 없기 때문에 한 단계 더 상위 레벨에서 검색하게 됩니다.
이제 이것을 식별자로 결정하게 됩니다.
이처럼 식별자를 결정할 때에는 현재의 유효범위 안에 없는 식별자를 찾는데 있어 바깥 범위로 호출자의 렉시컬 환경에 속한 외부 렉시컬 환경 참조를 찾아 따라가는 방식을 취하게 됩니다. 이러한 연결고리를 기존에는 Scope Chain이라고 불렀습니다.
프로그램에서 객체를 생성하면 메모리 공간이 동적으로 확보되는데 사용하지 않는 객체의 메모리 영역을 가비지 컬렉터가 해제해 줍니다.
이러한 mechanism을 가비지 컬렉션이라고 하고, 이 때 사용하지 않는 객체라고 하는 것은 다른 객체의 프로퍼티와 변수가 참조하지 않는 객체를 뜻합니다.
예를 들어 임의의 변수 q
가 다음과 같은 객체를 갖고 있다고 가정해봅시다.
const q = { x: 1, y: 2 };
console.log(q); // Object {x=1, y=2}
이렇게 출력이 되는 것을 확인하고 다시 q
에 null
을 선언하여 참조값을 없애봅시다.
q = null;
console.log(q); // null
이 코드가 실행되면 객체는 어떠한 변수에서도 참조하지 않고 가비지 컬렉터가 이를 메모리에서 해제하게 됩니다.
포스팅 마치겠습니다.
이소 히로시, 모던 자바스크립트 입문(길벗), 281-288.