프로그래밍/

자바스크립트 프로토타입

Jay Tech 2017. 3. 8. 21:05
반응형

자바스크립트에는 Object, Function, Array 등과 같이 다양한 기본 객체들이 있고 모든 객체는 자바스크립트의 객체 대빵인 Object를 확장하고 있다. 이러한 면을 보면 Java와 굉장히 비슷해 보여서 객체지향 언어라고 생각하기 쉽다. 하지만 자바스크립트는 이벤트와 함수 기반 언어이고 객체지향언어는 아니다.


자바스크립트는 함수 기반 언어인 Scheme을 기반으로 만들어진 뒤 Java에서 다양한 개념들을 가져왔다. 따라서 객체지향 언어는 아니지만 이에 비슷한 특징들을 지원한다. 


여기서 프로토타입(Prototype)이라는 것이 나온다. 


자바스크립트는 객체지향 개념을 지원하기 위해 프로토타입을 사용한다. 프로토타입을 이용해 대표적으로 구현할 수 있는 개념은 "상속" 이다. 


그럼 프로토타입이 무엇인지 알아보자. (사전적 의미 : 원형)

개발하면서 프로토타입을 전혀 쓰지 않아도 상관없다. 하지만 라이브러리르 사용하거나(jQuery와 같은) 깊이있게 다루려면 프로토타입은 반드시 이해하고 가야할 개념이다.


자바스크립트가 채택하고 있는 자바의 몇 가지 문법 중 대표적인 것은 new 키워드이다. 자바는 객체를 class로 정의하지만, 자바스크립트에서는 function으로 정의한다. 


1
2
3
4
5
6
function Person(name, address) {
    this.name = name;
    this.address = address;
}
 
var person = new Person("name""address");
cs

이렇게 정의할 수 있다. 이렇듯 자바스크립트에서 function은 자바의 class와 생성자를 합쳐 놓은 개념이다. 


This


자바스크립트에서는 함수가 4가지 방법으로 호출된다.


1. 일반 함수로의 호출

2. 멤버함수로의 호출

3. call() 함수를 이용한 호출

4. apply() 함수를 이용한 호출


call 과 apply는 function객체의 내장함수이다. 그러므로 정의하지 않고 그냥 쓸 수 있다. 둘의 차이는 매개변수를 문자열로 넘길지 배열로 넘길지의 차이이다.


this의 변화를 보자.


1
2
3
4
5
6
7
8
9
10
11
function what() {
    return this.toString();
}
 
var test = {
    what: what,
    toString: function () {
        return "[object test]";
    }
};
 
cs





첫 번째, 일반적 함수호출이다. 일반적으로 함수가 호출될 때에는 내부적으로 call()함수로 변형되어서 처리한다. 이때는 첫 번째 인자로 undefined를 넘긴다. 그래서 this의 기본값으로 window가 들어가게 된다.


두 번째, 멤버함수 호출인 경우 내부적으로 call() 함수를 호출할 때 첫 번째 인자로 멤버함수를 보유한 객체를 넘겨준다. 그래서 첫 번째 인자로 test가 넘어가기 때문에 this는 test가 된다.


같은 함수라도 멤버함수가 호출되는 방법이 다르면 this는 또 변경된다. 


 

이렇게 this는 함수나 스코프 기반으로 결정되는 것이 아니라 호출 방법에 따라 결정된다. 





프로토타입에 대해서 자세히 알아보자.


프로토타입은 다른 객체들과 공유되는 속성을 제공하는 객체이다. 

생성자 객체 생성 -> 객체는 내부적으로 prototype 속성을 활용하여 레퍼런스를 참조

공식문서에서는 이렇게 생성된 객체들 간의 prototype은 공유된다. 그리고 이를 상속하는 모든 객체에도 공유된다고  되어 있다. 그리고 생성자의 속성인 prototype 또한 하나의 객체라고 설명하는 부분이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person(name, address) {
    this.name = name;
    this.address = address;
}
 
Person.prototype.getName = function() {
    return this.name;
};
 
Person.prototype.getAddress = function() {
    return this.address;
};
 
var kim = new Person("kim","123");
var park = new Person("park""123");
 
cs



line 6 에 Person.prototype이라는 부분이 있다. 이것은 Person이라는 생성자에 prototype 속성을 설정하고 있는 것이다. 이후 새로 생성된 객체 kim과 park은 내부적으로 이 prototype 객체를 참고하여 prototype 객체가 가지고 있는 getName()과 getBlog() 함수를 사용할 수 있다는 것이다. 이는 표준에 명시된 생성자를 통해서 객체들이 프로토타입을 공유한다는 말인 것이다. 


또 다른 특징은 이미 정의된 프로토타입을 다시 수정할 수 있다는 것과

Person.prototype.getName = function {return "yes"}


프로토타입에 함수가 아닌 변수를 추가하여 공유할 수 있다는 것이다.

Person.prototype.getName = "me";


근데 이거 왜 쓸까?


그냥 이렇게 다 박으면 안되나?


1
2
3
4
5
6
7
8
9
10
11
function Person(name, address) {
    this.name = name;
    this.address = address;
    this.getName = function() {
        return this.name;
    };
    this.getAddress = function() {
        return this.address;
    }
}
 
cs


이렇게 할 수도 있다. 그런데 일반적으로 생성자를 사용하여 많은 객체를 중복해서 사용하려면 프로토타입을 쓴다. 생성자를 사용해서 객체를 조금만 생성한다면 그냥 속성을 부여하는 것이 좋다. 프로토타입은 모든 객체가 한 객체를 공유하고 있어서 메모리를 하나만 사용한다. 속성을 부여하는 방식은 객체를 생성할 때마다 새로운 function을 생성한다. 따라서 객체를 여러 개 생성해야 하는 때는 프로토타입을 사용하는 방법이 유리하다.


또한 실시간으로 여러 객체의 공통 속성의 내용을 수정하고자 할 때, 프로토타입을 사용했다면 프로토타입 속성만 수정하면 모든 객체에 수정한 내용이 반영되지만, 생성자 안에서 속성으로 부여했다면 루프를 돌면서 모든 객체를 다시 설정해야 하는 문제가 있다. 


단점은 프로토타입의 깊이가 깊어질 수록 메모리가 소모되는 양이 많아진다. 따라서 해당 객체에 자주 접근해서 참조해야 하는 속성이라면 프로토타입에 있는 값은 기본값으로만 사용하고 객체 속성을 추가하여 프로토타입 체인 탐색을 최소화하는 것이 좋은 방법이다.





반응형