출처 : http://beizix.tistory.com/entry/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-Function-%EC%9D%98-%EB%B9%84%EB%B0%80-prototype-%EA%B3%BC-new




자바스크립트는 프로토타입 prototype 속성과 new 연산자를 이용하여 새로운 인스턴스 객체를 생성할 수 있습니다. 프로토타입 속성에 추가된 메서드는 이 프로토타입을 근간으로 하는 모든 객체가 사용할 수 있는 공용 메서드가 됩니다. 그리고 명시적인 접근 제어자는 없어도, 클로저 closure 기법을 이용해서 private 속성을 정의할 수 있습니다.

이번 포스팅의 키워드는 아래와 같습니다.

  • function
  • prototype
  • new
  • this
  • private


function

자바스크립트에서 모든 객체는 자신만의 고유한 프로토타입 객체를 갖습니다. 이 프로토타입이 외부 객체와 연결되면, 연결된 외부 객체의 속성을 자신의 프로토타입으로 흡수합니다. 이 과정에서 자연스럽게 상속이 발생합니다. 자바스크립트에서 함수 Function 가 중요한 이유는 사용자가 정의할 수 있는 프로토타입 속성 prototype property 을 제공하는 유일한 객체이기 때문입니다.

function f () {}; 
var obj = {}; 
var array = []; 

f.prototype;        // Object 
obj.prototype;      // undefined 
array.prototype;    // undefined 



prototype

위의 f 함수 객체가 생성될 때 자동으로 부여 받은 프로토타입 속성은 다음과 같이 구성됩니다. 

f.prototype = {
    constructor : function f () {},
    __proto__ : Object
}


constructor 에는 f 함수가 생성될 때 사용된 함수 객체가 담겨 있습니다. __proto__ 는 현재 프로토타입에 연결된 부모 객체 나타냅니다. 파이어폭스, 사파리, 크롬은 이 속성을
__proto__ 라는 이름으로 노출하므로 개발자가 사용할 수 있지만, 다른 브라우저는 스크립트로 이 속성에 접근할 수 없습니다.  위의 경우 __proto__는 Object.prototype 과 연결되어 있음을 나타냅니다. Object.prototype 을 상속받은 f 함수는 부모가 정의한 toString hasOwnProperty 같은 메서드를 사용할 수 있습니다.

f.toString();    // "function f () {}"
f.hasOwnProperty('name'); // false


new

f 를 호출용 함수로만 사용한다면, 프로토타입 객체는 그다지 유용하지 않습니다. new 연산자를 앞에 붙이고 함수를 호출할 때, 프로토타입 객체는 중요한 역할을 수행합니다.

var result = f();      // result 는 undefined
var result = new f();  // result 는 Object 

f 는 텅 빈 함수이기에 반환값도 없지만, new 연산자와 함께 호출하면 호출된 함수의 프로토타입을 근간으로 하는 신규 객체가 반환됩니다. result 는 다음 구조를 갖습니다.

result = {
    __proto__ : {
constructor : function f () {},
__proto__ : Object

}


}


this

그리고 new 뒤의 함수를 실행하는데 여기서 this 와 함께 사용된 구문이 있다면, 신규 객체를 this 에 바인딩하여 구문을 실행합니다. 이를 살펴보기 위해, 사각형의 길이와 높이를 나타내는 widthheight, 그리고 사각형의 면적을 구하는 get_area 함수를 this 키워드와 함께 추가해 봅시다. 함수 이름도 의미에 맞게 rectangle 로 변경합니다.

function rectangle (w, h) {

this.width = w;
this.height = h;
this.get_area = function () {

return this.width * this.height;

}

};


var rect = new rectangle(10, 15);

this 에 바인딩된 신규 객체는 width, height, get_area 속성을 갖습니다. 이 때 rect의 구조는 다음과 같습니다.

rect = {
width : 10,
height : 15,
get_area : function () { … },
__proto__ : {
constructor : function rectangle () {...},
__proto__ : Object
}
}

신규 객체가 할당된
rect 는 의도한대로 동작합니다.

var
rect = new rectangle(10, 15);
rect.get_area(); // 150


내부적인 메카니즘을 제외하고 외형만 본다면, 함수 rectangle 마치 클래스처럼 동작합니다. new 연산자를 이용해 클래스의 새로운 인스턴스를 생성해냈고, 인스턴스는 자신의 속성과 메서드를 갖습니다. 이것이 다른 클래스 기반의 언어를 흉내낸 자바스크립트의 Pseudo Class 구현 원리입니다.



prototype 맴버

위의 rect 객체를 좀 더 보완해 보겠습니다. new 를 이용해 생성해낸 객체는 width, height, get_area 세가지 프로퍼티를 가집니다. width, height 는 객체별로 다를 수 있지만, get_area 는 동일합니다. rectangle 함수에서 파생된 객체들이 공유하게끔 만든 메서드를 일반적인 프로퍼티로 사용하는 것은 비합리적입니다.

이를 해결하기 위해 프로토타입이 사용됩니다.
rectangle.prototype 에 메서드를 추가하면, 이를 근간으로 하는 모든 신규 객체는 부모의 프로토타입 객체에 담긴 메서드를 사용할 수 있습니다.

function rectangle (w, h) {

this.width = w;
this.height = h;
};

rectangle.prototype.get_area = function () {
return this.width * this.height;
};

var rect1 = new a(7,5);
var rect2 = new a(10,7);

rect1.get_area();  // 35
rect2.get_area();  // 70

rect1 의 구조는 다음과 같습니다.

rect1 = {
width : 7,
height : 5,
__proto__ : {
get_name : function () {...},
constructor : function 
rectangle () {...},
__proto__ : Object
}
}
    
이제
rectangle 에서 파생된 객체는 어느 정도 제대로된 구성을 갖춘 듯 합니다. width height 는 자신만의 프로퍼티로 정의했고, 부모의 프로토타입에 선언된 get_area 함수를 공용 메서드 처럼 사용하고 있습니다.


private

하지만 여기서도 한가지 문제가 있습니다. 각 객체가 가진 width height 프로퍼티는 외부에 public 으로 노출되어 있어 악의적인 코드나 값들로 동작이 쉽게 변경됩니다.

var
rect1 = new rectangle(10,15);

rect1.width = ‘hacked !’;
rect1.get_area();  // NaN


앞에서 확인했듯이, new 연산자로 신규 객체를 생성할 때 this 키워드에 선언된 속성들이 신규 객체의 public 속성으로 할당됩니다. 이 점을 활용하여, 외부의 접근을 맊고자 하는 속성에 var 키워드를 주고, 외부에 공개될 속성에 this 키워드를 사용함으로써 해결할 수 있습니다.

function rectangle (w, h) {
var width = w,
 height = h;

this.get_area = function () {
    return width * height;
};
};

var
rect1 = new rectangle (10, 25);
var
rect2 = new rectangle (30, 20);

rect1.get_area();  // 250
rect2.get_area();  // 600


rect1 의 구조를 살펴보면 이전과 다른 점이 있습니다.

rect1 = {
get_area : function 
rectangle () { … },

__proto__ : Object
}


면적을 반환하는 get_area 메서드는 보이지만, 메서드 내에서 계산에 사용될 widthheight 는 감춰져 외부에 노출되지 않습니다. get_area 함수가 참조하는 width height 는 이 함수를 감싸 안은
rectangle 함수가 실행되는 시점(new 생성자와 함께)에 할당된 값을 참조하고 있습니다. 이는 클로저 패턴으로 width height private 이 됩니다. rect1 외부에서 절대 width height 프로퍼티를 변경하지 못합니다.

var
rect1 = new rectangle (10, 25);

rect1.width = ‘hacked !’;
rect1.get_area();  // 250


아쉬운 점은 get_area 프로토타입 맴버로 등록하지 못해, 신규 객체별로 접근 메서드를 가지고 있다는 점입니다. 
프로토타입에 등록되어 공용 메서드로 사용할 수 있는 이점은 여기서 사라집니다. 이럴 경우 별도의 부모 객체를 정의해서 공용 메서드나 상수 들을 위치시키고 이를 상속받는 방식으로 보완할 수 있습니다.


함수를 new 생성자로 호출하도록 강제하는 법

여기서 한가지 작업을 더 해보려고 합니다. 그동안 rectangle 함수는 new 연산자와 함께 사용하도록 정의한 생성자 함수입니다. 하지만 사용자가 new 를 빼먹고 호출하는 실수를 저지르면 예측하지 못한 결과가 나옵니다. 자바스크립트에서는 명시적인 클래스 선언이 없기에 이를 강제할 방법이 없습니다. 이를 보완하고자 생성자 역할의 함수 이름을 대문자로 시작하도록 정의하는 관례가 많이 쓰이고 있지만 선택은 개인의 몫입니다.

간단한 코딩을 추가하여, new 연산자와 함께 호출하든 그냥 호출하든 동일한 결과를 반환하도록 만들 수 있습니다. 생성자 함수 내부에서 this 가 해당 함수의 인스턴스인지를 확인하고, 그렇지 않은 경우 new와 함께 스스로를 재호출 하도록 처리하면 됩니다. 여기서 현재 실행중인 함수 자신을 나타내는 arguments.callee 속성이 사용됩니다.

function rectangle (w, h) {

if (!(this instanceof arguments.callee )) return new arguments.callee(w, h);

    var width = w,
     height = h;

    this.get_area = function () {
        return width * height;
    };
};

이제 new rectangle() 과 rectangle() 은 동일한 객체를 반환합니다. 물론, 두 경우 다 width height private 로 외부에서 접근할 수 없습니다.


Posted by motolies
,