에서 참조
개체 지향 기술을 이용한 고급 웹 응용 프로그램 만들기
Ray Djajadinata

이 기사에서 다루는 내용:

  • 프로토타입 기반 언어로서의 JavaScript
  • JavaScript를 사용한 개체 지향 프로그래밍
  • JavaScript에서의 코딩 트릭
  • JavaScript의 미래

이 기사에서 사용하는 기술:
JavaScript

최근에 필자는 웹 응용 프로그래밍 개발 부문에 5년 경력을 가진 소프트웨어 개발자와 인터뷰를 했습니다. 그녀는 4년 6개월 동안 JavaScript를 사용했으며 스스로의 JavaScript 숙련도를 매우 높게 평가하고 있었습니다. 나중에 알게 되었지만 이 개발자는 JavaScript에 대해서 그리 많은 것을 알고 있지는 못했습니다. 이 개발자를 탓하고자 하는 것은 아닙니다. JavaScript에는 이렇듯 상당히 재미있는 면이 있습니다. 많은 개발자들이 C/C++/C#에 대해 알고 있거나 프로그래밍 경험이 있다는 이유만으로 자신이 JavaScript에 대해 많은 것을 알고 있다고 착각하곤 합니다. 필자 역시 최근까지 그러했습니다.
이 러한 가정이 전혀 근거가 없는 것은 아닙니다. JavaScript를 사용하여 간단한 작업을 수행하기는 상당히 쉬우며 시작하기도 어렵지 않습니다. 이 언어는 또한 까다롭지 않으며 코딩을 시작하기 전에 많은 것을 배우도록 요구하지도 않습니다. 프로그래머가 아니더라도 몇 시간만 작업하면 홈페이지를 위한 유용한 스크립트를 작성할 수 있을 정도입니다.
사실 최근까지도 필자는 JavaScript에 대한 짧은 지식과 MSDN® DHTML 참조, 그리고 C++/C# 경험을 활용하여 필요한 작업을 수행할 수 있었습니다. 필자의 JavaScript 실력이 부족하다는 것을 느끼게 된 것은 현실적인 AJAX 응용 프로그램을 작성하기 시작하면서부터였습니다. 이 신세대 웹 응용 프로그램의 복잡성과 상호 작용성은 JavaScript 코드를 작성하는 데 있어 완전히 다른 방식을 요구하고 있습니다. 이것은 본격적인 JavaScript 응용 프로그램이며 지금까지 스크립트를 작성하던 느슨한 방법으로는 이러한 응용 프로그램을 작성할 수 없습니다.
개체 지향 프로그래밍(OOP)은 여러 JavaScript 라이브러리에서 코드베이스를 더욱 유지 관리하기 쉽게 하기 위해 널리 사용되는 방법입니다. JavaScript는 OOP를 지원하지만 널리 사용되는 Microsoft® .NET Framework 지원 언어인 C++, C# 또는 Visual Basic®에 서 지원하는 방법과는 상당히 다른 방법으로 제공합니다. 따라서 이러한 언어를 오랫동안 사용한 개발자라면 JavaScript에서 OOP를 사용하는 것이 낯설고 처음에는 사용하기 까다롭게 느껴질 수 있습니다. 이 기사에서는 JavaScript 언어에서 어떻게 개체 지향 프로그래밍을 지원하는지 자세히 살펴보고 이러한 지원을 사용하여 JavaScript에서 효과적으로 개체 지향 개발을 수행하는 방법을 알아보겠습니다. 우선 무엇보다 중요한 개체에 대해서 살펴보겠습니다.
JavaScript 개체는 사전입니다.
C++ 나 C#에서의 개체는 클래스나 구조체의 인스턴스를 의미합니다. 개체는 어떤 템플릿(클래스)에서 인스턴스화했는지에 따라 다른 속성 및 메서드를 가지지만 JavaScript 개체는 이와 다릅니다. JavaScript의 개체는 단순히 이름/값 쌍의 컬렉션입니다. JavaScript 개체는 문자열 키가 있는 사전으로 생각할 수 있습니다. 익숙한 “.”(점) 연산자나 일반적으로 사전을 처리할 때 사용되는 “[]” 연산자를 사용하여 개체 속성을 얻거나 설정할 수 있습니다. 다음 코드 조각을 살펴보십시오.
var userObject = new Object();
userObject.lastLoginTime = new Date();
alert(userObject.lastLoginTime);    

위 코드는 다음 코드와 정확히 같은 작업을 수행합니다.

var userObject = {}; // equivalent to new Object()
userObject[“lastLoginTime”] = new Date();
alert(userObject[“lastLoginTime”]);

다음과 같이 userObject 정의 내에서 직접 lastLoginTime 속성을 정의할 수도 있습니다.

var userObject = { “lastLoginTime”: new Date() };
alert(userObject.lastLoginTime);
C# 3.0 개체 이니셜라이저와 비슷하다는 것을 알 수 있을 것입니다. 또한 Python에 익숙한 개발자라면 두 번째와 세 번째 코드 조각에서 userObject를 인스턴스화하는 방식이 Python에서 사전을 지정하는 방식과 동일하다는 것도 알 수 있습니다. 유일한 차이는 JavaScript 개체/사전은 문자열 키만 받지만 Python 사전에는 hashable과 같은 개체를 사용할 수 있다는 것입니다.
이 러한 예는 JavaScript 개체가 C++ 또는 C# 개체에 비해 얼마나 유연한가를 보여 줍니다. lastLoginTime을 앞서 선언할 필요는 없으며 userObject에 해당하는 이름의 속성이 없는 경우에는 간단하게 userObject에 속성이 추가됩니다. JavaScript 개체가 사전이라는 사실을 기억한다면 이것은 그리 놀라운 일이 아닙니다. 사전에는 항상 새로운 키와 해당하는 값을 추가할 수 있습니다.
개체 속성은 앞서 설명한 것과 같습니다. 그렇다면 개체 메서드는 어떨까요? 이번에도 역시 JavaScript는 C++/C#과는 다릅니다. 개체 메서드를 이해하기 위해서는 먼저 JavaScript 함수를 살펴볼 필요가 있습니다.
JavaScript에서는 함수가 가장 중요합니다.
함 수와 개체를 서로 다른 것으로 취급하는 프로그래밍 언어가 많습니다. JavaScript에서는 이 차이가 모호합니다. JavaScript 함수는 실행 가능한 코드와 연결된 개체입니다. 다음과 같은 일반적인 함수를 예로 들어 보겠습니다.
function func(x) {
    alert(x);
}
func(“blah”);

이 코드는 JavaScript에서 일반적으로 함수를 정의하는 방법을 보여 줍니다. 그러나 다음과 같이 익명의 함수 개체를 만들고 변수 func에 할당하는 방법으로도 동일한 함수를 정의할 수 있습니다.

var func = function(x) {
    alert(x);
};
func(“blah2”);

또는 다음과 같이 Function 생성자를 사용할 수도 있습니다.

var func = new Function(“x”, “alert(x);”);
func(“blah3”);
이 를 통해서 함수는 함수 호출 작업을 지원하는 개체라는 것을 알 수 있습니다. Function 생성자를 사용하여 함수를 정의하는 마지막 방법은 자주 사용되지는 않지만 흥미로운 가능성을 열어 줍니다. 그 이유는 함수 본문이 Function 생성자에 대한 String 매개 변수이기 때문입니다. 이것은 런타임에 임의의 함수를 작성할 수 있음을 의미합니다.
함수가 개체라는 것을 확인시켜 주는 예로서 다른 JavaScript 개체를 대상으로 작업할 때와 마찬가지로 함수에 대해 속성을 설정하거나 추가할 수 있습니다.
function sayHi(x) {
    alert(“Hi, “ + x + “!”);
}
sayHi.text = “Hello World!”;
sayHi[“text2”] = “Hello World... again.”;

alert(sayHi[“text”]); // displays “Hello World!”
alert(sayHi.text2); // displays “Hello World... again.”
함수는 개체이므로 변수에 할당하고 다른 함수에 인수로 전달하며 다른 함수에서 값으로 반환하는 것은 물론, 개체의 속성이나 배열의 요소로 저장하는 등의 작업이 가능합니다. 그림 1에는 이에 대한 예가 나와 있습니다.
// assign an anonymous function to a variable
var greet = function(x) {
    alert(“Hello, “ + x);
};
greet(“MSDN readers”);

// passing a function as an argument to another
function square(x) {
    return x * x;
}
function operateOn(num, func) {
    return func(num);
}
// displays 256
alert(operateOn(16, square));

// functions as return values
function makeIncrementer() {
    return function(x) { return x + 1; };
}
var inc = makeIncrementer();
// displays 8
alert(inc(7));

// functions stored as array elements
var arr = [];
arr[0] = function(x) { return x * x; };
arr[1] = arr[0](2);
arr[2] = arr[0](arr[1]);
arr[3] = arr[0](arr[2]);
// displays 256
alert(arr[3]);

// functions as object properties
var obj = { “toString” : function() { return “This is an object.”; } };
// calls obj.toString()
alert(obj);
이를 기억한다면 이름을 선택하고 이 이름에 함수를 할당하는 작업으로 간단하게 개체에 메서드를 추가할 수 있습니다. 여기에서는 익명 함수를 해당 메서드 이름에 할당하여 개체에 3개의 메서드를 정의했습니다.
var myDog = {
    “name” : “Spot”,
    “bark” : function() { alert(“Woof!”); },
    “displayFullName” : function() {
        alert(this.name + “ The Alpha Dog”);
    },
    “chaseMrPostman” : function() { 
        // implementation beyond the scope of this article 
    }    
};
myDog.displayFullName(); 
myDog.bark(); // Woof!
C++/C# 개발자에게는 displayFullName 함수 내에 사용된 “this” 키워드가 친근하게 느껴질 것입니다. 이 키워드는 메서드를 호출한 개체를 참조합니다. Visual Basic 개발자에게도 역시 생소하지는 않을 것입니다. Visual Basic에서는 이를 “Me”라고 합니다. 따라서 위 예에서 displayFullName의 “this” 값은 myDog 개체입니다. 그러나 “this”의 값은 정적이지 않습니다. 그림 2에서 보여 주고 있는 것처럼 다른 개체를 통해 호출된 경우에는 “this” 값이 해당 개체를 가리키도록 변경됩니다.
function displayQuote() {
    // the value of “this” will change; depends on 
    // which object it is called through
    alert(this.memorableQuote);    
}

var williamShakespeare = {
    “memorableQuote”: “It is a wise father that knows his own child.”, 
    “sayIt” : displayQuote
};

var markTwain = {
    “memorableQuote”: “Golf is a good walk spoiled.”, 
    “sayIt” : displayQuote
};

var oscarWilde = {
    “memorableQuote”: “True friends stab you in the front.” 
    // we can call the function displayQuote
    // as a method of oscarWilde without assigning it 
    // as oscarWilde’s method. 
    //”sayIt” : displayQuote
};

williamShakespeare.sayIt(); // true, true
markTwain.sayIt(); // he didn’t know where to play golf

// watch this, each function has a method call()
// that allows the function to be called as a 
// method of the object passed to call() as an
// argument. 
// this line below is equivalent to assigning
// displayQuote to sayIt, and calling oscarWilde.sayIt().
displayQuote.call(oscarWilde); // ouch!
그림 2의 마지막 줄에서는 개체의 메서드로 함수를 호출하는 대안을 보여 줍니다. JavaScript에서 함수는 개체라는 사실을 기억하십시오. 모든 함수 개체에는 함수를 첫 번째 인수의 메서드로 호출하는 메서드 이름 호출이 있습니다. 즉, 호출에 전달하는 개체의 첫 번째 인수가 이 함수 호출에서 “this” 값이 됩니다. 뒤에 살펴보겠지만 이것은 기본 클래스 생성자를 호출하는 유용한 기술입니다.
한 가지 기억해야 할 것은 개체를 소유하지 않은 상태에서 “this”가 포함된 함수를 호출해서는 안 된다는 것입니다. 이러한 호출에서 “this”는 Global 개체를 참조하므로 전역 네임스페이스를 어지럽히게 되고 이것은 응용 프로그램에 심각한 재앙이 될 수 있습니다. 예를 들어 아래의 스크립트는 JavaScript의 전역 함수 isNaN의 동작을 변경하며 이러한 일은 절대 피해야 합니다.
alert(“NaN is NaN: “ + isNaN(NaN));

function x() {
    this.isNaN = function() { 
        return “not anymore!”;
    };
}
// alert!!! trampling the Global object!!!
x();

alert(“NaN is NaN: “ + isNaN(NaN));
지 금까지 개체를 만들고 속성과 메서드로 완성하는 방법을 살펴보았습니다. 그러나 위의 코드 조각을 자세히 살펴보면 속성과 메서드가 개체 정의 자체 내에 코드로 포함되어 있다는 것을 알 수 있습니다. 개체 생성에 대한 더 본격적인 제어가 필요한 경우에는 어떻게 해야 할까요? 예를 들어 몇 가지 매개 변수의 값에 따라 개체 속성의 값을 계산해야 할 수 있습니다. 또는 런타임에만 얻을 수 있는 값으로 개체 속성을 초기화해야 할 수 있습니다. 이외에도 개체의 인스턴스를 두 개 이상 만들어야 할 수 있는데 이것은 매우 일반적인 요구 사항입니다.
C# 에서는 개체 인스턴스를 인스턴스화하는 데 클래스를 사용하지만 JavaScript에는 클래스가 없으므로 상황이 다릅니다. 다음 섹션에서 살펴보겠지만 JavaScript에서는 “new” 연산자와 함께 사용하는 경우 함수가 생성자처럼 동작한다는 사실을 활용하게 됩니다.
생성자 함수는 있지만 클래스는 없습니다.
JavaScript OOP의 특이한 점은 앞서 언급했듯이 JavaScript에는 C# 또는 C++와 같은 클래스가 없다는 것입니다. C#에서의 작업 방법은 다음과 같습니다.
Dog spot = new Dog();

위 코드를 수행하면 클래스 Dog의 인스턴스인 개체를 얻을 수 있습니다. 그러나 JavaScript에서는 클래스가 없습니다. 클래스와 가장 비슷한 효과를 얻는 방법은 다음과 같이 생성자 함수를 정의하는 것입니다.

function DogConstructor(name) {
    this.name = name;
    this.respondTo = function(name) {
        if(this.name == name) {
            alert(“Woof”);        
        }
    };
}

var spot = new DogConstructor(“Spot”);
spot.respondTo(“Rover”); // nope
spot.respondTo(“Spot”); // yeah!

여기에서는 무슨 일이 일어나고 있을까요? DogConstructor 함수 정의에 대해서는 잠시 무시하고 다음 줄을 살펴보십시오.

var spot = new DogConstructor(“Spot”);
“new” 연산자가 수행하는 일은 간단합니다. 우선 이 연산자는 비어 있는 새 개체를 만듭니다. 그런 다음 비어 있는 새 개체를 함수 내의 “this” 값으로 설정하고 이어지는 함수를 호출합니다. 다른 말로 하면 위의 코드에서 “new” 연산자는 아래의 두 줄과 비슷하다고 생각할 수 있습니다.
// create an empty object
var spot = {}; 
// call the function as a method of the empty object
DogConstructor.call(spot, “Spot”);

DogConstructor의 본문에서 볼 수 있는 것처럼 이 함수를 호출하면 해당 호출 시 키워드 “this”가 참조하는 대상으로 개체를 초기화합니다. 이러한 방법으로 개체의 템플릿을 만드는 것이 가능합니다. 비슷한 개체를 만들어야 할 때마다 생성자 함수로 “new”를 호출하면 완전하게 초기화된 개체를 결과로 얻을 수 있습니다. 클래스와 비슷하게 느껴지지 않습니까? 실제로 JavaScript에서 일반적으로 생성자 함수의 이름은 시뮬레이트하는 클래스의 이름이며 따라서 위 예에서는 생성자 함수 이름을 Dog로 지정할 수 있습니다.

// Think of this as class Dog
function Dog(name) {
    // instance variable 
    this.name = name;
    // instance method? Hmmm...
    this.respondTo = function(name) {
        if(this.name == name) {
            alert(“Woof”);        
        }
    };
}

var spot = new Dog(“Spot”);
위 의 Dog 정의에서 필자는 name이라는 인스턴스 변수를 정의했습니다. Dog를 생성자 함수로 사용하여 생성한 모든 개체는 자체 인스턴스 변수 이름의 복사본을 가지게 됩니다. 이것은 앞서 언급했듯이 개체 사전에 대한 항목입니다. 예상할 수 있듯이 결국 모든 개체는 자체 상태를 저장하기 위한 자체 인스턴스 변수 복사본이 필요합니다. 그러나 다음 줄을 보면 Dog의 모든 인스턴스가 respondTo 메서드의 자체 복사본을 가지고 있음을 알 수 있습니다. 이것은 낭비이며 respondTo의 한 인스턴스를 Dog 인스턴스 간에 공유하면 충분합니다. 다음과 같이 respondTo 정의를 Dog 외부에 배치함으로써 이 문제를 해결할 수 있습니다.
function respondTo() {
    // respondTo definition
}

function Dog(name) {
    this.name = name;
    // attached this function as a method of the object
    this.respondTo = respondTo;
}
이 방법을 사용하면 Dog의 모든 인스턴스(즉, 생성자 함수 Dog를 사용하여 만든 모든 인스턴스)가 respondTo의 한 인스턴스를 공유할 수 있습니다. 그러나 메서드의 멤버가 늘어나면 이 방법은 점차 관리하기 어렵게 됩니다. 코드베이스 내에 많은 전역 함수가 생기게 되며 “클래스”의 수가 많아질수록, 해당 메서드의 이름이 비슷할수록 상황은 더욱 악화됩니다. 프로토타입 개체를 사용하는 더 좋은 방법이 있으며 자세한 내용은 다음 섹션에서 살펴보겠습니다.
프로토타입
프 로토타입 개체는 JavaScript의 개체 지향 프로그램에서 핵심적인 개념입니다. 프로토타입이라는 이름은 JavaScript에서 기존 예(말하자면 프로토타입) 개체의 복사본에서 개체를 만들기 때문에 붙여진 것입니다. 이 프로토타입 개체의 모든 속성 및 메서드는 이 프로토타입의 생성자로 만드는 모든 개체의 속성 및 메서드로 나타나게 됩니다. 이러한 개체가 해당 프로토타입에서 속성 및 메서드를 상속받는다고 할 수 있습니다. 다음과 같이 새 Dog 개체를 만든다고 가정해 보겠습니다.
var buddy = new Dog(“Buddy“);

프로토타입을 가져온 단 한 라인에서는 명백하게 보이지 않을 수 있지만 buddy에서 참조한 개체는 해당 프로토타입에서 속성 및 메서드를 상속하게 됩니다. 개체 buddy의 프로토타입은 생성자 함수(이 경우에는 Dog 함수)의 속성에서 가져옵니다.

JavaScript 의 모든 함수에는 프로토타입 개체를 참조하는 “prototype”이라는 속성이 있습니다. 이 프로토타입 개체에는 함수 자체를 참조하는 “constructor”라는 이름의 속성이 있습니다. 이것은 일종의 순환 참조라고 할 수 있는데 그림 3에서는 이러한 순환 관계를 더 잘 보여 주고 있습니다.

그림 3 모든 함수 프로토타입에는 생성자 속성이 있음 
이제 “new” 연산자로 개체를 만들기 위해 함수(이 예에서는 Dog)를 사용하면 결과 개체는 Dog.prototype의 속성을 상속하게 됩니다. 그림 3을 보면 Dog.prototype 개체에 다시 Dog 함수를 가리키는 생성자 속성이 있음을 알 수 있습니다. 따라서 모든 Dog 개체(Dog.prototype에서 상속한) 또한 Dog 함수를 가리키는 생성자 속성을 가지는 것처럼 보입니다. 그림 4의 코드에서 이를 확인할 수 있습니다. 생성자 함수, 프로토타입 개체 및 이들 사용하여 만든 개체 간의 관계는 그림 5에 표시되어 있습니다.
var spot = new Dog(“Spot”);

// Dog.prototype is the prototype of spot
alert(Dog.prototype.isPrototypeOf(spot));

// spot inherits the constructor property
// from Dog.prototype
alert(spot.constructor == Dog.prototype.constructor);
alert(spot.constructor == Dog);

// But constructor property doesn’t belong
// to spot. The line below displays “false”
alert(spot.hasOwnProperty(“constructor”));

// The constructor property belongs to Dog.prototype
// The line below displays “true”
alert(Dog.prototype.hasOwnProperty(“constructor”));

그림 5 해당 프로토타입에서 상속된 인스턴스 
그림 4에 서 hasOwnProperty 및 isPrototypeOf 메서드에 대한 호출을 볼 수 있을 것입니다. 이러한 메서드는 어디에서 온 것일까요? Dog.prototype에서 온 것은 아닙니다. 실제로 Dog.prototype 및 Dog의 인스턴스에서 호출할 수 있는 toString, toLocaleString 및 valueOf와 같은 다른 메서드도 있으며 이들 역시 Dog.prototype에서 온 것은 아닙니다. .NET Framework에도 모든 클래스에 대한 궁극적인 기본 클래스 역할을 하는 System.Object가 있는 것처럼 JavaScript에도 모든 프로토타입에 대한 궁극적인 기본 프로토타입인 Object.prototype이 있습니다. Object.prototype의 프로토타입은 Null입니다.
이 예에서는 Dog.prototype이 개체라는 것을 기억하십시오. 보이지는 않지만 이 개체는 Object 생성자 함수를 호출함으로써 생성되었습니다.
Dog.prototype = new Object();
따 라서 Dog의 인스턴스가 Dog.prototype에서 상속하는 것처럼 Dog.prototype은 Object.prototype에서 상속합니다. 결국 Dog의 모든 인스턴스는 Object.prototype의 메서드 및 속성 또한 상속하게 됩니다.
모 든 JavaScript 개체는 Object.prototype으로 끝나는 프로토타입의 체인을 따라 상속합니다. 지금까지 살펴본 이 상속은 라이브 개체 간의 상속입니다. 선언되는 시점에 클래스 간에 일어나는 상속의 일반적인 개념과는 차이가 있습니다. 결과적으로 JavaScript 상속은 훨씬 동적이며 다음과 같은 간단한 알고리즘을 사용합니다. 개체의 속성/메서드에 액세스하려고 시도하면 JavaScript는 해당 속성/메서드가 개체에 정의되어 있는지 확인합니다. 개체에 정의되어 있지 않은 경우에는 개체의 프로토타입을 확인합니다. 여기에도 정의되어 있지 않으면 Object.prototype에 이를 때까지 개체 프로토타입의 프로토타입을 계속 확인합니다. 그림 6에서는 이러한 확인 프로세스를 보여 줍니다.

그림 6 프로토타입 체인에서 toString() 메서드 확인 (더 크게 보려면 이미지를 클릭하십시오.)
JavaScript가 속성 액세스와 메서드 호출을 동적으로 확인함에 따르는 몇 가지 효과가 있습니다.

  • 프로토타입 개체에 대한 변경은 개체가 생성된 이후에도 개체에 영향을 줍니다.
  • 개체에 속성/메서드 X를 정의하여 동일한 이름의 속성/메서드는 해당 개체의 프로토타입에서 숨겨집니다. 예를 들어 Dog.prototype에서 toString 메서드를 정의하여 Object.prototype의 toString 메서드를 다시 정의할 수 있습니다.
  • 변경은 프로토타입에서 해당 파생 개체로 한 방향으로만 진행하며 반대로는 진행하지 않습니다.
그림 7에서는 이러한 결과를 보여 줍니다. 그럼 7에 서는 또한 앞서 발생했었던 불필요한 메서드 인스턴스 문제를 해결하는 방법도 보여 줍니다. 모든 개체가 별도의 함수 개체를 가지도록 하는 것이 아니라 메서드를 프로토타입 내에 배치함으로써 개체가 메서드를 공유하도록 할 수 있습니다. 이 예에서 getBreed 메서드는 spot에서 toString 메서드를 다시 정의하기 전까지 rover 및 spot에 의해 공유됩니다. 이후에 spot은 자신의 getBreed 메서드 버전을 가지지만 rover 개체 및 새 GreatDane으로 만든 이후 개체는 GreatDane.prototype 개체에 정의된 getBreed 메서드의 인스턴스를 공유하게 됩니다.
function GreatDane() { }

var rover = new GreatDane();
var spot = new GreatDane();

GreatDane.prototype.getBreed = function() {
    return “Great Dane”;
};

// Works, even though at this point
// rover and spot are already created.
alert(rover.getBreed());

// this hides getBreed() in GreatDane.prototype
spot.getBreed = function() {
    return “Little Great Dane”;
};
alert(spot.getBreed()); 

// but of course, the change to getBreed 
// doesn’t propagate back to GreatDane.prototype
// and other objects inheriting from it,
// it only happens in the spot object
alert(rover.getBreed());
정적 속성 및 메서드
인 스턴스가 아닌 클래스에 연결된 속성이나 메서드, 말하자면 정적 속성 및 메서드가 필요가 경우가 있습니다. JavaScript에서 함수는 원하는 대로 속성과 메서드를 설정할 수 있는 개체이므로 이러한 경우에 대처하기가 수월합니다. JavaScript에서는 생성자 함수가 클래스를 대신하므로 다음과 같이 생성자 함수에 정적 메서드와 속성을 설정함으로써 클래스에 추가하는 효과를 얻을 수 있습니다.
    function DateTime() { }

    // set static method now()
    DateTime.now = function() {
        return new Date();
    };

    alert(DateTime.now());
JavaScript 에서 정적 메서드를 호출하기 위한 구문은 C#에서의 구문과 거의 동일합니다. 생성자 함수의 이름은 사실상 클래스의 이름이므로 이는 놀라운 일이 아닙니다. 이제 클래스와 공용 속성/메서드, 그리고 정적 속성/메서드가 있습니다. 이외에 어떤 것이 필요할까요? 우선 전용 멤버가 있습니다. 그러나 JavaScript에는 전용 멤버에 대한 기본 지원이 없습니다. 적어도 보호되는 것은 없으며 모두가 개체의 모든 속성과 메서드에 액세스할 수 있습니다. 클래스에 전용 멤버를 추가하는 방법이 있지만 이를 위해서는 먼저 차단에 대해서 이해해야 합니다.
차단
필 자는 스스로 원해서 JavaScript를 배운 것은 아니었으며 JavaScript를 사용하지 않고 현실적인 AJAX 응용 프로그램을 개발한다는 것이 불가능하다는 것을 깨달은 후에야 배우기 시작했습니다. 처음에는 필자의 프로그래머 수준이 몇 단계가 떨어지는 것이 아닌가 하는 생각도 들었습니다. (JavaScript라니! C++를 사용하는 친구들이 뭐라고 말할까?) 그러나 처음 가지고 있던 거부감을 떨쳐내자 JavaScript가 상당히 강력하고 인상적이며 콤팩트한 언어라는 사실을 깨달았습니다. 필자는 또한 널리 사용되는 다른 언어에서 이제 막 지원하기 시작한 기능에 대해서 자랑하기도 했습니다.
JavaScript 의 고급 기능 중 하나로 C# 2.0에서는 익명 메서드를 통해 지원되는 차단에 대한 지원이 있습니다. 차단은 안쪽 함수(C#에서는 안쪽 익명 메서드)가 바깥쪽 함수의 로컬 변수와 바인딩되었을 때 일어나는 런타임 현상입니다. 분명한 것은 이 안쪽 함수가 바깥쪽 함수 외부에서 액세스 가능하지 않다면 이것이 의미가 없게 된다는 것입니다. 한 가지 예를 살펴보겠습니다.
예를 들어 100보다 큰 수만 통과할 수 있고 나머지는 필터링되는 간단한 조건으로 일련의 수를 필터링해야 한다고 가정해 보겠습니다. 그림 8와 같이 함수를 작성할 수 있습니다.
function filter(pred, arr) {
    var len = arr.length;
    var filtered = []; // shorter version of new Array();
    // iterate through every element in the array...
    for(var i = 0; i < len; i++) {
        var val = arr[i];
        // if the element satisfies the predicate let it through
        if(pred(val)) {
            filtered.push(val);
        }
    }
    return filtered;
}

var someRandomNumbers = [12, 32, 1, 3, 2, 2, 234, 236, 632,7, 8];
var numbersGreaterThan100 = filter(
    function(x) { return (x > 100) ? true : false; }, 
    someRandomNumbers);

// displays 234, 236, 632
alert(numbersGreaterThan100);
그런데 이제는 다른 필터링 조건, 예를 들어 300보다 큰 수만 통과하도록 다른 필터링 조건을 만들어야 한다고 가정해 보겠습니다. 이제 다음과 같은 코드를 사용할 수 있습니다.
var greaterThan300 = filter(
    function(x) { return (x > 300) ? true : false; }, 
    someRandomNumbers);
그 리고 50, 25, 10, 600 등의 수보다 큰 수를 필터링하게 할 수 있습니다. 영리한 독자라면 이 필터가 모두 “보다 큰”이라는 동일한 조건자를 사용하고 있으며 수만 다르다는 것을 간파할 수 있을 것입니다. 따라서 함수에서 수를 인수로 만들어 다음과 같이 작성할 수 있습니다.
function makeGreaterThanPredicate(lowerBound) {
    return function(numberToCheck) {
        return (numberToCheck > lowerBound) ? true : false;
    };
}

이제 다음과 같이 작업할 수 있습니다.

var greaterThan10 = makeGreaterThanPredicate(10);
var greaterThan100 = makeGreaterThanPredicate(100);
alert(filter(greaterThan10, someRandomNumbers));
alert(filter(greaterThan100, someRandomNumbers));
makeGreaterThanPredicate 함수에서 반환된 안쪽 익명 함수를 눈여겨보십시오. 이 익명 안쪽 함수는 makeGreaterThanPredicate로 전달된 인수인 lowerBound를 사용합니다. 일반적인 범위 지정의 규칙에 따르면 makeGreaterThanPredicate가 존재할 때는 lowerBound가 범위를 벗어나게 됩니다. 그러나 이 경우에 안쪽 익명 함수는 makeGreaterThanPredicate가 존재하는 한참 후에도 lowerBound를 가지고 있습니다. 이를 차단이라고 하는 것은 안쪽 함수가 정의된 위치에서 환경에 대해(바깥쪽 함수의 인수 및 지역 변수) 차단을 수행하기 때문입니다.
차 단은 처음에는 사소한 기능처럼 보일 수도 있습니다. 그러나 제대로만 활용한다면 개발자의 아이디어를 코드로 구현하는 새롭고 흥미로운 가능성을 열어 줄 수 있습니다. JavaScript에서 가장 흥미로운 차단의 활용 예는 클래스의 전용 변수를 시뮬레이트하는 것입니다.
전용 속성 시뮬레이션
차 단을 사용하여 전용 멤버를 시뮬레이트하는 방법을 살펴보겠습니다. 함수의 지역 변수는 일반적으로 함수 외부에서는 액세스할 수 없습니다. 함수가 더 이상 존재하지 않게 되면 지역 변수의 모든 실질적인 용도는 완전히 사라집니다. 그러나 지역 변수가 안쪽 함수의 차단에 의해 캡처되면 계속 존재하게 됩니다. 이것이 바로 JavaScript 전용 속성을 시뮬레이션하기 위한 핵심입니다. 다음 Person 클래스를 살펴보십시오.
function Person(name, age) {
    this.getName = function() { return name; };
    this.setName = function(newName) { name = newName; };
    this.getAge = function() { return age; };
    this.setAge = function(newAge) { age = newAge; };
}
인 수 name과 age는 생성자 함수 Person에 대해 로컬입니다. Person이 반환하는 순간 name과 age는 완전히 사라지게 됩니다. 그러나 여기에서 name과 age는 Person 인스턴스의 메서드로 할당된 네 함수에서 캡처되어 계속 유지되며 이러한 네 메서드 내에서만 엄격하게 액세스할 수 있게 됩니다. 즉, 다음과 같은 코드가 가능합니다.
var ray = new Person(“Ray”, 31);
alert(ray.getName());
alert(ray.getAge());
ray.setName(“Younger Ray”);
// Instant rejuvenation!
ray.setAge(22);
alert(ray.getName() + “ is now “ + ray.getAge() + 
      “ years old.”);
생성자에서 초기화되지 않은 전용 멤버는 다음과 같은 생성자 함수의 지역 변수가 될 수 있습니다.
function Person(name, age) {
    var occupation;
    this.getOccupation = function() { return occupation; };
    this.setOccupation = function(newOcc) { occupation = 
                         newOcc; };
  
    // accessors for name and age    
}

이러한 전용 멤버와 C#에서 사용하던 전용 멤버 사이에는 미세한 차이점이 있습니다. C#에서 클래스의 공용 메서드는 클래스의 전용 멤버를 액세스할 수 있었습니다. 그러나 JavaScript에서는 자체 차단 내에 이러한 전용 멤버를 가지고 있는 메서드를 통해서만 이러한 전용 멤버를 액세스할 수 있습니다. 이러한 메서드는 일반적인 공용 메서드와는 다르므로 보통 권한 있는 메서드라고 불립니다. 따라서 Person 공용 메서드 내에서도 전용 메서드에 접근하려면 권한 있는 접근자 메서드를 통해야 합니다.

Person.prototype.somePublicMethod = function() {
    // doesn’t work!
    // alert(this.name);
    // this one below works
    alert(this.getName());
};
Douglas Crockford는 차단을 사용하여 전용 멤버를 시뮬레이트하는 기술을 발견(또는 발표)한 첫 번째 인물로 널리 알려져 있습니다. 그의 웹사이트(javascript.crockford.com)에는 JavaScript에 대한 많은 정보가 있습니다. JavaScript에 관심이 있는 개발자라면 반드시 들러 보아야 할 곳입니다.
클래스로부터 상속
지 금까지 생성자 함수가 작동되는 방법과 프로토타입 개체를 사용하여 JavaScript에서 클래스를 시뮬레이트하는 방법을 살펴보았으며 프로토타입 체인을 통해 모든 개체가 Object.prototype의 메서드를 공통적으로 가진다는 것을 확인했습니다. 차단을 사용하여 클래스의 전용 멤버를 시뮬레이트하는 방법도 확인했습니다. 그러나 여기에는 무엇인가가 빠져 있습니다. C#에서는 일상적인 작업이라고 할 수 있는 클래스에서 파생시키는 방법을 아직 설명하지 않았습니다. 아쉽게도 JavaScript에서 클래스를 상속하는 것은 C#에서 콜론을 입력하는 것처럼 단순하지 않으며 그 이상의 작업이 필요합니다. 반면에 JavaScript는 매우 유연하여 클래스에서 상속하는 다양한 방법을 사용할 수 있습니다.
그림 9에 서 보여 주는 것처럼 Pet이라는 기본 클래스가 있고 여기에서 파생된 Dog 클래스가 있다고 가정해 보겠습니다. JavaScript에서는 이를 어떻게 구현해야 할까요? Pet 클래스는 간단하며 지금까지 배운 내용으로 구현할 수 있습니다.

그림 9 클래스 
// class Pet
function Pet(name) {
    this.getName = function() { return name; };
    this.setName = function(newName) { name = newName; };
}

Pet.prototype.toString = function() {
    return “This pet’s name is: “ + this.getName();
};
// end of class Pet

var parrotty = new Pet(“Parrotty the Parrot”);
alert(parrotty);
그렇다면 이제 Pet에서 파생된 Dog 클래스를 만들고자 한다면 어떻게 해야 할까요? 그림 9에 서 볼 수 있는 것처럼 Dog에는 breed라는 추가 속성이 있으며 Pet의 toString 메서드를 다시 정의하였습니다. C#에서는 메서드와 속성의 이름에 파스칼식 대/소문자가 권장되지만 JavaScript에서는 카멜식 대/소문자를 사용하는 것이 관례입니다. 그림 10에서는 이러한 작동 방식을 보여 줍니다.
// class Dog : Pet 
// public Dog(string name, string breed)
function Dog(name, breed) {
    // think Dog : base(name) 
    Pet.call(this, name);
    this.getBreed = function() { return breed; };
    // Breed doesn’t change, obviously! It’s read only.
    // this.setBreed = function(newBreed) { name = newName; };
}

// this makes Dog.prototype inherits
// from Pet.prototype
Dog.prototype = new Pet();

// remember that Pet.prototype.constructor
// points to Pet. We want our Dog instances’
// constructor to point to Dog.
Dog.prototype.constructor = Dog;

// Now we override Pet.prototype.toString
Dog.prototype.toString = function() {
    return “This dog’s name is: “ + this.getName() + 
        “, and its breed is: “ + this.getBreed();
};
// end of class Dog

var dog = new Dog(“Buddy”, “Great Dane”);
// test the new toString()
alert(dog);

// Testing instanceof (similar to the is operator)
// (dog is Dog)? yes
alert(dog instanceof Dog);
// (dog is Pet)? yes
alert(dog instanceof Pet);
// (dog is Object)? yes
alert(dog instanceof Object);
여기서 사용된 프로토타입 대체 트릭은 프로토타입 체인을 올바르게 설정하므로 C#에서와 마찬가지로 instanceof 테스트도 작동합니다. 또한 권한 있는 메서드 역시 예상대로 작동합니다.
네임스페이스 시뮬레이션
C++ 와 C#에서 네임스페이스는 이름 충돌의 우려를 최소화하기 위해 사용됩니다. .NET Framework에서 네임스페이스는 예를 들어 Microsoft.Build.Task.Message 클래스와 System.Messaging.Message 클래스를 구별하는 데 사용됩니다. JavaScript에는 네임스페이스 지원과 관련된 세부적인 언어 지원은 없지만 개체를 사용하여 손쉽게 네임스페이스를 시뮬레이트할 수 있습니다. JavaScript 라이브러리를 만들고자 한다고 가정해 보겠습니다. 함수 및 클래스를 전역으로 정의하는 대신 다음과 같이 네임스페이스 내에 래핑할 수 있습니다.
var MSDNMagNS = {};

MSDNMagNS.Pet = function(name) { // code here };
MSDNMagNS.Pet.prototype.toString = function() { // code };

var pet = new MSDNMagNS.Pet(“Yammer”);
한 수준의 네임스페이스는 고유하지 않을 수 있으므로 다음과 같이 중첩된 네임스페이스를 만들 수 있습니다.
var MSDNMagNS = {};
// nested namespace “Examples”
MSDNMagNS.Examples = {}; 

MSDNMagNS.Examples.Pet = function(name) { // code };
MSDNMagNS.Examples.Pet.prototype.toString = function() { // code };

var pet = new MSDNMagNS.Examples.Pet(“Yammer”);

짐작할 수 있겠지만 긴 중첩 네임스페이스를 입력하는 일은 금방 성가시게 느껴지게 됩니다. 다행스럽게도 라이브러리 사용자는 짧은 이름으로 네임스페이스에 별칭을 붙일 수 있습니다.

// MSDNMagNS.Examples and Pet definition...

// think “using Eg = MSDNMagNS.Examples;” 
var Eg = MSDNMagNS.Examples;
var pet = new Eg.Pet(“Yammer”);
alert(pet);
Microsoft AJAX 라이브러리의 소스 코드를 살펴보면 라이브러리의 저자 역시 네임스페이스를 구현하기 위해 비슷한 기술을 사용했음을 알 수 있습니다. 정적 메서드 Type.registerNamespace의 구현을 살펴보십시오. 자세한 내용은 보충 기사 “OOP 및 ASP.NET AJAX”를 참조하십시오.
JavaScript에서 이와 같이 코딩해야 할까요?
지 금까지 JavaScript에서도 개체 지향 언어를 문제 없이 지원한다는 것을 확인했습니다. 프로토타입 언어로 설계되었지만 JavaScript는 유연하고 강력하므로 널리 사용되는 다른 언어에서 일반적으로 사용되는 클래스 기반 프로그래밍 스타일을 지원할 수 있습니다. 그러나 문제는 JavaScript에서 이와 같이 코딩해야 하는지 결정하는 것입니다. C# 또는 C++에서 코딩하는 방식대로 JavaScript로 코딩하면서 없는 기능은 시뮬레이트하는 것이 현명한 방법일까요? 각각의 프로그래밍 언어는 서로 다르며 한 언어의 최상의 방법이 다른 언어에서는 최상의 방법이 아닐 수 있습니다.
JavaScript 에서는 클래스가 다른 클래스에서 상속하는 것과 달리 개체가 다른 개체에서 상속하는 것을 확인했습니다. 따라서 정적 상속 계층을 사용하여 많은 클래스를 만들기는 가능하지만 이것은 JavaScript에 맞는 방식은 아닙니다. Douglas Crockford가 그의 자료 “JavaScript의 프로토타입 상속“에서 밝힌 것처럼 JavaScript에 맞는 프로그래밍 방식은 프로토타입 개체를 만들고 해당 원본 개체에서 상속하는 새 개체 아래에 간단한 개체 함수를 만드는 방식일지도 모르겠습니다.
    function object(o) {
        function F() {}
        F.prototype = o;
        return new F();
    }

JavaScript의 개체는 유연하므로 만든 후에 필요에 따라 새 필드와 새 메서드를 사용하여 손쉽게 개체를 보강할 수 있습니다.

지 금까지는 모두 좋지만 전 세계에 있는 개발자 대부분이 클래스 기반 프로그래밍에 익숙하다는 사실은 부인할 수 없습니다. 클래스 기반 프로그래밍은 또한 계속해서 유지될 것입니다. 발표 예정인 버전 4 ECMA-262 규약(ECMA-262는 JavaScript의 공식 사양)에 따르면 JavaScript 2.0에는 진정한 클래스가 추가될 예정입니다. 즉, JavaScript는 클래스 기반 언어로 변화하고 있습니다. 그러나 JavaScript 2.0이 널리 보급되기까지는 몇 년이 걸릴 것입니다. 그 전까지는 현재의 JavaScript로 프로토타입 기반 스타일과 클래스 기반 스타일 코드를 모두 읽고 작성할 수 있다는 사실을 알고 있는 것이 중요합니다.
앞으로의 전망
클 라이언트에 크게 의존하는 대화식 AJAX 응용 프로그램이 확산됨에 따라 JavaScript는 빠른 속도로 .NET 개발자를 위한 가장 유용한 도구로 자리를 잡고 있습니다. 그러나 JavaScript의 프로토타입 특성은 C++, C# 또는 Visual Basic과 같은 언어에 익숙해 있던 개발자들에게 다소 생소할 수 있습니다. 필자의 경험을 이야기하자면 과정에 어려움이 없었던 것은 아니지만 JavaScript를 배우는 과정은 충분히 가치가 있었다고 생각합니다. 이 기사를 통해 여러분의 과정을 도울 수 있다면 좋다면 좋겠습니다. 바로 그것이 필자의 목표입니다.
OOP 및 ASP.NET AJAX
ASP.NET AJAX에 구현된 OOP와 이 기사에서 설명한 정식 구현과는 약간의 차이점이 있습니다. 이러한 차이점이 있는 이유는 두 가지입니다. ASP.NET AJAX 버전에서는 xml-script 및 매개 변수 유효성 검사와 같은 선언적 구문에 필수적인 리플렉션에 대한 더 많은 가능성을 제공하며 ASP.NET AJAX는 .NET을 사용하는 개발자에게 익숙한 속성, 이벤트, 열거형 및 인터페이스와 같은 몇 가지 추가 구조를 JavaScript에 제공하는 데 초점을 맞추고 있습니다.
현재 널리 사용 가능한 버전의 경우 JavaScript에는 .NET 개발자에게 익숙한 몇 가지 핵심 OOP 개념이 빠져 있지만 ASP.NET AJAX에서는 이러한 대부분의 개념을 에뮬레이트합니다.
클 래스에 명명 규칙 기반의 속성 접근자(예는 조금 뒤에 있음)는 물론 .NET에서 제공되는 패턴을 비슷하게 따르는 멀티캐스트 이벤트를 추가할 수 있습니다. 전용 변수는 밑줄로 시작하는 멤버는 전용이라는 관례에 바탕을 두고 있습니다. 진정한 전용 변수가 필요한 경우는 드물지만 이 정책은 디버거에서 이러한 변수를 검사할 수 있도록 해 줍니다. 일반적인 오리 형식 지정을 벗어난 형식 검사 시나리오를 지원하기 위해 인터페이스가 추가되었습니다. 오리 형식 지정이란 어떤 것이 오리처럼 걷고 오리 같은 소리를 낸다면 이것은 오리이거나 최소한 오리와 같이 취급해야 한다는 개념에 바탕을 둔 것입니다.
클래스와 리플렉션
JavaScript 에서 함수의 이름을 알아낼 수 있는 방법은 없습니다. 이것이 가능하다고 하더라도 네임스페이스 변수에 익명 함수를 할당함으로써 클래스 생성자를 작성하는 대부분의 상황에는 큰 도움이 되지 않을 것입니다. 실제 형식 이름을 구성하는 것은 동등한 수준으로 액세스가 불가능하며 생성자 함수 자체가 아무런 정보도 없는 이 변수의 정규화된 이름입니다. 이러한 한계를 해결하고 JavaScript 클래스에 대한 풍부한 리플렉션을 제공하기 위해 ASP.NET AJAX에서는 형식의 이름을 등록하도록 요구하고 있습니다.
ASP.NET AJAX의 리플렉션 API는 기본 제공 형식, 클래스, 인터페이스, 네임스페이스는 물론 열거형을 포함하여 어떤 형식에 대해서도 작동하며 런타임에 클래스 계층을 검사하기 위한 isInstanceOfType 및 inheritsFrom와 같은 .NET Framework와 비슷한 함수도 포함합니다. ASP.NET AJAX는 또한 디버그 모드에서 약간의 형식 검사를 수행하여 개발자가 더 일찍 버그를 찾을 수 있도록 지원합니다.
클래스 계층 및 호출 기반 등록
ASP.NET AJAX에서 클래스를 정의하려면 해당 생성자를 변수에 할당해야 합니다. 생성자가 기반을 호출하는 방법을 확인하십시오.

MyNamespace.MyClass = function() {
    MyNamespace.MyClass.initializeBase(this);
    this._myProperty = null;
}
Then, you need to define the class members itself in its prototype:

MyNamespace.MyClass.prototype = {
    get_myProperty: function() { return this._myProperty;},
    set_myProperty: function(value) { this._myProperty = value; },
    doSomething: function() {
        MyNamespace.MyClass.callBaseMethod(this, “doSomething”);
        /* do something more */
    }
}

이제 최종적으로 클래스를 등록하게 됩니다.

MyNamespace.MyClass.registerClass(
    “MyNamespace.MyClass “, MyNamespace.BaseClass);

생성자와 프로토타입 계층은 registerClass 함수에 의해 자동으로 관리되므로 직접 관리할 필요가 없습니다.

Bertrand Le Roy는 ASP.NET AJAX 팀의 소프트웨어 설계 엔지니어 II입니다.

멋진인생님 블로그참조

http://run2you.tistory.com/12

javascript prototype 이란 무엇인가???? (1부)

Posted 2008/06/16 01:51

최신 배포된 자바스크립트 프레임 워크들에 관심을 가지고 소스를 보다보면

prototype 이란게 나옵니다….

여기서 prototype 이란 prototype.js 의 prototype 하곤 틀린것입니다.  그럼 prototype 이란 무엇

이고 어떻게 써야 할건가 이번 시간에 한번 보도록 하죠..(무슨 선생님 같습니다 -_-;;;;;)

 

  1. var man = function(name){  
  2.      this.name = name;  
  3. }  
  4.   
  5. var niceguy = new man(‘멋진인생’);  
  6.   
  7. alert(niceguy.name);  

생성자 함수를 사용하여 객체를 하나 생성했습니다. 객체의 프로퍼티에 접근하기 위해선

상기 소스 처럼 사용해도 되나.. 우리는 인스턴스 메소드를 사용해서 name 을 가져오는

getName 메소드를 만들어 보도록 할겁니다.

  1. var man = function(name){  
  2.      this.name = name;  
  3.      this.getName = function(){  
  4.         alert(this.name);  
  5.      }  
  6. }  
  7.   
  8. var niceguy = new man(‘멋진인생’);    
  9. niceguy.getName();  
  10.   
  11. var panda     = new man(‘쿵후팬더’);    
  12. panda.getName();     

 자 멋지게 된거 같죠!!  그러나 사실 잘보시면 알겠지만 객체 마다 name 프로퍼티와 getName

 메소드가 포함되어 있습니다.. 사실 name 프로퍼티는 각 객체 마다 값이 틀리지만 getName

 메소드는 동일한 메소드이기 때문에 사실 객체마다 메소드가 존재해서 부피를 키우는 꼴이 되는

거죠.. 그럼 다른 방법은 없을까요..

  1. var man = function(name){  
  2.      this.name = name;  
  3. }  
  4.   
  5. var getName = function(){  
  6.       alert(this.name);  
  7. }    
  8.   
  9. var niceguy = new man(‘멋진인생’);    
  10. getName.call(niceguy);  
  11.   
  12. var panda     = new man(‘쿵후팬더’);    
  13. getName.call(panda);    

자 멋지게 된거 같죠!! 근데 사실 이렇게 함수로 빼서 call, apply 등으로 사용하게 되면, 일단

하나의 함수로 다같이 사용하는건 맞습니다. 맞고요~~ 근데 이렇게 되었을시 일단 지저분하고,

메소드가 계속 추가 되었을시 관리는 누가 하나요???(전 못합니다 -_-;;;)

사실 모듈화된 javascript 를 잘 만들려면

객체형식으로 분리해서 관리 하여야 합니다.. 이제 최종적으로 수정해 볼까요..

  1. var man = function(name){  
  2.      this.name = name;  
  3. }  
  4.   
  5. man.prototype.getName = function(){  
  6.     alert(this.name);  
  7. }  
  8.   
  9. var niceguy = new man(‘멋진인생’);    
  10. niceguy.getName();  
  11.   
  12. var panda     = new man(‘쿵후팬더’);    
  13. panda.getName();  

뭔지 모르겠지만 일단 잘 작동 합니다….

도대체 무슨일이 일어 난걸까요??? 살짝 흥분도 됩니다(저 혼자)

사실 function 함수 엔 Prototype 객체를 가르키는 prototype 이란 프로퍼티가 하나 존재합니다.

이 Prototype 객체에 공유되어  있는 프로퍼티와 메소드가 객체 생성시 해당 생성자 function 으로

생성한 모든 인스턴스에서 공유됩니다…

글이 길어지고 밤도 깊어지므로 더 자세한 내용은 2부로 넘기도록 하겠습니다..

회사에서 막간을 이용해 글을 이어 나가 봅니다..

모든 function 에는 Prototype 객체를 가르키는 prototype 이라는 프로퍼티가 있다고 했습니다.

그럼 그 prototype 에는 사실 딱 하나의 프로퍼티가 존재 합니다.

그 이름 하여 constructor 입니다..  

   

  1. var man = function(name){  
  2.     this.name = name;  
  3.     this.getName = function(){  
  4.         alert(this.name);  
  5.     }  
  6. }  
  7.   
  8. alert(a.prototype.constructor);  

해당 prototype의 constructor 는 prototype 을 가지고 있는 function 을 가르킵니다.

일단 이부분은 나중에 다시 설명하도록 하고

이전 글 마지막에서 보았듯이 어떻게 prototype 에서 각 객체의 인스턴스로 프로퍼티들을 공유 할까

요… 그건 상속과도 비슷한 개념입니다…

   

  1. var man = function(name){  
  2.     this.name = name;  
  3. }  
  4.   
  5. man.prototype.getName = function(){  
  6.         alert(this.name);                     
  7. }  
  8.   
  9.   
  10. var niceguy = new man(“멋진인생”);  
  11. niceguy.getName();  


상기 소스를 보시면 man 생성자 함수를 통하여 객체를 생성했습니다.

niceguy 인스턴스에는 분명 getName 이 존재 하지 않으나 getName 은 정상적으로 작동하게 되죠.

사실 원리는 이렇습니다.. 최초 호출시 man 자체에서 getName을 찾아 봅니다.

당연히 없겠죠.. 그럼 man.prototype 에서 getName 을 찾아 봅니다. 물론 있습니다.. 그럼 반환

하겠죠…. 만약에 man.prototype 에서 조차도 없다. 그럴땐 더 상위로 가봅니다..

즉 Object.prototype 에서 찾아 보는겁니다.  이마저 없을땐 오류가 발생하는거죠…

그림으로 볼까요? (필요 없을려나 -_-;;;)

사용자 삽입 이미지

자 관련 소스를 하나 볼까요 ^^;;

   

  1. var man = function(name){  
  2.     this.name = name;  
  3.     this.getName = function(){  
  4.         alert(‘man:’ + this.getName);  
  5.     }  
  6. }  
  7.   
  8. man.prototype.getName = function(){  
  9.         alert(‘man.prototype:’+this.name);                    
  10. }  
  11.   
  12.   
  13. Object.prototype.getName = function(){  
  14.         alert(‘Object.prototype:’+this.name);                     
  15. }                 
  16.   
  17. var niceguy = new man(“멋진인생”);  
  18. niceguy.getName();  

역시 예상대로 man 의 getName 을 가져오는군요….

이렇듯 prototype 베이스 상속이란건 실제로 값을 가져다가 복사하는게 아닌

상위로 찾아가면서 값을 구하는 거예요..

그럼 prototype 에서의 읽기는 그렇다 치고 쓰기는 어떨까요??

   

  1. var man = function(name){  
  2.     this.name = name;  
  3. }  
  4.   
  5. man.prototype.age = ’13’  
  6.   
  7. var niceguy = new man(‘멋진인생’);  
  8.   
  9. alert(niceguy.age) // 13 반환  
  10. alert(niceguy.hasOwnProperty(“age”)); // false 반환  
  11. niceguy.age = ’14’;  
  12. alert(niceguy.age)   // 14 반환         
  13. alert(niceguy.hasOwnProperty(“age”)); // true 반환      

age 프로퍼티를 하나 추가하려 합니다.. 보통 man 자체에 넣겠지만 prototype 에 넣어 보도록

하죠… 그럼 최초에 niceguy.age 호출시 man 에 있는지 확인 합니다.. 물론 없습니다.

그럼 man.prototype 에서 찾아 봅니다.. 자 있습니다.. 13을 반환 합니다.

niceguy.hasOwnProperty 를 호출 합니다. hasOwnProperty 메소드는 해당 객체 자체에 있는

프로퍼티 일 경우는 true , prototype 등으로 상속 받은 경우는 false 를 반환 합니다..

당연히 man.prototype 에서 가져온 age 이기 떄문에 false 를 반환 합니다.

그 뒤에 niceguy.age 의 값을 변경해 봅니다..

이때 주의하셔야 할것이 man.prototype 의 age 의 값을 변경하지 않는다는 겁니다..

man 자체에 age 란 변수를 만들고 그뒤에 14 값을 넣습니다..

이제 niceguy.hasOwnProperty 를 호출하면 true 를 반환 하는것을 알수 있습니다..

사실 prototype을 사용하실때 주의 하셔야 할점도 있습니다.

prototype.js 를 보신분들은 알고 계시겠지만 내장 타입도 확장을 함으로써 편하게 사용하실수

있게 만들어 놨습니다.

   

  1. Array.prototype.clear = function(){  
  2.         this.length = 0;  
  3. }  
  4.   
  5. var arr = [1, 2, 3];  
  6.   
  7. alert(arr); //[1, 2, 3]  
  8.   
  9. arr.clear()  
  10.   
  11. alert(arr); // []   

예제 소스가 한없이 저렴하네요 -_-;;;

상기 소스 처럼 내장 타입도 확장할수 있지만 Object.prototype 만은 테스트 용도가 아닌경우

확장 하길 권하지 않습니다… 왜 그런지는 충분히 아실거라 생각합니다……

사실 prototype 은 저정도 이외에도 내부적으론 좀더 복잡하게

돌아가는거 같습니다만 ….. 그건 좀더 확인해 본뒤에 글을 적도록 하죠~

업무시간에 적느라 눈치가 보이네요 -_-;;;;;

긴글 읽느라 고생하셨습니다…

멋진인생님 블로그참조

http://run2you.tistory.com/12

javascript prototype 이란 무엇인가???? (1부)

Posted 2008/06/16 01:51

최신 배포된 자바스크립트 프레임 워크들에 관심을 가지고 소스를 보다보면

prototype 이란게 나옵니다….

여기서 prototype 이란 prototype.js 의 prototype 하곤 틀린것입니다.  그럼 prototype 이란 무엇

이고 어떻게 써야 할건가 이번 시간에 한번 보도록 하죠..(무슨 선생님 같습니다 -_-;;;;;)

 

  1. var man = function(name){  
  2.      this.name = name;  
  3. }  
  4.   
  5. var niceguy = new man(‘멋진인생’);  
  6.   
  7. alert(niceguy.name);  

생성자 함수를 사용하여 객체를 하나 생성했습니다. 객체의 프로퍼티에 접근하기 위해선

상기 소스 처럼 사용해도 되나.. 우리는 인스턴스 메소드를 사용해서 name 을 가져오는

getName 메소드를 만들어 보도록 할겁니다.

  1. var man = function(name){  
  2.      this.name = name;  
  3.      this.getName = function(){  
  4.         alert(this.name);  
  5.      }  
  6. }  
  7.   
  8. var niceguy = new man(‘멋진인생’);    
  9. niceguy.getName();  
  10.   
  11. var panda     = new man(‘쿵후팬더’);    
  12. panda.getName();     

 자 멋지게 된거 같죠!!  그러나 사실 잘보시면 알겠지만 객체 마다 name 프로퍼티와 getName

 메소드가 포함되어 있습니다.. 사실 name 프로퍼티는 각 객체 마다 값이 틀리지만 getName

 메소드는 동일한 메소드이기 때문에 사실 객체마다 메소드가 존재해서 부피를 키우는 꼴이 되는

거죠.. 그럼 다른 방법은 없을까요..

  1. var man = function(name){  
  2.      this.name = name;  
  3. }  
  4.   
  5. var getName = function(){  
  6.       alert(this.name);  
  7. }    
  8.   
  9. var niceguy = new man(‘멋진인생’);    
  10. getName.call(niceguy);  
  11.   
  12. var panda     = new man(‘쿵후팬더’);    
  13. getName.call(panda);    

자 멋지게 된거 같죠!! 근데 사실 이렇게 함수로 빼서 call, apply 등으로 사용하게 되면, 일단

하나의 함수로 다같이 사용하는건 맞습니다. 맞고요~~ 근데 이렇게 되었을시 일단 지저분하고,

메소드가 계속 추가 되었을시 관리는 누가 하나요???(전 못합니다 -_-;;;)

사실 모듈화된 javascript 를 잘 만들려면

객체형식으로 분리해서 관리 하여야 합니다.. 이제 최종적으로 수정해 볼까요..

  1. var man = function(name){  
  2.      this.name = name;  
  3. }  
  4.   
  5. man.prototype.getName = function(){  
  6.     alert(this.name);  
  7. }  
  8.   
  9. var niceguy = new man(‘멋진인생’);    
  10. niceguy.getName();  
  11.   
  12. var panda     = new man(‘쿵후팬더’);    
  13. panda.getName();  

뭔지 모르겠지만 일단 잘 작동 합니다….

도대체 무슨일이 일어 난걸까요??? 살짝 흥분도 됩니다(저 혼자)

사실 function 함수 엔 Prototype 객체를 가르키는 prototype 이란 프로퍼티가 하나 존재합니다.

이 Prototype 객체에 공유되어  있는 프로퍼티와 메소드가 객체 생성시 해당 생성자 function 으로

생성한 모든 인스턴스에서 공유됩니다…

글이 길어지고 밤도 깊어지므로 더 자세한 내용은 2부로 넘기도록 하겠습니다..

회사에서 막간을 이용해 글을 이어 나가 봅니다..

모든 function 에는 Prototype 객체를 가르키는 prototype 이라는 프로퍼티가 있다고 했습니다.

그럼 그 prototype 에는 사실 딱 하나의 프로퍼티가 존재 합니다.

그 이름 하여 constructor 입니다..  

   

  1. var man = function(name){  
  2.     this.name = name;  
  3.     this.getName = function(){  
  4.         alert(this.name);  
  5.     }  
  6. }  
  7.   
  8. alert(a.prototype.constructor);  

해당 prototype의 constructor 는 prototype 을 가지고 있는 function 을 가르킵니다.

일단 이부분은 나중에 다시 설명하도록 하고

이전 글 마지막에서 보았듯이 어떻게 prototype 에서 각 객체의 인스턴스로 프로퍼티들을 공유 할까

요… 그건 상속과도 비슷한 개념입니다…

   

  1. var man = function(name){  
  2.     this.name = name;  
  3. }  
  4.   
  5. man.prototype.getName = function(){  
  6.         alert(this.name);                     
  7. }  
  8.   
  9.   
  10. var niceguy = new man(“멋진인생”);  
  11. niceguy.getName();  


상기 소스를 보시면 man 생성자 함수를 통하여 객체를 생성했습니다.

niceguy 인스턴스에는 분명 getName 이 존재 하지 않으나 getName 은 정상적으로 작동하게 되죠.

사실 원리는 이렇습니다.. 최초 호출시 man 자체에서 getName을 찾아 봅니다.

당연히 없겠죠.. 그럼 man.prototype 에서 getName 을 찾아 봅니다. 물론 있습니다.. 그럼 반환

하겠죠…. 만약에 man.prototype 에서 조차도 없다. 그럴땐 더 상위로 가봅니다..

즉 Object.prototype 에서 찾아 보는겁니다.  이마저 없을땐 오류가 발생하는거죠…

그림으로 볼까요? (필요 없을려나 -_-;;;)

사용자 삽입 이미지

자 관련 소스를 하나 볼까요 ^^;;

   

  1. var man = function(name){  
  2.     this.name = name;  
  3.     this.getName = function(){  
  4.         alert(‘man:’ + this.getName);  
  5.     }  
  6. }  
  7.   
  8. man.prototype.getName = function(){  
  9.         alert(‘man.prototype:’+this.name);                    
  10. }  
  11.   
  12.   
  13. Object.prototype.getName = function(){  
  14.         alert(‘Object.prototype:’+this.name);                     
  15. }                 
  16.   
  17. var niceguy = new man(“멋진인생”);  
  18. niceguy.getName();  

역시 예상대로 man 의 getName 을 가져오는군요….

이렇듯 prototype 베이스 상속이란건 실제로 값을 가져다가 복사하는게 아닌

상위로 찾아가면서 값을 구하는 거예요..

그럼 prototype 에서의 읽기는 그렇다 치고 쓰기는 어떨까요??

   

  1. var man = function(name){  
  2.     this.name = name;  
  3. }  
  4.   
  5. man.prototype.age = ’13’  
  6.   
  7. var niceguy = new man(‘멋진인생’);  
  8.   
  9. alert(niceguy.age) // 13 반환  
  10. alert(niceguy.hasOwnProperty(“age”)); // false 반환  
  11. niceguy.age = ’14’;  
  12. alert(niceguy.age)   // 14 반환         
  13. alert(niceguy.hasOwnProperty(“age”)); // true 반환      

age 프로퍼티를 하나 추가하려 합니다.. 보통 man 자체에 넣겠지만 prototype 에 넣어 보도록

하죠… 그럼 최초에 niceguy.age 호출시 man 에 있는지 확인 합니다.. 물론 없습니다.

그럼 man.prototype 에서 찾아 봅니다.. 자 있습니다.. 13을 반환 합니다.

niceguy.hasOwnProperty 를 호출 합니다. hasOwnProperty 메소드는 해당 객체 자체에 있는

프로퍼티 일 경우는 true , prototype 등으로 상속 받은 경우는 false 를 반환 합니다..

당연히 man.prototype 에서 가져온 age 이기 떄문에 false 를 반환 합니다.

그 뒤에 niceguy.age 의 값을 변경해 봅니다..

이때 주의하셔야 할것이 man.prototype 의 age 의 값을 변경하지 않는다는 겁니다..

man 자체에 age 란 변수를 만들고 그뒤에 14 값을 넣습니다..

이제 niceguy.hasOwnProperty 를 호출하면 true 를 반환 하는것을 알수 있습니다..

사실 prototype을 사용하실때 주의 하셔야 할점도 있습니다.

prototype.js 를 보신분들은 알고 계시겠지만 내장 타입도 확장을 함으로써 편하게 사용하실수

있게 만들어 놨습니다.

   

  1. Array.prototype.clear = function(){  
  2.         this.length = 0;  
  3. }  
  4.   
  5. var arr = [1, 2, 3];  
  6.   
  7. alert(arr); //[1, 2, 3]  
  8.   
  9. arr.clear()  
  10.   
  11. alert(arr); // []   

예제 소스가 한없이 저렴하네요 -_-;;;

상기 소스 처럼 내장 타입도 확장할수 있지만 Object.prototype 만은 테스트 용도가 아닌경우

확장 하길 권하지 않습니다… 왜 그런지는 충분히 아실거라 생각합니다……

사실 prototype 은 저정도 이외에도 내부적으론 좀더 복잡하게

돌아가는거 같습니다만 ….. 그건 좀더 확인해 본뒤에 글을 적도록 하죠~

업무시간에 적느라 눈치가 보이네요 -_-;;;;;

긴글 읽느라 고생하셨습니다…

javascript의 scope chain과 closure에 대한 설명은 간단히 구글 검색을 해보면, 얼마든지 읽어 볼 수 있습니다. 그럼에도 내가 여기에 다시 이 내용을 정리하는 이유는 내가 스스로 정리할 필요에서 이기도 하고, 내가 이해하고 있는 방식이, 어떤 사람들에게는 좀 더 쉽게 이해 될 수 있기 때문입니다. 
javascript에서의 변수의 유효범위 즉, scope는 직관적으로 이해하기가 쉽지 않다는 것을 javascript를 사용하여 개발해본 경험이 있는 분들은 다들 알고 있을 것입니다. 이 문제는 javascript의 scope chain이라는 주제로 알려져 있으며, javascript를 제대로 이해하기 위한 마지막 문법이라고 볼 수 있을 것 같습니다. javascript에서의 scope chain이란 수 많은 함수 호출의 연결로 이루어져 작성된 코드가 있다고 했을 때, 그 코드의 각 함수내에서 사용하는 변수나 함수가 어느 영역의 것을 참조하게 되는가에 대한 문제입니다. 
이 scope chain의 this 키워드의 사용과는 전혀 관련이 없는데, 이후 혼란을 방지하기 위해서 이 구분을 먼저 하고 가도록 하겠습니다.
var a=1;
function func1(){
    alert(a);
}

func1();   //1)함수 호출

var o = new Object();
o.a = 2;
o.func2 = function func2(){
                 alert(this.a);
              };
o.func2(); //2)메소드 호출

function func3(){
    alert(this.a); //->도대체 this.a는 무엇일까요?
}

func3(); //3)함수 호출

alert(this.a); //4)전역 script

위 코드에서 1)함수 호출에서 보듯이 함수는 어떤 객체와도 연관지어 지지 않은 상태에서 호출된 것입니다. 2)메소드 호출은 객체와 연관되어 있는 함수 있것이지요. this 키워드는 함수안에서 사용되며, 이 함수호출의 대상이 되는 객체를 참조하는 키워드 입니다. 여기서는 o 가 되겠습니다.
그런데. 3)함수 호출의 경우는 어떨까요? 여기에서 사용된 this는 무엇을 의미할까요? 바로 전역객체를 의미합니다. 보통 전역객체는 브라우저에서는 window객체가 됩니다. 브라우저의 javascript엔진은 javascript를 실행하기 전에 전역객체인 window객체를 먼저 만들게 되는 것이지요. 그렇다면 결국은 함수호출도 궁극적으로는 메소드호출이다 말할 수 있으며 그것은 window라는 객체에 대해서 호출된 것입니다. 
마지막입니다. 그럼 4)전역 script의 실행에서 도대체 this는 무엇일까요? 전역객체인 window 객체에 해당합니다. 이것은 무엇을 뜻하냐면, 전역 script는 그 전체가 하나의 가상 함수안의 코드이다라고 말할 수있으며, 그것은 window객체에 대해서 호출되었다라고 해석해 볼 수 있습니다. 여기서 this.a는 예제코드 1라인에서 선언한 a 에 해당합니다.(이에 대한 설명이 좀더 필요한데 아래에서 설명될 것입니다)
결론적으로 말해서, javascript의 모든 코드는 함수안에 존재하는 것이며(심지어 전역script도 가상의 함수의 내부 코드로 생각해볼 수 있다), 궁극적으로는 특정 객체(전역script나 함수호출의 경우는 전역객체)에 대해서 호출되는 메소드이다라고 말할 수 있는 것이고, this라는 키워드는 메소드가 호출되는 대상 객체를 지칭하는 것이지요.
이제 함수와 메소드는 궁극적으로 동일한 것으로 이해되었으니, 아래에서는 모두 함수로 통일합니다.
지금 까지 함수가 실행될 때 함수안에서 참조되는 this라는 객체에 대해서 설명했는데, this처럼 직접 참조할 수는 없지만 암묵적으로 생성되어 사용되는 객체가 하나 더 있습니다. 그것은 바로 실행객체라는 친구입니다. 실행객체는 함수가 호출될 때, 함수 내부의 실행문맥에서 생성되며 함수 내부의 코드상에 선언된 변수와 함수를 property로 저장하고 있는 객체입니다.
함수가 호출될 때 함수내부의 코드가 실행되기 전에 일단 this키워드로 참조되는 객체가 정해지며, 또한, 실행객체가 추가로 생성됩니다. 그런 후에 함수 내부의 코드는 parsing단계와 실행단계를 거칩니다. parsing단계에서 함수내부 코드에 선언된 모둔 변수와 함수가 실행객체의 property로 세팅됩니다. 그런 후에 코드가 실행단계에 이르면, this로 참조되는 변수와 함수는 this객체에서 찾게 되고, 그렇지 않은 변수와 함수는 실행객체에서 찾게 됩니다. 
var a =1 ;
function func(){
   var b =2;
   alert(b); // 1)실행객체에서 b를 찾아낸다.
   alert(this.c)// 2)this 객체에서 c를 찾아낸다.
   alert(a); //3)실행객체에도 없고 this객체에도 없는데 어디서 찾을까요?
}
var o = new Object();
o.c = 3;
o.f = func;

o.f();

그렇다면, 위의 예제 코드 3)라인에서 보여준 것 처럼 this로 참조하는 변수와 함수도 아니고, 내부코드에서 선언되어 실행객체에서 찾을 수도 없는 변수와 함수는 어떻게 찾아 내는 것일까요? 바로 여기서 scope chain의 개념이 시작합니다.
이 이야기를 시작하기 전에 전역객체인 window객체의 역할에 대해서 한가지 더 집고 넘어가야 합니다.
모든 함수는 실행시에 this객체와 실행객체를 가지게 된다고 설명했습니다. 두개의 객체는 전적으로 서로 다른 것입니다. 그런데, 한가지 예외가 있습니다. 전역script의 영역에 해당하는 가상의 함수의 실행의 경우와 오브젝트에 대해서 호출되지 않은 전역함수의 호출의 경우에는 this객체와 실행객체가 구분되지 않는 다는 것입니다. 이 경우에는 window객체가 this객체와 실행객체의 역할을 모두 수해합니다. 따라서, 전역 script영역에서 var 키워드로 선언한 것은 window객체의 property가 되며, 전역script나 전역 함수에서 this키워드로 참조할 수 있는 것입니다. 
이제 scope chain의 문제로 넘어가지요.
scope chain을 이해하기 위한 시작은 함수선언은 그것을 감싸고 있는 외부코드가 parsing되는 단계에서 함수선언을 만나면 함수객체가 만들어지고, 그것이 실행객체에 property로서 세팅되는 시점 부터입니다. 중요한 것은 이 시점에 외부코드의 실행문맥에서 가지고 있는 scope chain이 함수객체에 세팅된다는 것입니다. 이후 함수가 실행될 때 새로이 실행객체가 하나가 더 만들어진 후, 함수객체에 세팅된 scope chain에 더해져서 호출된 함수내부의 실행문맥에서 scope chain을 형성하게 됩니다.
다음의 코드로 이를 더 알기 쉽게 설명해보겠습니다.
//1-0)아래 script 실행시 전역객체가 실행객체의 역할을 함
var a; //1-1)global객체에 저장됨
function outer(){ //1-2)함수 선언이 함수객체가 되어 global객체에 저장됨
   //2-0)코드 실행시 실행객체1이 생성됨
   var b; //2-1)실행객체1에 저장됨
   function inner(){ //2-2)함수선언이 함수객체가 되어 실행객체1에 저장됨
      //3-0)코드 실행시 실행객체2에 저장됨
      var c; //3-1)실행객체 2에 저장돔
      //3-2)아래 코드 호출시 scope chain은 실행객체2->실행객체1->global객체
      alert(a); //global객체에서 발견
      alert(b); //실행객체1에서 발견
      alert©; //실행객체2에서 발견
   }
   inner(); //2-3)이코드 호출시 scope chain 은 실행객체1 -> global객체
}

outer(); //1-3) 이 코드 호출시 scope chain은 global객체

전역 script영역에서는 global객체가 이미 1개 존재하여 실행객체의 역할을 한다고 했던 것을 기억한다면, scope chain은 함수선언이 파싱단계에서 함수객체로 만들어질 때, 그 함수선언을 포함하고 있는 코드의 실행문맥에 존재하는 scope chain을 넘겨 받게 되고, 나중에 이 함수가 실제 호출될 때, 함수객체가 가지고 있는 scope chain에 실행시 새로 생성한 실행객체를 추가하여 scope chain을 구성하게 되는 중첩된 구조라고 이해하면 되겠습니다.

그럼, 다음으로는 javascript 궁극의 문법중의 하나인 closure에 대한 설명을 하겠습니다. closure와 관련된 문제는 함수를 return하는 함수의 경우에도 발생하는 사안입니다.
function outer(x){
    return function inner(){ //함수객체가 만들어질때,  outer함수의 scope chain이 세팅된다.
         alert(x);
    }
}
var inner = outer(3); //1)
inner(); //2)
 1)라인 코드 실행시 outer함수 내부에서는 실행객체가 하나 생성됩니다. 그리고 나서 함수객체를 리턴하는데, 이 함수객체의 scope chain에는 outer함수의 실행객체가 포함되어 있습니다. 다음에 2)라인의 코드가 실행되었을 때, x 라는 변수를 도대체 어디서 참조할까 이상해 보이겠지만, scope chain을 검색하다가 이전에 outer함수실행시 생성되었던 실행객체에서 x라는 변수를 만나게 되는 것입니다.
여기서는 설명 안하겠지만, closure와 관련된 개념을 사용하여, 변수의 은닉화같은 흉내를 낼수도 있다.  closure와 관련한 실용성을 나중에 다시 한번 정리하여 소개할 기회를 갖도록 하겠다.
여기까지 조금 장황하지만, 내가 이해하고 있는 javascript의 scope chain에 대해서 설명하였습니다. 내가 이해하고 있는 방식을 설명하고 있으므로, javascript의 spec의 설명과 일치하지 않는 것도 있겠습니다만, 제 설명이 공감이 가는 분들도 많으시리라고 봅니다. 
핵심이 되는 문장만 따로 정리하자면서 설명을 마치겠습니다.
1)모든 함수는 궁극적으로 객체에 대한 메소드이며 이 객체는 this로서 참조된다.
2)전역 script와 전역함수의 경우에는 대상객체가 window객체이다. 
3)모든 함수의 실행시에 this 객체이외에 실행객체도 하나 생성된다.
4)실행객체는  var 선언 변수와 함수선언을 parsing단계에서 property로 저장한다.
5)전역script와 전역함수의 경우에는 this와 실행객체가 구분되지 않고 모두 window객체이다. 
6)parsing단계에서 함수객체가 생성되며, 이때 현재의 scope chain이 함수객체에 세팅된다.
7)함수호출 시에 새로 생성된 실행객체가 함수개체에 저장된 scope chain에 추가되어 새로운 scope chain을 구성한다.
this is from robert’s blog

Closures

Now when you hopefully have gotten a better grasp of what scope is, let’s add closures to the mix. Closures are expressions, usually functions, which can work with variables set within a certain context. Or, to try and make it easier, inner functions referring to local variables of its outer function create closures. For instance:

1.function add (x) {
2.return function (y) {
3.return x + y;
4.};
5.}
6.var add5 = add(5);
7.var no8 = add5(3);
8.alert(no8); // Returns 8

Whoa, whoa! What just happened? Let’s break it down:

  1. When the add function is called, it returns a function.
  2. That function closes the context and remembers what the parameter x was at exactly that time (i.e. 5 in the code above)
  3. When the result of calling the add function is assigned to the variable add5, it will always know what x was when it was initially created.
  4. The add5 variable above refers to a function which will always add the value 5 to what is being sent in.
  5. That means when add5 is called with a value of 3, it will add 5 together with 3, and return 8.

So, in the world of JavaScript, the add5 function actually looks like this in reality:

1.function add5 (y) {
2.return 5 + y;
3.}
javascript의 scope chain과 closure에 대한 설명은 간단히 구글 검색을 해보면, 얼마든지 읽어 볼 수 있습니다. 그럼에도 내가 여기에 다시 이 내용을 정리하는 이유는 내가 스스로 정리할 필요에서 이기도 하고, 내가 이해하고 있는 방식이, 어떤 사람들에게는 좀 더 쉽게 이해 될 수 있기 때문입니다. 
javascript에서의 변수의 유효범위 즉, scope는 직관적으로 이해하기가 쉽지 않다는 것을 javascript를 사용하여 개발해본 경험이 있는 분들은 다들 알고 있을 것입니다. 이 문제는 javascript의 scope chain이라는 주제로 알려져 있으며, javascript를 제대로 이해하기 위한 마지막 문법이라고 볼 수 있을 것 같습니다. javascript에서의 scope chain이란 수 많은 함수 호출의 연결로 이루어져 작성된 코드가 있다고 했을 때, 그 코드의 각 함수내에서 사용하는 변수나 함수가 어느 영역의 것을 참조하게 되는가에 대한 문제입니다. 
이 scope chain의 this 키워드의 사용과는 전혀 관련이 없는데, 이후 혼란을 방지하기 위해서 이 구분을 먼저 하고 가도록 하겠습니다.
var a=1;
function func1(){
    alert(a);
}

func1();   //1)함수 호출

var o = new Object();
o.a = 2;
o.func2 = function func2(){
                 alert(this.a);
              };
o.func2(); //2)메소드 호출

function func3(){
    alert(this.a); //->도대체 this.a는 무엇일까요?
}

func3(); //3)함수 호출

alert(this.a); //4)전역 script

위 코드에서 1)함수 호출에서 보듯이 함수는 어떤 객체와도 연관지어 지지 않은 상태에서 호출된 것입니다. 2)메소드 호출은 객체와 연관되어 있는 함수 있것이지요. this 키워드는 함수안에서 사용되며, 이 함수호출의 대상이 되는 객체를 참조하는 키워드 입니다. 여기서는 o 가 되겠습니다.
그런데. 3)함수 호출의 경우는 어떨까요? 여기에서 사용된 this는 무엇을 의미할까요? 바로 전역객체를 의미합니다. 보통 전역객체는 브라우저에서는 window객체가 됩니다. 브라우저의 javascript엔진은 javascript를 실행하기 전에 전역객체인 window객체를 먼저 만들게 되는 것이지요. 그렇다면 결국은 함수호출도 궁극적으로는 메소드호출이다 말할 수 있으며 그것은 window라는 객체에 대해서 호출된 것입니다. 
마지막입니다. 그럼 4)전역 script의 실행에서 도대체 this는 무엇일까요? 전역객체인 window 객체에 해당합니다. 이것은 무엇을 뜻하냐면, 전역 script는 그 전체가 하나의 가상 함수안의 코드이다라고 말할 수있으며, 그것은 window객체에 대해서 호출되었다라고 해석해 볼 수 있습니다. 여기서 this.a는 예제코드 1라인에서 선언한 a 에 해당합니다.(이에 대한 설명이 좀더 필요한데 아래에서 설명될 것입니다)
결론적으로 말해서, javascript의 모든 코드는 함수안에 존재하는 것이며(심지어 전역script도 가상의 함수의 내부 코드로 생각해볼 수 있다), 궁극적으로는 특정 객체(전역script나 함수호출의 경우는 전역객체)에 대해서 호출되는 메소드이다라고 말할 수 있는 것이고, this라는 키워드는 메소드가 호출되는 대상 객체를 지칭하는 것이지요.
이제 함수와 메소드는 궁극적으로 동일한 것으로 이해되었으니, 아래에서는 모두 함수로 통일합니다.
지금 까지 함수가 실행될 때 함수안에서 참조되는 this라는 객체에 대해서 설명했는데, this처럼 직접 참조할 수는 없지만 암묵적으로 생성되어 사용되는 객체가 하나 더 있습니다. 그것은 바로 실행객체라는 친구입니다. 실행객체는 함수가 호출될 때, 함수 내부의 실행문맥에서 생성되며 함수 내부의 코드상에 선언된 변수와 함수를 property로 저장하고 있는 객체입니다.
함수가 호출될 때 함수내부의 코드가 실행되기 전에 일단 this키워드로 참조되는 객체가 정해지며, 또한, 실행객체가 추가로 생성됩니다. 그런 후에 함수 내부의 코드는 parsing단계와 실행단계를 거칩니다. parsing단계에서 함수내부 코드에 선언된 모둔 변수와 함수가 실행객체의 property로 세팅됩니다. 그런 후에 코드가 실행단계에 이르면, this로 참조되는 변수와 함수는 this객체에서 찾게 되고, 그렇지 않은 변수와 함수는 실행객체에서 찾게 됩니다. 
var a =1 ;
function func(){
   var b =2;
   alert(b); // 1)실행객체에서 b를 찾아낸다.
   alert(this.c)// 2)this 객체에서 c를 찾아낸다.
   alert(a); //3)실행객체에도 없고 this객체에도 없는데 어디서 찾을까요?
}
var o = new Object();
o.c = 3;
o.f = func;

o.f();

그렇다면, 위의 예제 코드 3)라인에서 보여준 것 처럼 this로 참조하는 변수와 함수도 아니고, 내부코드에서 선언되어 실행객체에서 찾을 수도 없는 변수와 함수는 어떻게 찾아 내는 것일까요? 바로 여기서 scope chain의 개념이 시작합니다.
이 이야기를 시작하기 전에 전역객체인 window객체의 역할에 대해서 한가지 더 집고 넘어가야 합니다.
모든 함수는 실행시에 this객체와 실행객체를 가지게 된다고 설명했습니다. 두개의 객체는 전적으로 서로 다른 것입니다. 그런데, 한가지 예외가 있습니다. 전역script의 영역에 해당하는 가상의 함수의 실행의 경우와 오브젝트에 대해서 호출되지 않은 전역함수의 호출의 경우에는 this객체와 실행객체가 구분되지 않는 다는 것입니다. 이 경우에는 window객체가 this객체와 실행객체의 역할을 모두 수해합니다. 따라서, 전역 script영역에서 var 키워드로 선언한 것은 window객체의 property가 되며, 전역script나 전역 함수에서 this키워드로 참조할 수 있는 것입니다. 
이제 scope chain의 문제로 넘어가지요.
scope chain을 이해하기 위한 시작은 함수선언은 그것을 감싸고 있는 외부코드가 parsing되는 단계에서 함수선언을 만나면 함수객체가 만들어지고, 그것이 실행객체에 property로서 세팅되는 시점 부터입니다. 중요한 것은 이 시점에 외부코드의 실행문맥에서 가지고 있는 scope chain이 함수객체에 세팅된다는 것입니다. 이후 함수가 실행될 때 새로이 실행객체가 하나가 더 만들어진 후, 함수객체에 세팅된 scope chain에 더해져서 호출된 함수내부의 실행문맥에서 scope chain을 형성하게 됩니다.
다음의 코드로 이를 더 알기 쉽게 설명해보겠습니다.
//1-0)아래 script 실행시 전역객체가 실행객체의 역할을 함
var a; //1-1)global객체에 저장됨
function outer(){ //1-2)함수 선언이 함수객체가 되어 global객체에 저장됨
   //2-0)코드 실행시 실행객체1이 생성됨
   var b; //2-1)실행객체1에 저장됨
   function inner(){ //2-2)함수선언이 함수객체가 되어 실행객체1에 저장됨
      //3-0)코드 실행시 실행객체2에 저장됨
      var c; //3-1)실행객체 2에 저장돔
      //3-2)아래 코드 호출시 scope chain은 실행객체2->실행객체1->global객체
      alert(a); //global객체에서 발견
      alert(b); //실행객체1에서 발견
      alert(c); //실행객체2에서 발견
   }
   inner(); //2-3)이코드 호출시 scope chain 은 실행객체1 -> global객체
}

outer(); //1-3) 이 코드 호출시 scope chain은 global객체

전역 script영역에서는 global객체가 이미 1개 존재하여 실행객체의 역할을 한다고 했던 것을 기억한다면, scope chain은 함수선언이 파싱단계에서 함수객체로 만들어질 때, 그 함수선언을 포함하고 있는 코드의 실행문맥에 존재하는 scope chain을 넘겨 받게 되고, 나중에 이 함수가 실제 호출될 때, 함수객체가 가지고 있는 scope chain에 실행시 새로 생성한 실행객체를 추가하여 scope chain을 구성하게 되는 중첩된 구조라고 이해하면 되겠습니다.

그럼, 다음으로는 javascript 궁극의 문법중의 하나인 closure에 대한 설명을 하겠습니다. closure와 관련된 문제는 함수를 return하는 함수의 경우에도 발생하는 사안입니다.
function outer(x){
    return function inner(){ //함수객체가 만들어질때,  outer함수의 scope chain이 세팅된다.
         alert(x);
    }
}
var inner = outer(3); //1)
inner(); //2)
 1)라인 코드 실행시 outer함수 내부에서는 실행객체가 하나 생성됩니다. 그리고 나서 함수객체를 리턴하는데, 이 함수객체의 scope chain에는 outer함수의 실행객체가 포함되어 있습니다. 다음에 2)라인의 코드가 실행되었을 때, x 라는 변수를 도대체 어디서 참조할까 이상해 보이겠지만, scope chain을 검색하다가 이전에 outer함수실행시 생성되었던 실행객체에서 x라는 변수를 만나게 되는 것입니다.
여기서는 설명 안하겠지만, closure와 관련된 개념을 사용하여, 변수의 은닉화같은 흉내를 낼수도 있다.  closure와 관련한 실용성을 나중에 다시 한번 정리하여 소개할 기회를 갖도록 하겠다.
여기까지 조금 장황하지만, 내가 이해하고 있는 javascript의 scope chain에 대해서 설명하였습니다. 내가 이해하고 있는 방식을 설명하고 있으므로, javascript의 spec의 설명과 일치하지 않는 것도 있겠습니다만, 제 설명이 공감이 가는 분들도 많으시리라고 봅니다. 
핵심이 되는 문장만 따로 정리하자면서 설명을 마치겠습니다.
1)모든 함수는 궁극적으로 객체에 대한 메소드이며 이 객체는 this로서 참조된다.
2)전역 script와 전역함수의 경우에는 대상객체가 window객체이다. 
3)모든 함수의 실행시에 this 객체이외에 실행객체도 하나 생성된다.
4)실행객체는  var 선언 변수와 함수선언을 parsing단계에서 property로 저장한다.
5)전역script와 전역함수의 경우에는 this와 실행객체가 구분되지 않고 모두 window객체이다. 
6)parsing단계에서 함수객체가 생성되며, 이때 현재의 scope chain이 함수객체에 세팅된다.
7)함수호출 시에 새로 생성된 실행객체가 함수개체에 저장된 scope chain에 추가되어 새로운 scope chain을 구성한다.
this is from robert’s blog

Closures

Now when you hopefully have gotten a better grasp of what scope is, let’s add closures to the mix. Closures are expressions, usually functions, which can work with variables set within a certain context. Or, to try and make it easier, inner functions referring to local variables of its outer function create closures. For instance:

1.function add (x) {
2.return function (y) {
3.return x + y;
4.};
5.}
6.var add5 = add(5);
7.var no8 = add5(3);
8.alert(no8); // Returns 8

Whoa, whoa! What just happened? Let’s break it down:

  1. When the add function is called, it returns a function.
  2. That function closes the context and remembers what the parameter x was at exactly that time (i.e. 5 in the code above)
  3. When the result of calling the add function is assigned to the variable add5, it will always know what x was when it was initially created.
  4. The add5 variable above refers to a function which will always add the value 5 to what is being sent in.
  5. That means when add5 is called with a value of 3, it will add 5 together with 3, and return 8.

So, in the world of JavaScript, the add5 function actually looks like this in reality:

1.function add5 (y) {
2.return 5 + y;
3.}

javascript는 함수 실행시에 아래 두 개의 객 체의 도움을 받는다. arguments object: 실행될 함수에 전달된 매 개변수들과 관련된 속성을 가짐 call object 이 두 객체는 함께 동작하며, 다음과 같은 일 반적인 특징을 가진다. javascript 함수를 지원한다. 임시적으로 존재한다. 함수가 실행될 때 만들어졌다가 함수가 종 료되면 사라진다. call object는 argumets object를 참조한다. 이 포인터는 call object의 속성이다. 이 포인터는 “arguments"라는 이름으로 식 별된다.

이 포인터는 argument object이 속성에 접 근시에 사용된다. 이 포인터는 함수 실행 시 함수 몸체에서 사 용할 수 있다.
[the call object description]
call object는 특별한 임시 객체로 함수가 실 행되는 동안 존재한다. call object는 두 가지 중요한 목적을 가진다. argument object를 가리키는 포인터를 제공 한다. 함수 실행 시 scope chain을 관장한다. call object는 이 외의 다른 속성이나 기능이 없다.
[the pointer to the arguments object]
call object는 argument object를 가리키는 포인터를 제공한다. 이 속성이 바로 arguments 속성이다. arguments 속성을 통 해 argument object의 속성에 접근할 수 있 다.

javascript는 왜 arguments 속성을 call object에 넣어두었을까? 이유는 다음과 같 다. arguments object는 native object가 아니기 때문에 인스턴스를 만드는 등, 끌어다 쓸 방 법이 없다. 즉, arguments object의 속성에 접근할 수 있는 방법이 없는거다. 그래서 call object에 arguments object를 가리키는 포인터(속성)을 제공하는 것이다. call object 와 arguments object는 함수 실행시에 동시 에 만들어졌다가 함수 실행 종료와 함께 동 시에 사라진다. javascript의 함수 실행되는 데 일조하는 친구들이다. arguments 속성은 함수의 지역 변수와 함께 scope chain에 들어가야 한다. 그래야만 함 수 중첩시에 적절한 arguments object에 접 근할 수 있다.

[administer the scope chain]
call object는 함수 실행 중에만 존재한다. call object는 scope chain에 아래 나열한 것 들을 가져다놓는다. 함수 내에 선언된 지역 변수 함수에 넘겨진 매개 변수 함수 표현을 도와주는 arguments object의 포인터, 우리가 알고 있는 바로 그 arguments! 위에 나열한 것들은 모두 call object의 속성 들이다. call object는 global object와 결합 되어 처리한다. 모든 전역 변수들은 global object의 속성이다. 페이지가 초기화될 때 global object는 전역 변수들을 scope chain 에 올린다. call object의 목적은 scope chain 에 전역 변수에 앞서 지역 변수를 올리는 것 이다. javascript는 전역 변수와 지역 변수가 이름이 같다고 해도 지역 변수에 전역 변수 가 덮어써지지 않도록 한다. 함수 중첩 시에 도 각 함수들은 나름의 call object를 가진다.

요약하자. 두 개의 함수가 같은 이름을 가지 고 있을 때 그 독립성은 유지된다. 하위 객체의 scope chain에 있는 변수는 상 위 객체의 같은 이름을 가진 변수로 덮어써 지지 않는다. 중첩 함수들은 나름의 call object를 통해 지 역 변수의 독립성을 보장받는다. 함수 안의 call object는 scope chain의 최상위에 존재 한다. call object가 scope chain에서 사라지면, 하 위 객체의 변수들은 해당 함수 실행 시에 적 절한 scope에서 다시 접근할 수 있다.

javascript는 함수 실행시에 아래 두 개의 객 체의 도움을 받는다. arguments object: 실행될 함수에 전달된 매 개변수들과 관련된 속성을 가짐 call object 이 두 객체는 함께 동작하며, 다음과 같은 일 반적인 특징을 가진다. javascript 함수를 지원한다. 임시적으로 존재한다. 함수가 실행될 때 만들어졌다가 함수가 종 료되면 사라진다. call object는 argumets object를 참조한다. 이 포인터는 call object의 속성이다. 이 포인터는 “arguments”라는 이름으로 식 별된다.

이 포인터는 argument object이 속성에 접 근시에 사용된다. 이 포인터는 함수 실행 시 함수 몸체에서 사 용할 수 있다.
[the call object description]
call object는 특별한 임시 객체로 함수가 실 행되는 동안 존재한다. call object는 두 가지 중요한 목적을 가진다. argument object를 가리키는 포인터를 제공 한다. 함수 실행 시 scope chain을 관장한다. call object는 이 외의 다른 속성이나 기능이 없다.
[the pointer to the arguments object]
call object는 argument object를 가리키는 포인터를 제공한다. 이 속성이 바로 arguments 속성이다. arguments 속성을 통 해 argument object의 속성에 접근할 수 있 다.

javascript는 왜 arguments 속성을 call object에 넣어두었을까? 이유는 다음과 같 다. arguments object는 native object가 아니기 때문에 인스턴스를 만드는 등, 끌어다 쓸 방 법이 없다. 즉, arguments object의 속성에 접근할 수 있는 방법이 없는거다. 그래서 call object에 arguments object를 가리키는 포인터(속성)을 제공하는 것이다. call object 와 arguments object는 함수 실행시에 동시 에 만들어졌다가 함수 실행 종료와 함께 동 시에 사라진다. javascript의 함수 실행되는 데 일조하는 친구들이다. arguments 속성은 함수의 지역 변수와 함께 scope chain에 들어가야 한다. 그래야만 함 수 중첩시에 적절한 arguments object에 접 근할 수 있다.

[administer the scope chain]
call object는 함수 실행 중에만 존재한다. call object는 scope chain에 아래 나열한 것 들을 가져다놓는다. 함수 내에 선언된 지역 변수 함수에 넘겨진 매개 변수 함수 표현을 도와주는 arguments object의 포인터, 우리가 알고 있는 바로 그 arguments! 위에 나열한 것들은 모두 call object의 속성 들이다. call object는 global object와 결합 되어 처리한다. 모든 전역 변수들은 global object의 속성이다. 페이지가 초기화될 때 global object는 전역 변수들을 scope chain 에 올린다. call object의 목적은 scope chain 에 전역 변수에 앞서 지역 변수를 올리는 것 이다. javascript는 전역 변수와 지역 변수가 이름이 같다고 해도 지역 변수에 전역 변수 가 덮어써지지 않도록 한다. 함수 중첩 시에 도 각 함수들은 나름의 call object를 가진다.

요약하자. 두 개의 함수가 같은 이름을 가지 고 있을 때 그 독립성은 유지된다. 하위 객체의 scope chain에 있는 변수는 상 위 객체의 같은 이름을 가진 변수로 덮어써 지지 않는다. 중첩 함수들은 나름의 call object를 통해 지 역 변수의 독립성을 보장받는다. 함수 안의 call object는 scope chain의 최상위에 존재 한다. call object가 scope chain에서 사라지면, 하 위 객체의 변수들은 해당 함수 실행 시에 적 절한 scope에서 다시 접근할 수 있다.