자바스크립트는 프로토타입의 객체지향언어입니다.

따라서 자바스크립트를 사용해 프로그래밍하는 사람들은 대부분 면접에서 OOP에 대한 질문을 받습니다.

 

OOP, 객체지향프로그래밍(Object Oriented Programming)에 대해 알아보도록 하겠습니다.

개념에 들어가기 전에 객체지향프로그래밍 패러다임에 대해 알아보도록 하겠습니다.

 

객체지향 프로그래밍은 구조적 프로그래밍보다 2년 앞서 등장했다고 합니다.

함수 호출이 반환된 이후 함수에서 선언된 지역변수가 오랫동안 유지되는 것을 발견한 것은 클래스의 생성자가 되었고 지역변수는 인스턴스의 변수가 되었고 중첩함수는 메서드가 되었다고 합니다.

 

함수 포인터를 특정 규칙에 따라 사용하는 과정을 통해 다형성이 등장했다고 하는데,

다형성은 아래 객체지향의 3요소에서 설명하듯이 객체지향의 최대 장점이라고 합니다.

 

 

 

객체지향프로그래밍(OOP : Object Oriented Programming)

프로그래밍 패러다임 중 하나로 객체 중심적인 사고를 하고 있으며

데이터를 추상화시켜 상태와 행위를 가진 객체를 만들고 객체 간의 유기적인 상호작용을 통해 로직을 구성하는 프로그래밍 방법입니다.

 

객체지향을 검색하면 붕어빵과 붕어빵 틀에 대한 예제를 많이 접하게 되는데,

잘못된 코드 비유라는 것을 설명해주고 있습니다.

클래스 = 붕어빵 틀

객체 = 붕어빵

 

"

붕어빵틀 붕어빵 = new 붕어빵틀(); 를 클래스에 대입해보면 아래와 같은데,

클래스 객체변수명 = new 클래스(); 말이 되지 않는 예제라고 합니다.

붕어빵틀은 붕어빵을 만드는 Factory로 이해해야 하며 클래스와 객체관계로 이해하면 안된다고 합니다.

"

-출처:https://sjh836.tistory.com/158 

 

그렇습니다.

클래스는 추상화적이어야 하며 붕어빵틀과 같이 구체화하고 실체면 안 됩니다.

또한, 클래스는 개념일 뿐 객체가 될 수 없기 때문입니다.

 

또 다른 예시가 잘되있는 블로그글을 가져와보겠습니다.

블로그 방문해서 읽어보시는 것을 추천합니다.

 

"

예를 들어 "바나나"라는 클래스가 있다고 하면 우리는 개념적으로 바나나가 갖고 있는 특징들을 떠올릴 수 있습니다. 껍질은 노란색이고 속은 하얗고 맛은 달고 길쭉한 모양이라는 특징들을요. 

하지만 실제로 바나나를 먹기 위해서는 마트에 가서 진열대에 놓여있는 실제의 "바나나(object)" 사와야 합니다.  

마트 직원에게 "바나나 어디 있나요?" 라고 물어볼 때 마트 직원이 안내를 해 줄 수 있는 것은 나와 마트 직원이 모두 바나나라는 추상적인 개념을 알고 있기 때문에 가능한 것입니다. 

진열대에는 바나나가 여러 개 있을 수 있죠. 그 중에 내가 어떤 바나나를 사서 먹었다면 그 바나나는 다른 바나나들 구별되는 유일한 실체입니다.

 

위 예문에서 글씨색으로 클래스와 객체를 구분해놓았는데 차이를 아시겠지요?

개(dog)라는 클래스가 있다면 우리집 강아지 로이, 옆집 강아지 초코는 객체인 것이죠.

"

출처: https://gracefulprograming.tistory.com/130 [Peter의 우아한 프로그래밍]

 

객체지향의 개념은 명확히 정의할 수가 없고 특성을 통해 이해하는 것이 최고의 방법이라고 합니다.

객체지향의 개념을 알아보기 위한 3요소와 5원칙에 대해 알아보겠습니다.

 

3요소

  • 캡슐화 (Encapsulation) = 정보 은닉(data hiding)
  • 다형성(Polymorphism) = 사용편의
  • 상속(Inheritance) = 재사용 + 확장

캡슐화 (Encapsulation) = 정보 은닉(data hiding)

객체의 속성(data fields)과 행위(methods)를 하나의 클래스라는 캡슐에 묶는 것을 캡슐화라고 합니다.

객체지향프로그래밍에서는 쉽고 효과적으로 데이터와 함수들을 캡슐화할 수 있습니다.

캡슐화는 외부에서 객체의 내부데이터로 직접 접근하지 못하게 통제하여 정보를 은닉합니다.

은닉된 정보로의 접근은 접근지정자를 통해서만 조작할 수 있도록 합니다.

 

JAVA에서는 접근지정자를 사용하는데, 접근지정자로는 private, protected, public이 있습니다.

 

private : 자기 클래스 내부의 메서드만 접근 허용

protected : 자기 클래스, 상속받은 자식 클래스에서의 접근을 허용

public : 모든 접근을 허용

 

Javascript에서는 키워드를 제공하지 않기 때문에 외부에서 접근이 제한된 데이터에 접근하려고 할 때 외부에서 접근할 수 있는 메서드를 제공하여 객체의 데이터에 접근하는 방식을 구현하여 사용하고는 합니다.

 

예제

var Person = function(name){

    // private
    var name = name;
   
    // public
    return {
        getName : function(){
            return name;
        },

        setName : function(newName){
            name = newName;
        }
    }
};

var yoon = new Person('Yoon');
console.log(yoon.name); // undefined

console.log(yoon.getName()); // yoon

yoon.setName('yoonhee');
console.log(yoon.getName()); // yoonhee

yoon.name으로 접근하면 캡슐화(정보은닉)로 인해 undefined가 반환되는 것을 확인할 수 있습니다.

이렇듯 캡슐화는 원본 데이터를 유지하며 보존, 보호하기 위해 존재합니다.

의미적인 이유로는 사용자가 굳이 알 필요가 없는 정보를 은닉함으로 최소한의 정보를 가지고 사용자가 프로그램을 사용할 수 있게 합니다.

 

다형성(Polymorphism) = 사용편의

하나의 객체가 여러 가지 형태를 가질 수 있는 것을 의미합니다.

쉽게 예를 들면 스마트폰이라는 객체가 전화도 하고 문자도 하고 게임도 할 수 있는,

즉 여러 가지 기능을 수행할 수 있는 것을 말합니다.

하나의 객체를 여러 개의 타입으로 보거나 혹은 하나의 타입을 여러 개의 객체로 해석할 수 있는 개념입니다. 다형성의 개념을 사용하는 방법으로는 오버라이딩(Overriding)과 오버로딩(Overloading)이 있습니다.

 

오버라이딩(Overriding)

 

부모 메서드로부터 상속받은 자식 메서드는 부모와 같은 이름, 인자, 반환 값을 가지게 됩니다.

이때 상속받은 메소드를 자식객체에서 재정의하는 것을 오버라이딩 이라고합니다.

 

예제

function Person(){
    this.name = '아무개';  
}

Person.prototype.getName = function(){
    console.log('내 이름은 : ' + this.name);
};

function Parents() {
    console.log('Parents');
}

Parents.prototype = new Person(); // 생성자
Parents.prototype.constructor = Parents;

// 오버라이딩
Parents.prototype.getName = function(){
    console.log('내 아이의 이름은 : ' + this.name + ' 입니다.');
};

var Child  = new Parents();
Child.getName(); // 내 아이의 이름은 아무개 입니다.

 

오버로딩(Overloading)

C#, C++, 자바 등의 다양한 프로그래밍 언어에서 사용되는 함수의 특징으로, 같은 함수 이름을 가지고 있으나 매개변수, 리턴타입 등의 특징은 다른 여러개의 서브프로그램 생성을 가능하게 하는 것을 말합니다.

Javascript에서는, 함수를 변수로 취급하고 모든 변수는 전역객체의 속성(객체의 키가 겹칠 수 없다)으로 취급되기 때문에 기본적으로 없는 개념입니다. 인자 값을 이용해서 오버로딩 기능을 구현할 수는 있습니다.

 

예제

Java의 경우

void overload(){
  System.out.println("매개변수 0개");
}

void overload(int i, int j){
  System.out.println("매개변수 "+ i + " 그리고 " + j);
}

void overload(double d){
  System.out.println("매개변수 " + d);
}

Javascript에서의 구현

function overload(a, b, c) {
  if (typeof c === 'function') { // 문자열 두 개와 콜백
    c(a, b);
  } else if (typeof b === 'function') { // 옵션 객체와 콜백
    b(a);
  } else { // 콜백 하나
    a();
  }
}

function callback(a, b) {
  if (b) {
    console.log('문자열', a, b);
  } else if (a) {
    console.log('옵션 객체', a);
  }  else {
    console.log('매개변수 없음');
  }
}

overload('zero', 'babo', callback); // 문자열 zero babo
overload({ name: 'zero', value: 'babo' }, callback); // 옵션 객체 { name: 'zero', value: 'babo' }
overload(callback); // 매개변수 없음

- 위 코드는 제로초님의 코드입니다! --> 제로초님 블로그 바로가기!

 

상속(Inheritance) = 재사용 + 확장

상속은 상위 클래스의 특성을 하위 클래스에서 물려받는 것을 말하고 하위 클래스에서는 더 필요한 속성을 확장해서 사용할 수 있습니다.

하위 클래스는 상속받은 상위 클래스의 속성(변수) 및 기능(메소드,함수)을 물려받습니다.

메모리 관점에서는 하위클래스의 인스턴스가 생성될 때 상위클래스의 인스턴스도 같이 생성된다고 합니다.

Javascript에서 상속은 Class 키워드를 사용하거나 프로토타입을 통해 할 수 있습니다.

 

예제

function Person(){
    this.type = '사람';
}

var Yoons = new Person();
console.log(Yoons); 

상속

상위 클래스의 속성을 가져온 것을 확인할 수 있으며 상속으로 인해 속성이나 기능이 재사용 가능합니다.

그리고 추가로 하나의 중요한 요소 추상화가 있습니다.

 

추상화(Abstraction) = 모델링

객체지향에서 추상화는 모델링이며 구체적인 것을 상세히 하지 않고 필요성에 의한 특성만을 가지고 구성하는 것을 말합니다.

코드상에서는 동작의 구현을 제외한 선언하는 부분에 해당하며 설계하는 부분을 말합니다.

 

Javascript는 추상클래스와 인터페이스를 제공하지 않아서 리터럴 방식, 함수 활용방식, 프로토타입 방식, 클래스로 구현할 수 있습니다.

하지만 자바의 인터페이스는 제공하지 않기 때문에 인터페이스의 기능을 사용할 수는 없습니다.

(인터페이스는 클래스의 필수 메서드 규약을 할 수 있습니다)

 

추상화는 객체를 만들기 전에 필요하며 추상화 클래스를 생성한 후 상태와 행동을 추가하여 객체를 생성합니다. 이때 추상화의 개념이 잘 안 섰는데, https://itewbm.tistory.com/24 이 블로거의 글이 도움되었습니다.

 

추상화 단계가 없는 경우를 가정해보겠습니다.

동물이라는 객체를 추상화클래스 없이 생성합니다.

동물로부터 상속하여 코끼리와 독수리 객체를 생성하려 할 때 객체는 구체화가 되어야 하기 때문에 구체화 된 기능을 추가해보겠습니다.

동물은 (코끼리) 살아있다.
동물은 (코끼리) 코가 길다.
동물은 (코끼리) 아프리카에 서식한다.
동물은 (코끼리) 4족 보행을 한다.

동물은 (독수리) 살아있다.
동물은 (독수리) 하늘을 난다.
동물은 (독수리) 아프리카에 서식한다.
동물은 (독수리) 잡식성을 가졌다.
동물은 (독수리) 날개가 있다.

 

구체화하여 선언된 것을 봤을 때, 같은 기능도 있으나 같지 않은 기능도 있습니다.

그리고 결론적으로 동물이라는 객체를 봤을 때 동물 객체는 이미 동물이 아니라는 것입니다.

동물은 살아있다. 코가 길다. 하늘을 난다. 아프리카에 서식한다. 4족 보행을 한다. 잡식성을 가졌다. 날개가 있다. 어느 동물이 위와 같은 객체가 될까요?

 

쉽게 생각하자면 위와 같은 예제를 생각할 수 있다는 것입니다.

추상화 단계를 거쳐 동물이라는 객체를 구현하게 된다면 동물이라는 객체를 만들 수 있으므로 추상화하는 단계의 설계는 중요합니다.

 

추상화

동물은 살아있다.
동물은 세포를 가지고 있다.
동물은 눈이 있다.

이렇게 구체적이지 않은 추상화 설계가 필요하며

이를 통해 코끼리라는 구체화한 객체를 생성하는 것입니다.

(어느 동물에 대입해도 가능한 경우처럼 말이죠)

 

코끼리는 살아있다.
코끼리는 세포를 가지고 있다.
코끼리는 눈이 있다.

독수리는 살아있다.
독수리는 세포를 가지고 있다.
독수리는 눈이 있다.

 

 

객체지향의 기초가 되는 5원칙

SOLID

  • SRP (Single responsibility principle)  단일 책임 원칙
  • OCP (Open-closed principle)  개방 폐쇄 원칙
  • LSP (Liskov substitution principle)  리스코브 치환 원칙
  • ISP (Interface segregation principle)  인터페이스 분리 원칙
  • DIP (Dependency inversion principle)  의존 역전 원칙

 

단일 책임 원칙 (Single Responsibility Principle)

단 한 개의 기능을 가져야 한다는 원칙입니다.

하나의 기능을 가지고 있으므로 클래스의 변경에서도 이유는 한 개가 되어야 합니다.

(클래스를 설계할 때 하나의 기능만을 수행하도록 해야 하며 그 기능에 집중해야 한다는 것을 의미합니다.)

 

단일책임원칙을 통해 개발하면 기능은 하나이기 때문에 책임영역이 확실하며 변경이 필요한 경우도 하나의 이유이기 때문에 코드의 가독성 및 유지보수에 유리합니다.

 

만약 단일책임 원칙을 위배하였을 경우 위와는 반대로 책임영역이 확실하지 않기 때문에 로직을 파악하는 시간이 오래 걸리며 버그 발생확률 또한 높아져 유지보수가 어려워집니다.

따라서 단일책임원칙을 위배하면 나쁜 설계가 될 수 있습니다.

 

산탄총 수술이라는 재미있는 말이 있는데,

하나의 책임이 여러 개의 클래스로 분산되었을 경우 단일 책임 원칙에 맞게 변경하는 경우를 말합니다.

산탄총은 하나의 총알에 여러 탄이 들어가 있고 산탄총을 맞게 된다면 여러 개의 탄을 하나하나 찾아서 치료해야 하기 때문이라고 합니다.

 

개방폐쇄의 원칙 (Open Close Principle)

소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에 열려(Open)있어야 하고 수정에는 닫혀(Close)있어야 한다는 원칙입니다. 기능의 확장은 가능하지만, 수정은 하지 않아야 한다는 원칙입니다.

 

확장의 비용을 극대화 시키고 수정의 비용은 최소화한다는 의미로 요구사항의 변경이나 추가 요청사항이 발생했을 때 구성요소의 수정은 없어야 하며 기존요소를 확장하여 재사용할 수 있는 설계를 하는 것을 의미합니다.

Open
모듈의 기능을 확장할 수 있습니다.

Close
모듈의 소스코드를 수정하지 않아도 모듈의 기능을 확장하거나 변경할 수 있어야 합니다.

재사용 코드를 만드는데 기반이 되며 개방 폐쇄원칙을 가능하게 하는 메커니즘으로는 추상화와 다형성이 있습니다.

 

리스코프 치환의 원칙 (The Liskov Substitution)

부모 클래스와 자식 클래스 사이의 기능이 일관성이 있어야 한다는 의미입니다.

일관성이 있다는 의미는 부모 클래스로 동작하던 프로그램이 속성의 변경 없이 자식클래스로 치환되었을 때 정상적으로 동작해야 한다는 것을 말합니다. 그러므로 상위 클래스는 공통속성이나 추상화된 기능만을 가지고 있어야 합니다.

 

이해가 잘 안 가서 찾던 중에 쉬운 예제를 작성한 블로거를 찾았습니다.

https://programmingfbf7290.tistory.com/entry/SOLID-%EC%9B%90%EC%B9%993-%EB%A6%AC%EC%8A%A4%EC%BD%94%ED%94%84-%EC%B9%98%ED%99%98-%EC%9B%90%EC%B9%99LSP

 

예제 

부모클래스는 스마트폰, 자식클래스는 갤럭시폰

갤럭시 폰은 스마트폰이다.

 

스마트폰의 속성

전화, 메시지가 가능하다.
데이터, 와이파이를 이용해 인터넷을 사용할 수 있다.
앱 마켓을 통해 앱을 다운받는다.

 

스마트폰의 속성을 가진 프로그램이 갤럭시 폰으로 치환되면

갤럭시 폰은 전화, 메시지가 가능하다.
데이터, 와이파이를 이용해 인터넷을 사용할 수 있다.
앱 마켓을 통해 앱을 다운받는다.

치환되어도 정상적으로 동작하므로 리스코프 치환의 원칙을 잘 적용한 케이스입니다.

 

인터페이스 분리 원칙 (Interface Segregation Principle)

클래스가 사용하지 않는 인터페이스는 구현하지 않아야 하며 의존하지 않아야 한다는 원칙입니다.

(인터페이스는 클래스의 필수 메서드 규약을 할 수 있습니다)

또한, 클래스가 다른 클래스에 종속적일 때에는 가능한 최소한의 인터페이스만을 사용해야 합니다.

큰 단위의 인터페이스는 구체적이면서 작은 단위의 역할 인터페이스로 분리해서 클래스에서 필요한 메서드만을 사용할 수 있도록 해야 합니다.

그리고 이렇게 인터페이스를 분리하는 것을 통해 시스템의 내부 의존성을 약화해 리팩토링 및 수정, 재배포를 쉽게 할 수 있게 됩니다.

 

의존성 역전의 원칙 (Dependency Inversion Principle)

상위 레벨 모듈이 하위 레벨 모듈의 구현에 의존해서는 안 되며 하위 레벨 모듈이 상위 레벨 모듈에서 정의한 추상 타입에 의존해야 하는 것을 말합니다.

 

전통적인 구조적 디자인에서는 상위 레벨 모듈에서 하위레벨 모듈을 의존하게 합니다.

이렇게 되면 하위레벨모듈에서 변경이 있으면 상위레벨 모듈도 변경될 수 있습니다.

따라서 의존성 역전은 모듈 간의 직접적인 의존을 끊고 상위 레벨 모듈에서 정의한 추상을 하위레벨 모듈이 구현하게 하는 원칙입니다.

결과적으로 상위 레벨 모듈과 하위 레벨 모두 추상클래스에 의존하게 되며 변화에 쉽게 영향받지 않게 됩니다.

즉, 외부에서 의존성을 주입함으로써 의존성을 줄이는 것이며 모듈 간의 관계를 최대한 느슨하게 만드는 것이 원칙입니다.

 

 

끝맺음

실제로 객체지향을 공부하면서 객체지향의 정의란? 하면 나오는 요소들은 실제로 객체지향만 적용되는 부분이 아니었습니다. 캡슐화, 상속 같은 경우 절차지향 프로그래밍에서도 완벽히 구현할 수 있으며 `클린아키텍쳐 소프트웨어 구조와 설계의 원칙`책을 보면 예제와 함께 실제 프로그래머들도 오래전부터 사용해왔음을 알려줍니다.

 

캡슐화에 대한 글을 인용하자면 아래와 같이 나와 있습니다.

"

객체지향이 강력한 캡슐화에 의존한다는 정의는 받아들이기 힘들다.

실제로 많은 객체지향언어가 캡슐화를 거의 강제하지 않는다.

"

 

상속 같은 경우에도 캡슐화와 마찬가지로 새로운 개념이 아니며 절차지향에서도 구현할 수 있지만, 객체지향에서는 조금 더 편리한 방식으로 사용할 수 있다는 것을 말합니다.

 

객체지향에서 가장 중요한 요소는 다형성입니다.

다형성도 물론 절차지향언어에서 구현해서 사용할 수 있으나 안전하지 않으며 위험합니다.

반대로 객체지향에서는 편리하고 안전하게 구현할 수 있으며 대수롭지 않게 사용할 만큼 중요한 요소입니다.

객체지향의 최대 장점은 다형성이며 다형성을 통해 구현하는 원칙들에 있습니다. (의존성 역전)

다형성과 의존성 역전을 통해 소스코드의 의존성 방향 제어의 흐름을 결정할 수 있으며 이는 객체지향이 지향하는 방식입니다.

 

"

객체지향을 사용하면 아키텍트는 플러그인 아키텍처를 구성할 수 있고,

이를 통해 고수준의 정책을 포함하는 모듈은 저수준의 세부사항을 포함하는 모듈에 대해

독립성을 보장할 수 있다.

저수준의 세부사항은 중요도가 낮은 플러그인 모듈로 만들 수 있고,

고수준의 정책을 포함하는 모듈과는 독립적으로 개발하고 배포할 수 있다.

"

- 클린아키텍쳐 소프트웨어 구조와 설계의 원칙, 로버트 C.마틴 지음

 

 

 


 

참고 및 출처, 인용

*주의 : push한 후 사용하지 말고 커밋에서 사용하셔야 충돌 및 꼬이는것을 방지한답니다.

 

 

 

1.커밋이 많이 쌓였을 경우

 

 

 

 

2.적용하려는 브랜치(origin/master, origin/HEAD)에 마우스 우클릭 > 자식커밋을 쌍방향 재배치(Rebase children of ..... interactively....) 

 

 

 

 

3.합칠 커밋 선택 후 이전 커밋과합치기 클릭

 

 

아래와 같아진다.

 

 

 

 

4.메세지 편집 클릭

 

 

 

 

5.커밋 텍스트 수정 후 확인 > 확인

 

 

 

 

6.합쳐진커밋확인 후 push

 

 

push 후 합쳐진 것 확인

 

 

pull 받기 전 충돌날 것 같으면 사용합니다.

 

 

1.커밋하지 않은채로 스태시 버튼 클릭

 

 

 

 

2.스태시 이름을 정해주고 확인클릭

 

 

 

 

3.스태시 하위에 내가 저장한 목록확인, 커밋해야하는 파일들 사라진것을 확인

 

 

 

 

4.Pull 받아서 최신으로 업데이트

 

 

 

 

 

5.스태시에서 마우스 우클릭 > 스태시 적용 클릭

 

 

 

 

 

6.적용 후 내가 수정했던 소스 적용된 것 확인하기.

이후 commit, push 진행 (정상적인지 파일 확인은 필수)

 

 

 

 

7.스태시 삭제

 

 

git bash에서 작업시 

1.Stash 생성
git stash 또는 git stash save

2.Stash 리스트확인
git stash list

3.Stash 적용
git stash apply 또는 git stash apply [stash이름] 또는 git stash apply --index 
// --index를 추가해야 staged상태까지 복원

 

(ES6에서는 클래스 문법이 생겼다지만. 동작 및 방식에 대해 아직 공부하지 않았으므로 언급하지 않고 다음으로 미루겠습니다.)

 

 

자바(Java)나 파이썬(Python) 등, 보통 객체지향언어들은 클래스(Class) 기반의 언어라고 합니다.
하지만 자바스크립트는 프로토타입 기반(Prototype-based programming)의 객체지향언어입니다.

그래서 자바스크립트로 프로그래밍을 하다 보면 

필수적으로 프로토타입 기반 프로그래밍이라는 말을 접하게 됩니다.


자바스크립트 그 자체로 불릴 만큼 중요한 개념인 프로토타입에 관해서 공부해보겠습니다.

 

 

 

프로토타입(Prototype)

"
원래의 형태 또는 전형적인 예, 기초 또는 표준이다.
시제품이 나오기 전의 제품의 원형으로……. 블라블라….
중요한 기능들이 포함된 시스템의 초기 모델이다.
"

- 위키백과

 

프로토타입에 대한 핵심 단어들을 기억하고 넘어가 보겠습니다.
핵심단어는 [기초, 표준, 제품의 원형, 초기 모델]입니다.

 

 

자바스크립트에서 프로토타입의 개념은 프로토타입 객체(Prototype Object), 프로토타입 링크(Prototype Link)를 통틀어 말합니다.

 

자바스크립트에서 객체가 생성될때, 생성된 객체의 부모가 되는 객체의 원형을 프로토타입 객체(Prototype Object)라고 하며 생성된 객체와 부모 객체와의 참조를 이어주는 링크를 프로토타입 링크(Prototype Link)라고 합니다.

그리고 이 프로토타입 객체를 프로토타입이라고 말하며 객체지향의 상속개념과 같이 부모객체의 속성, 메소드를 상속받아 객체를 재생성하며 프로그래밍하는 기법을 프로토타입 프로그래밍(Prototype-based programming)이라고합니다.

 

 

 

프로토타입 기반 프로그래밍(Prototype-based programming)

다른 명칭으로는 

클래스 리스(class-less) 프로그래밍,

프로토타입 지향(prototype-oriented) 프로그래밍,

인스턴스(instance-based) 프로그래밍이라고 불리기도 합니다.

프로토타입 프로그래밍은 자바 클래스의 상속을 흉내 내는 방식의 프로그래밍인데, 클래스와의 차이점을 간단히 확인하고 넘어가겠습니다.


클래스 기반

클래스라는 추상화된 개념을 선언한 뒤, 이 클래스를 기반으로 객체에 상속을 지원합니다.
여기서 주목해야 할 점은 객체의 형식이 정의된 클래스는 객체가 아닌 개념이라는 점입니다.

// 1. 클래스 정의
public class Person{
	public string country = "korea";
    public string name;
    public int age;

    // 2. 클래스 생성자 정의
    public Person(string name, int age){
        this.name = name;
        this.age = age;
    }
}

// 3. 객체 생성
Person boy = new Person("yoonhee", "12");

 

프로토타입 기반

프로토타입 원형 객체를 생성한 뒤, 이 객체를 이용해서 클래스의 상속을 흉내 냅니다.

여기서 주목해야 할 점은 프로토타입은 객체입니다.

// 1. 프로토타입 객체 정의
var  base = function(){
    this.country = "korea"
}

// 2. 프로토타입 객체 생성자 정의
var Person = function(name, age){
    this.name = name;
    this.age = age;
}

// 3. 생성자에 프로토타입 상속
Person.prototype = base;

// 4. 객체 생성
var boy = new Person("yoonhee", "12");

 

매우 유사해 보이지만 프로토타입에서는 생성자에 프로토타입을 상속받는 절차가 필요한 것을 확인할 수 있습니다.

 

 

자바의 클래스를 흉내낸다고 하는 이유는,

클래스는 개념이며 이 개념을 통해 객체를 생성하여 인스턴스로 만듭니다.

하지만 프로토타입은 클래스와 같은 개발 방식을 흉내내지만, 인스턴스입니다.

 

클래스는 객체의 형식이 정의된 개념

프로토타입은 객체

 

인스턴스란(Instance)?

더보기

객체 지향 프로그래밍에서 인스턴스는 해당 클래스의 구조로 컴퓨터 저장공간에서 할당된 실체를 의미한다.

 

 

 

프로토타입 객체(Prototype Object)

자바스크립트는 함수(Function) 자료형으로 객체를 선언할 때 생성자(Constructor)를 부여받습니다. 

 

함수 자료형

var func = new Function();
var protoFunc = new func();

var func2 = function(){};
var protoFunc2 = new func();

console.log(func.prototype); // { constructor : f(), __proto__ : Object }
console.log(func2.prototype); // { constructor : f(), __proto__ : Object }

function(){}는 new Function()으로 선언한것과 같습니다.

 

함수 자료형 외의 다른 자료형에서는 프로토타입 객체를 생성할 수 없습니다.

var obj = {};
var protoObj = new obj(); // obj is not a constructor

var num = 123;
var protoNum = new num(); // num is not a constructor

var boolean = true;
protoBoolean = new boolean() // boolean is not a constructor

 

그리고 이 생성자(Constructor)가 있는 객체만이 자바스크립트의 new 키워드를 통해 객체를 생성할 수 있습니다.

new 키워드를 통해 객체를 선언하면 함수의 생성과 함께 프로토타입 객체(Prototype Object)도 같이 생성됩니다.

 

Person 함수를 선언해보겠습니다.

function Person(){}

 

Person 함수 객체

Person 함수의 프로토타입 객체가 생성되며

각 객체는 서로를 참조할 수 있게 연결되는 속성을 가지고 있습니다.

 

출처 https://medium.com/@bluesh55/javascript-prototype-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-f8e67c286b67

 

1. 

Person 객체의 prototype을 통한 프로토타입 객체확인

 

2.

Person 객체의 constructor 는 Person 객체에 접근할 수 있다.

Person 객체의 __proto__는 Person 객체의 원형에 접근할 수 있습니다.

이때 Person 객체의 원형은 new Function이므로 자바스크립트 네이티브 코드가 반환됩니다.

__ proto __ 속성은(비표준) 모든 객체가 가지고 있다.

 

그리고 객체와 객체 사이에 참조하는 속성들을 퍼런스 변수라고 합니다.
또한, 이 참조는 동적으로 추가된 사항에 대해서도 접근할 수 있습니다.

 

 

 

레퍼런스 변수 예제

1.생성자를 부여받은 객체만이 new 키워드를 통한 객체 생성이 가능하다고 한 것을 기억해야 합니다.

function Person(x) {
    this.x = x;
}; // 프로토타입 객체 생성자 정의

var A = new Person('hello'); // 객체 생성

console.log(A.x); // hello 
// Person.x와 동일한 결과

console.log(A.prototype.x) // syntax error 
// A는 생성자의 권한이 없는 단일객체이기에 문법오류를 일으킨다.


 

 

2.프로토타입 원형 객체의 내부 스코프에 선언한 변수 x와 prototype으로 선언한 x의 위치를 확인합니다.

function Person(){
    this.x = 'Person x';
}; // 프로토타입 객체 생성자 정의

Person.prototype.x = 'prototype x'; // 프로토타입 선언

var newPerson = new Person(); // 객체 생성

console.log(newPerson.x); // Person x
console.log(newPerson.__proto__.x); // prototype x 

console.log(newPerson); 
/*
  { 
      x : 'Person x', 
      __proto__ :
           x : 'prototype x', 
          constructor : f Person(),
          __proto__ : Object 
  } 
*/

new 키워드를 통해 생성한 객체 newPerson은 원형 객체 Person을 참조하고 있기 때문에 

Person.x = newPerson.x이 됩니다. 

prototype으로 선언한 x는 __proto__ 레퍼런스 변수를 통해 원형 객체 Person의 x를 참조할 수 있습니다.

 

 

 

프로토타입 링크(Prototype Link)

__proto__ 속성은 프로토타입 링크라고 불리며 모든 객체에 존재하는 레퍼런스 속성이자 객체의 원형을 참조합니다. 객체가 생성될 때 프로토타입이 결정되며 사용자가 임의로 변경할수도 있습니다.

사용자가 임의로 동적으로 변경하는 특징을 사용해 상속을 구현할 수도 있습니다.

 

이 프로토타입 링크를 통해 상위 __proto__를 접근할 수 있으며 이것을 프로토타입 체인(Prototype chain)이라고 합니다. __proto__ 속성의 동작은 내부적으로 Object.getPrototypeOf가 호출되며 프로토타입객체를 반환합니다.

 

__proto__와 prototype는 둘다 프로토타입객체를 가리키고 있지만 두 속성의 관점은 차이가 있다고합니다.

__proto__ prototype

모든 객체가 가지고 있다.

함수 객체만 가지고 있다.

부모 프로토타입 객체를 가리킨다.

(함수의 경우 Function.prototype을 가리킨다.)

생성자를 가진 원형으로 생성할 수 있다.

참고 : https://poiemaweb.com/js-prototype

 

 

{}나 new Object로 선언한 객체는 자바스크립트에서 제공하는 네이티브 코드를 원형으로 두고 있어서 __proto__는 네이티브 코드를 contstuctor로 가지고 있습니다.

 

var object = {};
var object2 = new Object();

 

 

예제

원형 객체 Person의 prototype을 상속받습니다.

function Person(){}

Person.prototype.getType = function(){  
    return "사람입니다";
};

var yoon = new Person();  
var jun = new Person();

console.log(yoon.getType());   // 사람입니다
console.log(jun.getType());  // 사람입니다

 

1. __proto__와 prototype의 위치

function Person(){
    this.x = 'Person x';
};

Person.prototype.x = 'prototype x';

var newPerson = new Person();

console.log(newPerson.x); // Person x
console.log(newPerson.__proto__.x);  // prototype x'

- Person 함수 내부 스코프에 this.x를 선언

- Person의 prototype에 x를 선언

 

newPerson 객체의 x에 접근할 때 Person 내부 스코프에 선언한 대로 반환합니다.

__proto__.x로 참조 시 newPerson의 prototype에 접근하여 x를 반환합니다.

 

그렇다면 프로토타입 객체 원형의 내부 스코프에 this.x를 선언하지 않고 진행했을때에는 어떤 결과가 나오는지 확인해봅니다.

 

 

 

2. 프로토타입체인, 내부에 선언된 값이 없다면 상위를 참조

function Person(){};

Person.prototype.x = 'prototype x';

var newPerson = new Person();

console.log(newPerson.x); //prototype x
console.log(newPerson.__proto__.x);  // prototype x'

- Person 내부에 선언된 x의 값을 찾고 x의 값이 없으면 __proto__를 통해 상위 객체에서 x의 값을 찾는다. 

위의 예제에서는 prototype.x가 있기 때문에 prototype.x의 값을 반환한다.

 

 

프로토타입객체 내부에 변수가 없으면 해당 변수의 값을 찾기 위해 상위 프로토타입을 이어 참조하면서 해당 변수가 있을때까지 반복하여 값을 찾습니다. 끝까지 값이 없으면 undefined를 반환합니다.

 

 

 

렇게 

객체와 객체의 단방향 공유관계를 프로토타입 체인(Prototype Chain)이라고 합니다.

프로토타입체인은 동적으로 상속된 내용을 참조하기 때문에 실행할 때 값이 변경될 수 있으며 동적으로 변경되는 객체의 메서드나 속성을 찾아가는 과정을 프로토타입 룩업이라고 합니다.

 

__proto__를 통한 직접적인 접근은 참조에 대한 확인일 뿐 개발 시에는 지양해야 하며 Object.getPrototypeOf() 메서드를 사용하여 프로그래밍해야 합니다.

 

상위 객체를 참조하는 예제는 아래와 같습니다.

var obj = function(){}

console.log(obj.__proto__); // f(){[native code]};
console.log(obj.__proto__.__proto__); // { constructor : f, __defineGetter : f, ..... }
console.log(obj.__proto__.__proto__.__proto__); // null

 

 


 

출처 및 참고

- https://meetup.toast.com/posts/104

- https://velog.io/@yhe228/prototype%EC%9D%B4%EB%9E%80-6dk3v32r55

- https://medium.com/@bluesh55/javascript-prototype-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-f8e67c286b67

+ Recent posts