이 기사는 Dmitry A. Soshnikov에 의해 작성된 JavaScript. The Core. 에 대한 내용을 허가를 받아 번역하였습니다.
The Core
웹 상에서는 자바스크립트에 대한 수 많은 레퍼런스와 기사들이 있으나 ECMAScript 사양에 준하여 자세히 설명하고 있는 문서는 그리 많지 않습니다. 이 문서가 자바스크립트 본연의 기술을 이해하기 위해 필히 학습을 권장합니다. 뿐만 아니라 Dmitry 블로그에는 ECMAScript 사양에 대해서 굉장히 자세히 분석하여 포스팅을 하고 있으니 관심을 갖고 보기를 권장합니다.
소개
이 메모는 ECMA-262-3 in detail 시리즈에서 얻은 지식을 정리한 것이며, 또한 설명도 되어 있습니다. 각각의 해당 ES3 시리즈 각 챕터에 대한 참조가 포함되어 있기 때문에, 관심이 있으면 자세한 내용은 그곳을 참조하십시오.
이 글을 경험있는 프로그래머 혹은 전문가를 대상으로 합니다.
그러면 서둘러, ECMAScript 의 기본이 되는 객체의 개념을 살펴보도록 하겠습니다.
객체
ECMAScript 는 객체를 중심으로 동작하는 높은 레벨의 추상화된 객체 지향 언어입니다. 물론 기본값이 존재하지만, 그들이 필요에 따라 객체로 변환할 수 있습니다.
“객체”는 속성과 하나의 프로토타입 객체를 가진 집합이다. 프로토타입은 다시 “객체”이거나 null 값을 취한다.
여기에서는 간단한 예제를 봅시다. 객체의 prototype은 내부 [[Prototype]] 속성을 참조하지만 아래의 그림에서는 __<internal-property>__형태의 표기법을 사용할 것입니다. 그리고 프로토타입 객체에 대해서는 prototype라고 표기합니다. 이것은 표준 사양은 아니지만 SpiderMonkey 등 일부 ECMAScript 엔진에서 실제로 구현되는 속성의 이름이기도 합니다.
코드를 살펴봅니다.
x : 10,
y : 20
};
이 경우 “foo” 객체는 두개의 명시적인 속성과 하나의 암시적인 속성 __proto__(“foo” 객체의 프로토타입 참조)를 가지게 됩니다.

이 프로토타입은 도대체 무엇을 위해서 있는가? 이 질문에 대한 답은 프로토타입 체인 에서 다룰 것입니다.
프로토타입 체인
프로토타입 객체는 자기 자신 또는 프로토타입 객체를 갖는 하나의 단순한 객체입니다. 만약 어떤 프로토타입 객체가 prototype 에 null 이 아닌 참조가 있는 경우는 또한 마찬가지입니다. 이것이 프로토타입 체인입니다.
“프로토타입 체인”은 상속 및 공유 속성을 구현하는데 이용되는 객체의 유한(finite) 사슬입니다.
예를 들어 대부분 같고 약간 다른 두 개의 객체가 있다고 해봅니다. 물론 잘 디자인 되었다면 비슷한 기능을 각 객체에 중복해서 구현하지 않고 재사용 할 수 있도록 했을 것입니다. 클래스를 기본으로 한 시스템에서는 이 코드 재사용 방법을 클래스기반 상속이라고 부를 수 있습니다.
즉 클래스 A에 중복되는 기능을 갖도록 하고 클래스 B와 C 에서는 A의 기능을 상속받고 추가적인 변경사항만 각각에서 구현할 것입니다.
ECMAScript 는 클래스 개념은 없습니다. 하지만 코드의 재사용 방법에 큰 차이점은 없고(오히려 어떤 면에서는 클래스 구조보다 유연한 구조를 갖음) 바로 프로토타입 체인을 통해 이 방법을 실현하고 있는 것입니다. 이런 유형의 상속 방법을 위임 기반의 상속이라고 부를 수 있습니다.(좀더 ECMAScript 스럽게는 프로토타입 기반 상속 이라고 할 수 있습니다.)
클래스 기반의 예제에서 “A”, “B”, “C” 클래스와 마찬가지로 ECMAScript 의 “a”, “b”, “c” 객체를 생성합니다. 즉, 객체 “a” 에 “b”, “c” 의 공통 부분을 가지고 “b”, “c” 는 각각의 추가적인 속성과 메소드를 가지도록 구현합니다.
x : 10,
calculate : function(z){
return this.x + this.y + z
}
};
var b = {
y : 20,
__proto__ : a
};
var c = {
y : 30,
__proto__ : a
};
//상속된 메소드를 호출합니다.
b.calculrate(30); //60
c.calculrate(40); //80
무척 간단하죠? 여기에서는 “b” 및 “c” 객체에서 “a” 에 정의된 calculrate 메소드에 접근했습니다. 이것은 프로토타입 체인을 사용하여 구현되고 있다는 것을 알 수 있습니다. 규칙은 매우 간단합니다. 만약 속성 또는 메소드가 객체 자신에 없으면 다음의 프로토타입 체인에서 찾으려고 합니다. 또한 프로토타입에서도 해당 속성이 발견되지 않으면 프로토타입의 프로토타입이 다음 대상이 됩니다. 이것을 프로토타입 체인 전체에 걸쳐 반복하게 됩니다.(클래스 기반의 상속에서 상속되는 메소드를 클래스 체인에서 찾아 가는 것과 같음) 그래서 처음 발견된 같은 이름의 속성 혹은 메소드가 사용도는 것입니다. 상속된 속성 입니다. 그러나 만약 프로토타입 체인 전체를 탐색해도 같은 이름의 속성이 발견되지 않는 경우는 undefined 값이 반환됩니다.
여기에서는 상속된 메소드에서 사용되는 this 객체는 메소드가 정의될 당시의 객체가 아니라 “원래”의 객체임을 주의해야 합니다. 코드상에서는 this.x는 프로토타입 체인의 구조에 따라 “a” 객체의 것이 사용되는 반면 this.y 는 각각 “b”, “c” 객체의 것이 사용됩니다.
비록 지정된 객체에 명시적으로 프로토타입이 지정되지 않아도 __proto__는 Object.prototype 을 기본값으로 취하고 있게 됩니다. 물론 Object.prototype객체도 __proto__ 가 있고 이것은 프로토타입 체인의 끝이며 null 이 할당되어 있습니다.
다음 그림이 “a”, “b”, “c” 객체의 상속 구조입니다.

그런데 어떠한 경우에 동일 또는 유사한 상태 구조를 가지면서 서로 다른 상태값을 갖게하고 싶은 경우가 있습니다. 이 경우에는 객체를 특정 형태로 생성할 수 있는 생성자 함수를 사용하게 됩니다.
생성자
특정 형태로 객체를 생성하는 것 이외에도 생성자 함수에는 편리한 기능이 있습니다. 새로 생성된 객체에 자동으로 프로토타입 객체를 설정해 주는 것입니다. 이 프로토타입 객체는 ConstructorFunction.prototype 속성에 저장됩니다.
예를 들어 위의 “b”, “c” 객체를 생성자 함수를 사용하여 다시 작성할 수 있습니다. 이 경우 “a” 객체(프로토타입)의 역할은 Foo.prototype에 해당합니다.
function Foo(y){
//특정 형태의 객체를 생성합니다.
//생성된 객체는 자신의 "y" 속성을 갖게 됩니다.
this.y = y;
}
//동시에 "Foo.prototype" 은 새로 생성되는 객체의 프로토타입에 대한 참조입니다.
//즉 여기에 위의 예처럼 공유와 상속 메소드를 정의할 수 있습니다.
//상속되는 속성 "x"
Foo.prototype.x = 10;
//상속되는 메소드 "calculrate"
Foo.prototype.calculrate = function(z){
return this.x + this.y + z;
}
//"Foo" 는 "패턴"을 이용하여 "b", "c" 객체를 생성합니다.
var b = new Foo(20);
var c = new Foo(30);
//상속된 메소드를 호출합니다.
b.calculrate(30); //60
c.calculrate(40); //80
//예상대로 속성을 볼 수 있는지 표시합니다.
console.log(
b.__proto__ === Foo.prototype, //true
c.__proto__ === Foo.prototype, //true
//"Foo.prototype" 도 마찬가지로 특별한 프로퍼티 "constructor" 를 가지고 있습니다. (Foo.prototype.constructor)
//이것은 생성자 함수 자체에 대한 참조입니다.
//인스턴스 "b" 및 "c" 는 위임되어 이 속성을 참조하여 자신의 생성자를 확인할 수 있습니다.
b.constructor === Foo, //true
c.constructor === Foo, //true
Foo.prototype.constructor === Foo, //true
b.calculrate === b.__proto__.calculrate, //true
b.__proto__.calculrate === Foo.prototype.calculrate //true
);
위의 코드를 객체간의 관계도로 그려본 그림입니다.

이 그름은 모든 객체가 프로토타입 객체를 가지는 것을 보여줍니다. 생성자 함수 “Foo” 자신도 Function.prototype 을 가리키는 __proto__를 갖습니다. 또한 Function.prototype 의 __proto__를 통해 Object.prototype에 대한 참조합니다. 다시 말하지만 Foo.prototype은 간단히 “Foo”의 명시적인 속성의 하나이며 “b” 및 “c” 객체의 프로토타입에 대한 참조가 되고 있는 것입니다.
형식적으로 “클래스” 컨셉을 다시 보면 (조금전 Foo는 다른 계층에서 추상화하고 “클래스”화 한것입니다.) 생성자 함수와 프로토타입 객체의 조합을 “클래스”라고 부를 수 있을 지 모릅니다. 실제 예를 들어보면 Python 의 일급 클래스(first class) “동적 클래스” 라는 개념은 속성, 메소드에 대해 ECMAScript 와 동일한 구현되었습니다. 이 관점에서 이야기하면 Python 의 클래스는 ECMAScript 에서 사용되고 있는 위임 기반의 상속 모델로 구문적 설탕(Syntactic Sugar)이라 볼 수 있습니다.
‘Syntactic Sugar’ 란? 언어나 다른 형식주의에 첨가되어 인간에게 더욱 달콤하게 다가갈 수 있게 하는 특징적 기능을 말한다. 예를 들어 자바스크립트에 클래스 개념을 갖는 기능은 없지만 jQuery 와 같은 라이브러리에서는 클래스처럼 사용할 수 있도록 Class 함수를 Syntactic Sugar 라고 할 수 있다.
이 주제에 대한 좀더 완벽한 설명은 ES3 시리즈 7장에서 볼 수 있습니다. 두 가지 Chapter 7.1 OOP The general Theroy 에서는 ECMAScript 와 비교하여 다양한 OOP 이론과 스타일을 이야기하고, Chapter 7.2 OOP ECMAScript implementation 전부를 ECMAScript OOP 에 대해서만 이야기하고 있습니다.
지금까지 기본적인 객체에 대한 것을 이해할 수 있었습니다. 다음 ECMAScript 에서 프로그램의 실행이 어떻게 구현되어 있는지 살펴 봅 예정입니다. 이것은 “실행 컨텍스트” 라는 것입니다. 이것도 물론 객체로 표시할 수 있습니다. 네 맞습니다. ECMAScript 에서는 거의 모든 동작이 객체라는 개념 아래 행해지고 있기 때문입니다.
실행 컨텍스트 스택
ECMAScript 의 코드는 3개의 유형으로 나눌 수 있습니다. “글로벌 코드”, “함수 코드”, “eval 코드” 입니다. 각각의 코드는 각각의 실행 컨텍스트에 의해 평가됩니다. ECMAScript 는 하나의 글로벌 컨텍스트와 많은 함수 또는 eval 실행 컨텍스트의 인스턴스가 존재하게 됩니다. 함수를 호출할 때마다 함수 실행 컨텍스트에 들어가고 “함수코드”로 평가됩니다. eval 을 실행할 때마다 eval 실행 컨텍스트에 들어가 그 코드를 “eval 코드” 로 평가하는 것입니다.
여기에서는 어느 한 함수가 무한에 컨텍스트를 생성할 수 있다는 것에 주의해야 합니다. 왜냐하면 함수 호출이라는 것은 함수 자체에 대한 재귀적인 호출을 포함하여 내부에서 새로운 함수를 호출하여 새로운 “컨텍스트 상태” 를 가진 실행 컨텍스트를 무한으로 생성할 수 있기 때문입니다.
//같은 함수를 호출하지만 호출마다 각각 3개 다른 컨텍스트 상태(여기서는 모두 다른 bar)를 가진
//실행 컨텍스를 생성하고 있습니다.
}
foo(10);
foo(20);
foo(30);
실행 컨텍스트에서는 다른 컨텍스트를 시작할 수 있습니다. 예를 들어 함수는 또 다른 함수를 호출할 수 있으며 글로벌 컨텍스트는 전역 함수를 호출할 수 있습니다. 로직상으론 스택처럼 구현되어지고 이를 실행 컨텍스트 스택이라고 합니다.
새로운 컨텍스트를 시작하는 측의 컨텍스트를 caller, 시작되는 측의 컨텍스트를 callee 라고 합니다. 글로벌 컨텍스트에서 함수를 호출하고 그 함수가 또한 내부에서 함수를 호출할 수 있도록 callee는 또한 동시에 새로운 callee 와 caller 가 될 수 있습니다.
caller 가 callee 를 시작하면 caller 는 실행을 일시 중단하고 제어 흐름을 callee 에 전달합니다. 즉, callee 는 스택에 쌓이고 현재 실행 컨텍스트가 됩니다.callee 의 컨텍스트가 종료되면 제어는 다시 caller 로 돌아갑니다. 그리하여 caller 의 컨텍스트 평가가 지속되고 혹은 새로운 컨텍스트를 시작, 종료하면서 마지막까지 평가를 진행합니다. callee 는 단순히 return 또는 예외로 종료할 수 있습니다. throw 를 수행했더라도 caller 에서 catch 되지 않는 예외는 스택의 나머지 컨텍스트를 종료(스택에서 pop) 시킵니다.
즉, ECMAScript 는 모든 프로그램 실행은 실행 컨텍스트(Execution Context 이하 EC)의 스택으로 표시하고 있는 것입니다. 물론 스택의 최상이 활성 컨텍스트입니다.

프로그램이 시작될 때 스택의 가장 아래 컨텍스트 요소인 글로벌 컨텍스트로 진입합니다. 글로벌 코드는 먼저 다양한 초기화 및 객체, 함수를 생성합니다. 그리고 글로벌 컨텍스트의 실행 동안 코드는 또 다른 (생성된) 함수를 시작하고 각각의 실행 컨텍스트로 들어갑니다. 즉, 스택에 새로운 컨텍스트 요소가 push 됩니다. 그렇게 전역코드의 실행이 완료되면 런타임은 사용자의 마우스 클릭등의 이벤트 발생을 기다리는 상태로 이동합니다. 이벤트가 발생 즉시 함수가 시작되며 새로운 컨텍스트로 들어갑니다.
이 그림은 “Global EC” 즉 글로벌 컨텍스트에서 “EC1″ 이라는 함수 컨텍스트에 들어가고 빠져 나갈때 스택의 변화를 나타낸 것입니다.

이렇게 ECMAScript 의 런타임은 코드 실행을 관리하고 있습니다.
ECMAScript 의 실행 컨텍스트에 대한 자세한 내용은 Chapter 1. Execution context 에서 볼 수 있습니다. 앞서 말했던것 처럼 스택상의 모든 실행 컨텍스트는 객체로 표현됩니다. 그러면 이 객체의 구조와 코드가 샐행되기 위해서 어떤 상태(속성)가 필요한지 살펴보기로 합니다.
실행 컨텍스트
실행 컨텍스트는 추상적으로 간단한 객체로 나타낼 수 있습니다. 모든 실행 컨텍스트는 그 컨텍스트가 속하는 코드의 실행 상태를 추적하는 속성(컨텍스트의 상태라 불리는)을 가지고 있습니다. 다음의 그림을 보세요.

이 세가지 필수 속성(Variable Object/변수 객체, Scope Chain/범위 체인, thisValue/this 값) 외에도 구현에 따라서 다른 상태를 유지하는 경우가 있습니다.
여기 중요한 3가지 속성에 대해 자세한 내용을 살펴보도록 합시다.
변수 객체
변수 객체는 실행 컨텍스트에 관련된 데이터의 “범위”이고, 컨텍스트에서 정의되는 변수와 함수를 유지하는 특별한 객체입니다.
함수 정의가 아닌 함수식(Function Expression)은 변수객체에 포함되지 않습니다.
변수 객체는 추상적인 개념이고 역할입니다. 다른 컨텍스트에서 실제로는 다른 객체가 변수 객체 역할을 합니다. 예를 들어 글로벌 컨텍스트에서 변수 객체는 글로벌 객체 그 자체입니다.(역주: 브라우저 구현상으로 window 객체를 말함) 우리는 글로벌 오브젝트의 속성을 통해 전역 변수에 접근하는 것이 가능하게 되어 있습니다.
글로벌 실행 컨텍스트의 다음 예제를 보세요.
function bar(){} //함수 정의(function declaration, FD)
(function baz(){}); //함수 식(function expression, FE)
console.log(
this.foo == foo, //true
window.bar == bar //true
);
console.log(baz); //ReferenceError "baz" is not defined
즉, 이 글로벌 컨텍스트의 변수 객체(VO)는 다음과 같은 속성을 가진다고 할 수 있습니다.

함수식인 함수 “baz” 는 변수 객체에 포함되지 않습니다. 그래서 “baz” 함수의 외부 접근 시에 ReferenceError 가 발생합니다.
주의해야 할 것은 C와 C++ 등 다른 언어와 달리 ECMAScript 에서는 함수만이 새로운 범위를 생성합니다. 변수와 함수의 범위 내에서 정의되는 내부 함수는 함수의 외부에서 직접 액세스할 수 없으며, 전역 변수 객체를 변경 시킬 수도 없습니다.
eval 을 호출하면 새로운 eval 실행 컨텍스트에 들어가게 됩니다. 그러나 eval 실행 컨텍스트는 변수 객체로 전역 변수 객체 또는 eval 을 호출한 함수 즉,caller 실행 컨텍스트의 변수 객체를 그대로 사용합니다.
그럼 함수 컨텍스트의 경우 변수 객체는 어떻게 될까요? 함수 컨텍스트에서는 변수 객체의 역할로 활성 객체(Activation Object)가 주어집니다.
활성 객체
함수가 caller 실행 컨텍스트에 의해 활성(호출)될 때 활성 객체라는 특별한 객체가 생성됩니다. 활성 객체는 함수의 인수와 특별한 arguments 객체(임시 인수 맵 형태이지만 인덱스 접근도 가능한 객체)도 속성으로 설정되어 있습니다. 이후 이 활성 객체는 함수 컨텍스트에서 변수 객체로 사용됩니다.
간단하게 말하면 함수의 변수 객체는 단순 변수 객체와 같지만 변수와 함수 게다가 형식적 매개변수 및 인수 객체를 저장하고 활성 객체라고 부릅니다.
다음 예제를 보세요.
var z = 30;
function bar(){} //FD(함수의 정의)
(function baz(){}); //FE(함수 표현식)
}
foo(10, 20);
이 경우는 그림과 같이 “foo” 함수 컨텍스트의 활성 객체가 생성됩니다.

다시 말하지만 함수식 “baz” 는 변수 객체(이 경우 활성 객체)에 포함되지 않습니다.
기타 다양한 케이스는 (변수와 함수정의 “hoisting”)에 대한 보다 완벽한 설명은 Chapter 2. Variable object 를 참조하십시오.
자 그럼 다음 단계로 진행합니다. 아시다시피 ECMAScript 에서는 내부 함수에서 부모 함수 범위의 변수와 글로벌 컨텍스트의 변수에 접근할 수 있습니다. 지금까지 살펴본 프로토타입 체인과 마찬가지로 컨텍스트의 범위 객체로 소위 말하는 “범위 객체”를 변수 객체로 명명합니다.
범위 체인
범위 체인은 컨텍스트의 코드에 있는 식별자를 검색하기 위한 객체들의 목록입니다.
이 규칙은 또 간단히 말하면 프로토타입 체인과도 같습니다. 변수가 범위에서 발견되지 않을 경우 부모 변수 객체로 검색이 이어집니다.
컨텍스트 관점에서 식별자는 변수, 함수 선언, 임시 매개 변수 등의 이름에 해당됩니다. 함수가 코드에서 지역 변수(또는 지역 함수와 임시 매개변수) 로 존재하지 않는 식별자를 참조할 때 참조가 가능한 변수를 자유 변수라고 합니다. 이 자유 변수를 검색하기 위해 범위 체인이 사용되는 것입니다.
일반적인 경우는 범위 체인은 해당 함수가 갖는 변수/활성객체가 지닌(범위 체인 앞단에) 모든 “부모 변수 객체”의 목록입니다. 그러나 “with 객체” 및 “try catch” 구문의 특별한 객체처럼 범위 체인이 컨텍스트 실행중에 동적으로 객체가 추가되는 경우가 있습니다.
식별자를 발견(상위 탐색)한 경우 범위 체인에서 먼저 활성 객체를 탐색하고 식별자가 활성 객체에서 찾지 못한 경우 범위 체인의 최상위까지 검색하게 됩니다. 다시 말하지만 이것은 프로토타입 체인과 동일합니다.
(function foo(){
var y = 20;
(function bar(){
var z = 30;
//"x"와 "y"는 "자유변수" 이며 bar가 가진범위 체인의 다음과 그 다음(bar 활성객체 이후에) 객체를 찾을 수 있습니다.
console.log(x+y+z);
})();
})();
범위 체인 사이의 관계는 체인이 다음의 객체를 참조하는 암묵의 __parent__ 속성을 사용한다고 생각해 볼 수 있습니다. 이 방법은 실제 Rhino 용 코드에서 시도할 수 있는 방법으로 바로 ES5 의 어휘 환경(lexical environments, outer link 라는 이름으로) 사용되고 있습니다. 범위 체인을 다른 방법으로 간단히 표현하면 배열이라고 할 것입니다. __parent__ 개념을 이용하면 위의 코드 예제는 아래 그림에서 나타낼 수 있습니다.(부모 변수 객체들은 bar 함수의[[Scope]] 내부 속성에 저장됩니다.)

바로 앞서 설명했었지만 with 문과 catch 절로 코드를 실행할 때는 범위 체인이 확장되는 경우가 있습니다. 그리고 이러한 객체들은 프로토타입(그리고 프로토타입 체인)을 갖는 간단한 객체입니다. 이 사실은 범위 체인 탐색은 두가지 차원에서 탐색이 이루어집니다.(만약 범위 객체가 프로토타입을 갖을 경우)
- 범위 체인을 추적
- 모든 범위의 프로토타입 체인을 추척
예제
var w = 20;
var y = 30;
//SpiderMonkey 는 전역 객체(즉 글로벌 컨텍스트 변수 객체)는 "Object.prototype" 을 계승하고 있습니다.
//따라서 전역 변수로 정의되지 않은 "x" 를 프로토타입 체인중에 찾을 수 있습니다.
console.log(x); //10
(function foo(){
//"foo" 지역 변수
var w = 40;
var x = 100;
//"x" 는 "Object.prototype" 객체에서 찾아 집니다.
//{ z : 50 } 라는 객체가 "Object.prototype" 를 계승하고 있기 때문입니다.
with({ z : 50 }) {
console.log(w, x, y, z); //40, 10, 30, 50
}
//with 객체가 범위 체인에서 제거되었습니다.
//그 결과 다시 "x", "w" 같은 "foo" 컨텍스트 변수 객체에서 로컬 변수로 처리되도록 합니다.
console.log(x, w); //100, 40
//브라우저 실행 환경에서는 보통 이렇게 해서 지역 변수에 숨겨지는 전역 변수에 접근할 수 있습니다.
console.log(window.w); //20
})();
그림으로 나타내면 이러한 구조입니다.(__parent__ 링크를 추적하기 전에 __proto__ 링크가 먼저 검색됩니다.)

모든 구현에서 글로벌 객체가 Object.prototype 을 상속하고 있는 것은 아닙니다. 이 그림의 동작(글로벌 컨텍스트에서 “정의되지 않은” 않는 변수 x를 참조)은 SpiderMonkey 에서 테스트 된 것입니다.
부모가 되는 모든 변수 객체가 존재하는 한 내부 함수에서 부모의 데이터를 얻기 위한 특별한 것은 없습니다. 단순히 필요한 변수에 대해 범위 체인을 탐색하기만 하면 됩니다. 그러나 위에서 언급한 것과 같이 컨텍스트가 종료되고 모든 상태와 컨텍스트 자체가 파괴됩니다. 동시에 내부 함수는 부모 함수로 반환될 수 있습니다. 게다가 이 반환된 함수는 나중에 다른 컨텍스트에서 활성화됩니다. 몇몇의 자유 변수 컨텍스트가 이미 없어졌다면 이런 함수 호출은 도대체 어떻게 활성화 될까요? 일반적으로 이러한 문제의 해결점을 찾아주는 것을 ECMAScript 에서는 범위 체인 컨셉과 직접적으로 연관이 있는 클로져 라고 합니다.
클로저
ECMAScript 에서 함수는 “일급 객체” 입니다. 이 말이 의미하는 바는 즉 함수를 다른 함수의 인수로 전달할 수 있다는 것입니다.(이 경우 전달하는 함수는 “functional arguments” 을 줄여서 “funargs” 라고 함) “funargs” 를 받은 함수는 고차 함수 또는 좀더 수학적으로 표현하면 연산자라고 합니다. 또한 동시에 함수는 다른 함수에 반환값으로 반환할 수 있습니다. 다른 함수를 반환하는 함수는 “function valued” 함수(혹은 함수 값을 갖는 함수)
“funarg” 및 “functional values” 에는 두가지의 이론적인 문제가 알려져 있으며, 이 두가지를 “funarg” 문제 또는 “functional argument” 문제로 정리하고 있는데 바로 “funarg 문제” 를 해결하기 위해 클로져라는 개념이 만들어진 것입니다.
그럼 이 두가지 문제에 대해 자세히 살펴 보겠습니다. (ECMAScript 에서는 함수의 [[Scope]] 내부 속성 그림에서 보는 바와 같이 이 문제는 해결 되었습니다.)
“funarg 문제” 의 첫번째는 “상승 funarg 문제” 입니다. 이것은 함수가 다른 함수에서 위로(외부) 반환될 때 자유 변수를 참조하는 경우 발생합니다. 부모의 컨텍스트가 종료 후에도 부모 컨텍스트의 변수를 참조할 수 있도록 내부 함수를 생성할 때 그 함수의 [[Scope]] 속성에 부모의 범위 체인을 보존합니다. 그리고 함수가 호출되었을 때, 그 함수의 컨텍스트가 활성 객체와 (미리 저장해 놓은) [[Scope]] 속성에 의해 생성되는 것입니다.
범위 체인 = 활성 객체 + [[Scope]]
반복해서 말하지만 주된 포인트를 기억해야 합니다. 함수가 생성되는 시점에 함수는 그때의 부모 범위 체인(자신의 [[Scope]] 로)을 저장합니다. 이 저장된 범위 체인을 함수가 호출될 때 변수 검색 대상으로 사용되게 됩니다.
var x = 10;
return function bar(){
console.log(x);
};
}
//"foo" 함수는 함수를 반환하고 반환된 이 함수는 자유 변수 "x" 를 사용합니다.
var returnedFunction = foo();
//전역 변수 "x"를 설정합니다.
var x = 20;
//반환 함수를 실행합니다.
returnedFunction(); //20이 아니라 10이 됩니다.
이런 스타일의 범위를 정적(혹은 어휘적) 범위라고 합니다. 변수 “x” 는 반환된 함수 “bar”에 저장된 [[Scope]] 내에서 탐색됩니다. 일반적으로 위의 예제에서 “x” 가 10 이 아니라 20 이 되는 동적 범위라는 것도 존재합니다. 그러나 동적 범위는 ECMAScript 에서는 채용하지 않았습니다.
“funarg 문제”의 두번째는 “하향 funarg 문제” 입니다. 이 경우 부모 컨텍스트는 존재해도 식별자 탐색은 모호해 질 수 있습니다. 문제는 어떤 부모의 범위에서 식별자 값을 탐색하는가 라는 것입니다. 함수 생성시 정적으로 저장된 것인지 또는 런타임에 동적으로 생성된 것(caller 를 범위로 하는)인지 애매함을 피하기 위해서 정적 범위가 사용되게 됩니다.
var x = 10;
//전역 함수 "foo"
function foo(){
console.log(x);
}
(function(funArg){
//지역 "x"
var x = 20;
//전역 "x" 가 호출되는 것은 분명합니다.
//왜냐하면 "foo" 함수의 [[Scope]] 에 정적으로 저장되기 떄문입니다.
//하지만 "funarg"를 활성화한 caller 범위상의 "x" 는 그렇지 않습니다.
funArg(); //20이 아니라 10
})(foo); //"foo"를 "funarg"로 하향 전달
우리는 ‘정적 범위의 언어로 클로져를 가질 의무적인 요구사항입니다’ 라고 말할 수도 있습니다. 그러나 일부 언어에서는 동적 및 정적 범위를 모두 함꼐 사용하고 있고(내부 블록을 클로져로 하거나 하지 않거나 함) 프로그래머가 자유롭게 선택할 수 있는 경우가 있습니다. ECMAScript 는 정적 범위에만 채용되고 있기 때문에 ( “funarg 문제” 가 해결되고 있다는 것입니다. ) 결론은 ECMAScript 는 클로저를 완벽히 지원하고 기술적으로는 [[Scope]] 내부 속성을 가지고 구현하는 것입니다. 클로저의 정확한 정의를 살펴봅시다.
클로저는 코드 블록(ECMAScript 에서는 함수)의 조합이며, 정적 또는 어휘적으로 모든 부모 범위를 저장한다. 그런데 이렇게 저장된 범위를 경유하여 함수는 손쉽게 자유 변수들을 참조할 수 있다.
모든 (일반) 함수는 [[Scope]] 내부 속성을 생성할 때 저장하기 위해 이론적으로 ECMAScript 의 모든 함수는 클로져라고 할 수 있습니다.
또 하나의 중요한 것은 몇가지 함수는 같은 부모 범위를 가질 수 있는 것입니다.(예를 들어 두 내부/전역 함수가 있으면 ) 이 경우 함수 각각의 **[[Scope]] 속성에 저장된 변수와 같은 부모 범위 체인을 가진 함수간에 공유하게 됩니다.
즉 하나의 클로저에 의해 만들어진 변수의 변화는 다른 클로저에서 이 변수들을 참조하려할 때 반영되어 버립니다.
var x = 1;
return {
foo : function foo(){ return ++x; },
bar : function bar(){ return -x; }
};
};
이 코드를 그림으로 나타내면 아래의 그림과 같습니다.
![그림11. 공유 \[\[Scope\]\]](http://frends.kr/wp-content/uploads/2011/11/share_scope.png)
이 기능은 루프의 일부 혼동하기 쉬운 문제를 설명해 줄 것입니다. 루프 내부에서 생성된 함수에서 루프 카운터를 사용하면 때때로 모든 함수에서 카운터가 같은 값을 표시하는 의도하지 않는 결과가 나오는 경우가 있습니다. 그 이유는 분명이 있습니다. 왜냐하면 모든 함수는 동일한 [[Scope]]를 공유하고, 마지막에 할당된 루프 카운터 값이 저징되어 있기 때문입니다.
for(var k = 0; k < 3; k++){
data[k] = function(){
alert(k);
};
}
data[0](); //0이 아닌 3
data[1](); //1이 아닌 3
data[2](); //2가 아닌 3
이 문제를 해결하려면 몇 가지 방법이 있습니다. 하나는 아래의 예제와 같이 추가저인 함수를 이용하여 범위 체인에 객체를 추가하여 주는 방법입니다.
for(var k = 0; k < 3; k++) {
//무명 내부 함수의 변수 객체가 [[Scope]] 앞에 추가됩니다.
//루프 카운터는 공유되는 "k" 가 아닌 자신만이 가진 "x" 에서 참조할 수 있다.
data[k] = (function(k){
return function(){
alert(k);
};
})(k); //"k" 값을 전달
}
//이번에는 정답
data[0](); //0
data[1](); //1
data[2](); //2
클로저 및 실제적인 응용에 대해 더 관심이 있다면 Chapter 6. Closure 를 참조하세요. 범위 체인은 그 이름대로 Chapter 4. Scope chain 라는 장을 참조하시면 됩니다.
그러면 다음 항목으로 이동합니다. 실행 컨텍스트의 마지막 속성인 this 라는 값에 대한 고찰입니다.
this
“this” 는 실행 컨텍스트와 관련된 특별한 객체입니다. 따라서 컨텍스트 객체로 명명 할 수 있습니다.(실행 컨텍스트가 활성화 되는 컨텍스트의 객체라는 의미)
어떤 객체든지 한 컨텍스트의 “this” 가 될 수 있습니다. 나는 여기서 다시 ECMAScript 의 실행 컨텍스트에 대한 오해, 특히 “this” 에 대해 명확하게 하고 싶은 생각을 가지고 있습니다. 종종 “this” 를 변수 객체의 속성이라고 설명하는 실수가 종종 있습니다. 최근 이 책 에서도 실수가 발견되었습니다.(물론 이 챕터 자체는 매우 좋은 내용을 담고 있습니다.) 그래서 다시 설명해 봅니다.
“this” 는 실행 컨텍스트의 속성이며 변수 객체의 속성은 없습니다.
이 특성은 매우 중요한 포인트입니다. 왜냐하면 변수와 달리 “this” 는 결코 식별자 탐색 과정에 참여하지 않기 때문입니다. 즉 코드에서 “this” 에 접근할 때 그 값은 실행 컨텍스트에서 직접 참조되고 범위 체인 탐색은 행해지지 않습니다. “this” 에 값은 컨텍스트에 들어간 그 순간에 한 번만 결정적으로 결정되는 것입니다.
그런데 Python 이야기지만, ECMAScript 에 대해 Python 에서 메소드는 “self” 라는 임시 임수를 단순한 변수로 받고 변수와 마찬가지로 식별자를 해결하고 또한 실행중에 값을 할당할 수도 있습니다. ECMAScript 에서는 불가능합니다. “this” 에 새로운 값을 할당할 수 없습니다. 왜냐하면 반복되지만 “this” 는 변수가 아닌 변수 객체에 저장되지 않기 때문입니다.
글로벌 컨텍스트의 “this” 는 글로벌 객체 그 자체(즉, “this” 는 변수 객체와 동일한 것입니다.)입니다.
console.log(
x, //10
this.x, //10
window.x //10
);
함수 컨텍스트의 경우 모든 단일 함수에서 “this” 객체가 다른 경우가 있습니다. 호출식의 형태(단순히 () 를 사용하여 함수를 시작하는 형태)를 가지고 caller 에서 호출될 때 “this” 값이 함수 컨텍스트로 부여됩니다. 예를 들어 다음 함수 “foo” 는 callee 이며 caller 인 글로벌 컨텍스트에서 호출됩니다. 아래의 코드는 동일한 함수 코드도 함수 호출(다른 함수 활성 방법) 에 따라 각각 다른 “this” 값을 caller 에서 주어진 것을 주의해야 합니다.
//그러나 모든 활성화 시점에 따라 "this" 값은 다를 수 있습니다.
function foo(){
alert(this);
}
//caller가 "foo" (callee) 를 활성화하고 callee 에 "this" 를 제공합니다.
foo(); //전역 객체
foo.prototype.constructor(); //foo.prototype
var bar = {
baz : foo
};
bar.baz(); //bar
(bar.baz)(); //이것도 bar
(bar.baz = bar.baz)(); //그러나 여기에서는 전역 객체
(bar.baz, baz.baz)(); //이것도 전역 객체
(false || bar.baz)(); //이것도 전역 객체
var otherFoo = "bar.baz";
outerFoo(); //또한 이것도 전역 객체
각각의 함수 호출에서 왜(그리고 더욱 중요하게… 어떻게) “this” 값을 변화하고 있는지에 대한 자세한 내용은 Chapter 3. This 를 참조하세요. 위의 예제의 모든것을 자세히 설명하고 있습니다.
끝내며
이제 간단한 개요를 마칩니다. 하지만 그렇게 간단하지 만은 않았던 것 같습니다. 그러나 이러한 항목을 전체 자세히 설명하려면 원리만 설명하는데도 책 한권이 필요합니다. 비록 여기서 함수(예를 들어 함수 선언과 함수 표현식의 일부 유형의 차이)와 ECMAScript 에서 사용되는 평가 전략에 대해서는 다루지 않았지만 두가지의 주제는 ES3 시리즈의 Chapter 5. Functions 과 Chapter 8. Evaluation strategy 에서 다룹니다.
코멘트, 질문 또는 부족한 부분이 있으면 부디 사양말고 의견 부탁드립니다. ECMAScript 학습에 행운을 기원합니다.
작성자 : Dmitry A. Soshnikov 게시일 : 2010-09-02
- 한국어 번역 : @rhiokim, rhio.kim+translation@gmail.com


Posted in 

[...] this article in: Chinese, Japanese, German, Arabic, Russian, Korean. An objectA prototype chainConstructorExecution context stackExecution contextVariable [...]
This is awesome,this is what I need
잘봤습니다. 역시 에반젤리스트는 틀리군요.
그렇죠. 내용을 잘 풀어서 써놓았더라구요.
아 원문 끙끙 대면서 보고 있었는데..
번역 하셨군요.
잘 보겠습니다.
원문 보는 것도 추천드려요.
좋은 레퍼런스들이 대부분 영문으로 되어있으니까요. ^-^;;
범위체인 예제에서 window.w는 20이 아니라 undefined 나지 않나요?
자답. jsfiddle 에서 따라하다 발생한 에러였네요 ㅎㅎ
아. 이런 댓글 너무 좋아요.
소셜하는 것 같잖아요.
즐거운 주말 되세요.
[...] this article in: English, Chinese, Japanese, German, Arabic, Korean. ОбъектЦепь прототиповКонструкторСтек контекстов [...]
안녕하세요.
우선 좋은 글 번역해 주셔서 감사합니다. 자바스크립트에 대해 헷갈리는 부분이 있을 때마다 들어와서 보게 될 것 같습니다.^^;
근데 예제에서 궁금한 부분이 한가지 있습니다. 바로 위 JT 님이 언급해 주셨는데요.
window.w 를 출력하면 20이 출력되는 것은 브라우저에서만 가능합니다. node를 이용해서, global.w 를 출력하면 undefined 가 출력됩니다.
예를 들면,
var bar = 5;
console.log(window.bar); // 브라우저에서 테스트시 5 출력
var bar = 5;
console.log(global.bar); // 노드로 테스트시 undefined
왜 그럴까 한참 고민해봤는데, 아마 이렇지 않을까 합니다.
브라우저에서 전역 객체인 window와 전역 컨텍스트의 VariableObject가 동일하기 때문에 window.bar 로 접근이 가능한 것 같습니다.
반면에 node에서는 전역 객체인 global 과 전역컨텍스트의 VariableObject가 같지 않기 때문에, global.bar가 undefined 으로 나오는 게 아닌가 합니다.
아시다시피, global.bar 라고 함은, global 객체의 bar라는 (변수가 아닌)’프로퍼티’로 접근하는 것이기 때문에, 프로토타입 체이닝을 탈 터인데,
var bar=5; 는 변수를 정의한 것이고 variableobject에 들어가 있기 때문에, bar를 찾지 못하는 것으로 보입니다.
사실
bar = 5;
console.log(global.bar);
로 하면 5가 잘 찍히는 것을 보면, 제 생각이 맞는 것도 같은데, 어떻게 생각하시는지요?
정확히 잘 아시고 계시네요.
물어보러 오지 않으셔도 되겠는데요.
안녕하세요. 먼저, 좋은 글 번역해 주셔서 정말 감사합니다.
그런데, 이미지 링크가 짤려서 원문과 왔다 갔다 하면서 보고 있자니.. 조금 힘드네요. ^^;
이미지 링크를 복구해 주시면 감사하겠습니다~
github 에서 작업하다 저장소를 날렸는데 미처 생각지 못했었네요.
복구했구요. 피드백 감사합니다. ^-^/
좋은 글 감사합니다. 프로토타입 체인에 대해 알고 싶던 차에 적절한 글을 발견하게 되어 기쁩니다. 좋은 나날되세요.
생성자 부문에서 객체관계도가 성립이 안됩니다.
직관적으로는 그럴듯 하게 보이는데.. 실제 코드로 확인을 해보면 false로
떨어집니다. 혹시 알고 계신분 있다면 답변 부탁 드립니다.
////////////////////////////////////////////////////////
그림대로 라면 (__proto__ 는 IE에서는 접근 불가하니 FF로 테스트)
console.log(‘Foo.prototype.__proto__ === Object.prototype : ‘ +
Foo.prototype.__proto__ === Object.prototype
);
console.log(‘Foo.__proto__ === Function.prototype : ‘ +
Foo.__proto__ === Function.prototype.__proto__
);
요 부분이 성립이 되야 하는데.. false 로 떨어지네요.
아무래도 관계도 이미지 부분에서 보라색으로 표시된 영역은 제외 하는게 옳지 않나 싶습니다.
테스트 하나 잘못 붙여넣은게 있네요. 테스트 실패 코드 입니다.
console.log(‘Foo.__proto__ === Function.prototype : ‘ +
Foo.__proto__ === Function.prototype
);
관계도가 이상한게 아니라 console.log 기능 내부 구현이 문제가 있는거 같네요.
alert 을 이용해서 체크 하니 true 로 나옵니다. -_-;;