자바스크립트를 공부하면서 가장 이해가 안되는 개념이 프로토타입이다.
c++, java의 OOP(객체지향 프로그래밍)은 class를 기반으로 상속, 캡슐화를 위한 키워드인 public, private, protected를 사용하는데 자바스크립트는 매우 다르다...
객체의 집합으로 프로그램을 표현한려는 프로그래밍 패러다임
객체지향의 가장 큰 특징은 캡슐화, 추상화, 다형성이 있다.
캡슐화의 주된 목적은 정보의 은닉이다. 객체 외부에서 접근이 가능한지, 객체 내부에서만 접근이 가능한지 제한하는 것이다.
그리고 하나의 객체에 특정한 목적에 맞게 코드를 잘게 쪼갠 형태를 의미한다.
함수형 프로그래밍과 같이 기능 별로 프로그램을 구성하면 유지 보수에 장점을 가지고, 코드의 재사용을 높일 수 있어서 개발자가 덜 노력할 수 있다...?
프로그램에서 추상화란 개념이 없었다면 지금과 같은 프로그램이 없었을 것이라고 생각한다.
운영체제에서 추상화의 존재가 없었으면, 지금과 같이, 저렴한 가격에 빠른 컴퓨터를 만들 수 없었을 것이고, 객체지향에서 추상화가 없었으면 반복적인 코드를 노가다 하는 일들이 많았을 것이라고 생각한다.
예를 들면, 00대학교 무역학과 1학년 학생들의 데이터를 저장할 때, 100명의 특징을 따로 적는 방법도 있지만 학생, 성별, 나이, 사는 곳 등 공통적인 요소나 특징을, 추상화를 통해 요소를 끄집어 내면 더 쉽게 데이터를 저장할 수 있다.
다시 말해, 클래스는 객체의 추상화된 개념이고, 객체(인스턴스)는 실제 모습이라고 할 수 있다.
학생 클래스에 정의된 나이
라는 속성이 정의되어 있다. 재성이라는 객체를 생성하면, 나이라는 속성이 상속
되어 저장된다.
기본적으로 1학년은 20살이지만 나는 재수를 해서 21살이라고 가정하면, 추상화된 속성과 다른 속성값을 가질 수 있다.
이를 다형성이라 합니다.
다형성을 위해 overriding, overloading의 개념을 알아야 합니다.
오버라이딩
은 부모클래스를 상속받은 자식 클래스에서 부모의 매서드를 변경하여 사용하는 것을 의미한다.
오버로딩
은 같은 이름을 가진 매서드지만 다른 기능을 수행해, 새로운 결과물을 만들어내는 것을 의미한다.
때문에 다형성을 활용해서 객체 간 특징의 차이를 올바르게 표현할 수 있습니다.
기본적인 oop에 대해 정리했고, 자바스크립트와 클래스 지향 객체지향이 어떤 점이 다른지 알아보자!
기본적으로 생성자 함수로 생성된 인스턴스들은 동일한 매서드를 소유하면서 메모리를 불필요하게 낭비한다. 왜 다를까
function Circle(radius){
this.radius = radius;
this.getArea = function(){
return Math.PI * this.radius ** 2
}
// 프로토타입
Circle.prototype.getRadius = function() {
return this.radius;
}
}
const circle1 = new Circle(5)
const circle2 = new Circle(2)
// circle1, circle2 인스턴스에 getArea 매서드를 새로 생성한다.
circle1
와 circle2
에는 getArea 매서드가 새로 생성된다.
클래스 기반 oop에서 추상화에 의해 circle1과 circle2가 Circle의 getArea 매서드를 가졌지만 자바스크립트는 약간의 차이가 있다.
새로운 객체를 생성할 때 새로운 getArea 매서드를 생성하므로, 불필요한 메모리를 사용한다.
getArea 매서드를 부모에서 받아오는 방법을 사용해야 추상화의 개념이 아닌가 생각한다..!
자바스크립트는 이를 프로토타입 기반 oop로 처리한다.
모든 객체에는 하나의 프로토타입이 존재하고, 프로토타입에 정의한 매서드는 인스턴스들의 부모 객체의 역할을 한다.
다시 말해, getArea 매서드는 단 하나만 생성되어 모든 인스턴스는 프로토타입의 매서드를 상속받아 사용할 수 있다.
프로토타입은 객체 간 상속을 구현하기 위해 사용된다. 모든 객체는 Prototype
의 내부 매서드를 가지고, 이 값은 프로토타입의 참조값으로 객체 생성 방식에 의해 결정된다.
예를 들어, 객체 리터럴에 의해 생성된 객체의 프로토타입은 Object.prototyope
이고, 생성자 함수에 의해 생성된 객체는 생성자 함수의 prototype 프로퍼티에 바인딩 되어 있는 객체이다.
모든 객체는 프로토타입 객체를 가지고, user는 Prototype
내부 슬롯에 간접적으로 접근할 수 있다.
__proto__
는 크롬 개발자 도구에서 확인이 가능하다.
하지만 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 바람직하지 않다!
모든 객체가 __proto__
를 사용할 수 있는 것이 아니기 때문이다. 직접 상속을 통해 Object.prototype
을 상속받지 않고 객체를 생성할 수 있기 때문이다.
함수 객체가 소유한 프로토타입은 생성자 함수가 생성할 인스턴스의 프로토타입을 가진다.
따라서 생성자 함수로서 호출이 불가능한 non-constructor인 arrow function과 축약 매서드표현은 프로토타입 프로퍼티를 소유하지 않는다.
const Person = name => {
this.name = name;
}
console.log(Person.haOwnProperty('prototype')); // false
const obj = {
foo()
}
console.log(obj.foo.hasOwnProperty('property')); //false
생성자 함수로 호출하기 위해 정의한 함수 선언문과 함수 표현식도 prototype 프로퍼티를 가지지만 객체를 생성하지 않는 일반 함수의 prototype은 아무런 의미가 없다.
모든 객체가 가지고 있는 즉, Object.prototype에서 상속받은
proto 접근자 프로퍼티와 함수 객체만 가지는 prototype 프로퍼티는 동일한 프로퍼티를 가리킨다.
하지만 각 프로퍼티를 사용하는 주체가 다르다.
모든 객체에서 사용하는 __proto__
, constructor 내부 매서드를 가진 prototype 프로퍼티를 가진 객체는 생성자 함수가 인스턴스의 프로토타입을 할당하기 위해 사용된다.
앞서 생성자 함수에 의해 생성된 객체와 다르게, 리터럴 표기법에 의한 객체 생성은 new연산과 함께 인스턴스를 생성하지 않을 수 있다.
리터럴 표기법에 의해 생성된 객체도 프로토타입이 존재하지만 모든 객체의 프로퍼티가 constructor
프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 단정할 수 없다.
// 객체 리터럴로 생성한 객체
const obj = {}
console.log(obj.constructor === Object) //true
obj
는 리터럴로 생성 했다. 하지만 Object
생성자 함수와 constructor
프로퍼티로 연결되어 있다.
그렇다면 결국 객체 리터럴은 생성자 함수의 동작 원리를 가지는 것이 아닌가?
일단 답은 아니다. 근데 답을 설명하려는 근거가 이해가 되지 않는다.
자바스크립트의 내부 동작 추상 연산
의 개념을 이해하고, 후에 수행되는 동작을 이해해야 생성자함수와 리터럴의 차이를 분명하게 알 수 있다.
일단은 이것만 기억하자.
생성자 함수 호출과 객체 리터럴의 평가는 추상 연산을 호출해 빈 객체를 생성하는 점은 같지만, 프로퍼티를 처리하는 세부 내용이 다르다.
하지만 리터럴
에 의해 생성된 객체도 상속을 위해 프로토타입
이 필요하다. 프로토타입
은 생성자 함수
와 prototype.constructor
프로퍼티와 연결되어 단독으로 존재하지 않고 쌍으로 존재한다.
자바스크립트의 개념은 끝이 없는 기분이다.. 프로토타입이 뭔지 가슴으로 다가오지 않는 기분이 든다. 어떤 장점이 있어서 자바스크립트는 프로토타입 기반 객체지향을 선택 했을까 자기전에 생각해봐야겠다.
정리 자주 하자...좀...