본문 바로가기
Programming/자바스크립트

[자바스크립트] 프로토타입 - 응용

by 코딩하는 랄로 2023. 11. 4.
728x90

https://codingralro.tistory.com/entry/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85-%EA%B0%9C%EB%85%90

 

[자바스크립트] 프로토타입 - 개념

프로토타입 기반의 언어 : 자바스크립트 자바스크립트는 클래스 기반의 객체지향 언어가 아닌 프로토 타입 기반의 객체지향 언어이다. 그렇기 때문에 클래스라는 개념이 존재 하지 않는다. 여

codingralro.tistory.com

 

 

 

프로토타입의 응용

저번 포스팅에서는 프로토타입에 대한 개념을 알아보았다. 이번 포스팅에서는 프로토 타입을 응용하는 방법에 대해서 다루어보겠다. 기본적으로 프로토 타입은 코드의 재사용성을 높여준다는 장점이 있다. 

 

자바에서는 클래스라는 개념을 통해서 중복된 코드를 상속다아 사용한다면, 자바스크립트는 프로토타입을 이용하여 코드의 재사용성을 높일 수 있는 것이다. 이를 활용하기 방법으로는 크게 두가지 방식이 존재한다.

 

바로, classical 방식과 prototypal 방식인데, classical 방식은 new 연산자를 통해 생성한 객체를 사용하여 코드를 재사용하는, 마치 Java 와 유사한 방식이다.

 

prototypal 방식은 리터럴 또는 Object.create을 이용하여 객체를 생성하고 확장해 나가는 방식으로 프로토타입 기반의 언어인 자바스크립트에 더 어울이는 방법이고 classical 방식보다 더 간결하게 구현할 수 있다. 그렇기 때문에 자바스크립트에서는 classical 방식보다는 prototypal 방식을 더 선호한다.

 

 

 

classcial : 기본

부모에 해당하는 해당하는 생성자 함수를 이용하여 프로토타입 객체를 생성한다. 이 후 자식에 해당하는 함수의 프로토 타입의 속성을 이 전에 생성한 부모의 프로토타입 객체를 참조하게 하는 방법이다.

function Parent (name) {
    this.name = name || 'parent'
}

Parent.prototype.getName = function() {
    return this.name;
}


function Child(name) {}

Child.prototype = new Parent();

const child1 = new Child();
console.log(child1.getName()); // parent

const child2 = new Child('child');
console.log(child2.getName()); // parent

 

위에서 Child 객체는 prototype은 부모 객체로 바꾸어주었기 때문에 new 연산자를 이용하여 생성된 Child 객체는 부모 객체인 Parent의 속성 사용할 수 있다. 단순히 prototype을 속성값을 바꿔주는 것으로 부모 객체를 지정하여 해당 속성을 사용할 수 있지만, 아래의 출력 결과를 통해 단점 또한 알 수 있다.

 

바로 child 객체를 생성하면서 넘겨준 인자를 부모 객체를 생성할 때 넘어가지 않기 때문에 Parent 객체의 name 값은 default 값인 parent가 들어가게 되기 때문에 Child 생성자에 인자를 넘겨주어도 getName 메소드의 결과로 parent가 출력이 되는 것이다.

 

해당 문제를 해결하기 위해서는, 위와 같이 Child 객체를 생성할 때마다 부모의 생성자를 호출하는 방법이 있지만 매우 비효율 적이다. 이를 효율적으로 해결하기 위해서는 다음의 방법을 사용할 수 있다.

 

 

 

classical : 생성자 빌려 쓰기

이 방법은 부모 함수의 생성자를 자식 함수의 생성자에서 빌려쓰는 방법으로 위의 문제를 해결한다. 함수 객체의 정적 메소드인 call, apply 를 이용하면, this에 지정된 객체를 바인딩하여 함수를 호출할 수 있는데 부모 객체의 생성자에 자식 객체를 바인딩하여 호출하는 것이다. (this 바인딩 궁금하신 분은 해당 링크 참고 : this 바인딩)

function Parent (name) {
    this.name = name || 'parent';
}

Parent.prototype.getName = function() {
    return this.name;
}

function Child(name) {
    Parent.apply(this, arguments)
}

const child = new Child('child')
console.log(child.name); // child
console.log(child.getName()); // TypeError: child.getName is not a function

 

Child 생성자 함수에서 부모 함수의 생성자를 빌려쓸 경우, 부모 함수의 속성을 자식 함수 안에 모두 복사한다. 그렇기 때문에 Child 객체를 생성하고 name 프로퍼티에 접근하였을 때 넘겨 받은 인자를 잘 출력하는 것을 볼 수 있다.

 

기존 방법은, 부모 객체의 멤버를 참조하는 것이었다면, 생성자 빌려 쓰기 방법은 부모 객체의 멤버를 복사하여 자신의 것으로 만들어 사용한다는 차이점이 있다. 이러한 생성자 빌려 쓰기에도 단점이 있는데, 바로 부모 객체의 this로 된 멤버만 물려받게 된다는 것이다,

 

그렇기 때문에, 부모 객체가 가지고 있는 age, getName() 은 복사하지 못해 해당 멤버에 접근하려고 하면 위와 같은 결과를 출력하게 되는 것이다. 이를 해결하기 위해서 다음 방법을 살펴보자.

 

 

 

classical : 기본 + 생성자 빌려 쓰기

다음 방식은...두 가지 방식을 합치는 방식이다..!! 기본방식은 부모 객체에게 인자를 못 넘겨주는 대신 부모 객체의 모든 속성에 접근할 수 있고, 생성자 빌려 쓰기 방식은 부모 객체에 this로 된 멤버를 복사해와 자신의 멤버로 가질 수 있게 되는 대신, 부모 객체의 모든 속성에 접근할 수 없다는 단점이 존재했다.

 

서로가 서로의 장점을 단점으로, 단점을 장점으로 가지고 있기 때문에, 두가지 방식을 같이 사용하면 문제가 해결되는 것이다!!

function Parent (name) {
    this.name = name || 'parent';
}

Parent.prototype.getName = function() {
    return this.name;
}

function Child(name) {
    Parent.apply(this, arguments)
}
Child.prototype = new Parent();

const child = new Child('child')
console.log(child.name); // child
console.log(child.getName()); // child

 

해당 방법은, 위에서 제시되는 모든 문제를 해결하였지만, 아쉬운 점 또한 존재한다. 바로 부모의 생성자 함수를 두번 호출해야 한다는 것이다. Child 생성자 함수 내에서 한번, 링크를 할 때 한번...이런 아쉬운 점도 없앨 수 있는 다음 방식을 살펴보자.

 

 

 

classical : prototype 공유

해당 방식은 보모 생성자를 한번도 호출하지 않으면서 프로토타입 객체를 공유할 수 있는 방법이다.

function Parent (name) {
    this.name = name || 'parent';
}

Parent.prototype.getName = function() {
    return this.name;
}

function Child(name) {
    this.name = name;
}
Child.prototype = Parent.prototype;

const child = new Child('child')
console.log(child.name); // child
console.log(child.getName()); // child

 

child 생성자 함수는 일반 생성자 함수와 같이 변수를 초기화 해주고, Child의 prototype을 Parent의 prototype으로 지정하여 주었다. 부모의 prototype 속성에는 Parent 생성자를 constructor로 가지는 프로토타입 객체를 참고하고 있기 때문에 가능한 방법이다. (이 부분이 잘 이해가 안 가시는 분들은 다음 글 참고 : 프로토타입)

 

Parent 생성자 함수가 참조하고 있는 프로토타입을 객체를 같이 참조하면서 Parent 객체의 속성들을 사용할 수 있는 것이다.

 

 

 

prototypal 방식

마지막으로 prototypal 방식에 대해서 살펴보겠다. 이 방식은 Object.create() 메소드를 사용하여 객체를 생성과 동시에 프로토타입 객체를 지정해 줄 수 있다.

 

Object.create()의 구문은 다음과 같다.

  • 첫번째 매개변수 : 부모 객체를 지정
  • 두번째 매개변수 : 반환되는 자식 객체의 속성을 추가 => 사용이 복잡

첫번째 매개변수를 이용하여 위의 예제들을 다음과 같이 간단하게 만들 수 있다.

const parent = {
    name : 'parent',
    getName : function() {
        return this.name;
    }
};

const child = Object.create(parent);
child.name = 'child';
console.log(child.getName()); // child

 

따로 Child 생성자를 생성하지 않고도, parent를 부모 객체로 갖는 child 객체를 생성할 수 있다. Object.create의 두번째 매개변수는 추가하고 싶은 속성을 저정해주면 된다. 이 때, 다음과 같은 형식을 가져야 한다. 

/*
Object.create( parentObj, 
    {  
        propertyKey : 
            {
                value : propertyValue,  //프로퍼티 값
                enumarable : false,  //default : false
                configurable : false,  //default : false
                writable : true,  //default : false
            },
    }
)       
*/
const parent = {
    name : 'parent',
    getName : function() {
        return this.name;
    }
};

const child = Object.create(parent, {age : {value : 26, writable : true}});
child.age = 28;
console.log(child.getName()); // child
console.log(child.age); // 28

 

두번째 매개변수의 속성을 지정해주는 방식은 더 있지만, 해당 내용에 대해서는 추후에 자세히 다루겠다. 이번 포스팅에서는 create 사용시 두번째 매개변수를 통해서도 객체의 속성을 지정해줄 수 있다는 것만 알고 포스팅을 끝내겠다.

728x90