계급, 집단, 집합. 공통적인 속성을 모아 한데 묶은 덩어리.
해당 클래스의 속성을 지닌 구체적인 객체.
사과, 바나나, 감 등은 모두 과일이다. 과일은 음식의 한 종류이다.
사과, 바나나, 감 등은 과일이면서 음식이다.
음식과 과일은 실질적인 형태가 없는 추상적인 개념이다.
사과, 바나나, 감 등은 실질적인 형태가 있는 구체적인 물체이다.
음식과 과일은 클래스, 사과/바나나/감 등을 인스턴스라고 비유할 수 있다.
프로그래밍에서 추상적인 개념이란 이미 정의되어 있는 개념이 아니다. 추상적인 개념을 정의해야만, 그 하위에 위치한 구체적인 물체를 정의할 수 있다.
위에서 비유한 과일 이야기에서 음식은 상위 클래스, 과일은 하위 클래스의 위치에 있다. 여기서 음식은 Superclass, 과일은 Subclass라고 칭한다.
프로토타입 프로퍼티 내부에 할당되지 않고, 생성자함수에 직접 할당되어 있는 메서드와 프로퍼티들. 생성자함수를 new 연산자 없이 함수로써 호출할 때만 의미가 있는 값들에 속한다. 인스턴스의 개별적인 동작이 아닌, 소속 여부 확인 및 부여 등의 공동체적 판단을 필요로 할 때 사용된다.프로토타입 내부의 정의된 메서드들은 '프로토타입 메서드'라고 불리운다. 통상적으로 그냥 '메서드'라고 축약되어 표시된다.
이 2가지 개념은 인스턴스에서 직접 접근이 가능한지 여부가 갈린다.
'프로토타입 메서드'는 [[Prototype]]으로 연결되어 이것이 곧 인스턴스와 겹쳐 동작하면서 인스턴스에서 직접 접근이 가능하다. 반면 Static 메서드와 Static 프로퍼티들은 그렇지 않다. (단, 프로토타입의 생성자를 통해 우회하여 접근할 수는 있다. 다만 this가 인스턴스를 가리키지 않고, 정상적인 동작을 기대하기 힘들다.)
function Person(name, age) {
this._name = name;
this._age = age;
};
Person.getInformations = function(instance) {
return {
name: instance._name,
age: instance._age
};
};
Person.prototype.getName = function() {
return this._name;
};
Person.prototype.getAge = function() {
return this._age;
};
var kim = new Person('김', 10);
console.log(kim.getName());
console.log(kim.getAge());
console.log(kim.getInformations(kim));
위 코드에서 console.log(kim.getInformations(kim));은 에러가 발생한다. 인스턴스에서 Static 메서드에 직접 접근할 수 없기 때문. 생성자 함수에서 직접 접근해야 에러가 발생되지 않는다.
console.log(Person.getInformations(kim));
function People(name, age) {
this.name = name;
this.age = age;
};
People.prototype.getName = function() {
return this.name;
};
People.prototype.getAge = function() {
return this.age;
};
function Employee(name, age, position) {
this.name = name;
this.age = age;
this.position = position;
};
Employee.prototype.getName = function() {
return this.name;
};
Employee.prototype.getAge = function() {
return this.age;
};
Employee.prototype.getPosition = function() {
return this.position;
};
People 클래스의 프로토타입 메소드는 Employee 클래스의 그것과 완전히 동일하다. 불필요한 중복은 코딩에서 지양해야하는 일, 그렇다면 어떻게 이를 줄일 수 있을까. People 클래스를 Employee 클래스로 상속시킴으로써 코드의 중복성을 최소화할 수 있다.
다른 의미에서는 People 클래스의 삼각 도식(생성자 함수와 prototype, 그리고 인스턴스)과 Employee 클래스의 삼각 도식이 연결되어야 한다.
일차적으로 생각해볼 수 있는 것은 People 클래스의 인스턴스 위치가 Employee 클래스의 prototype 위치와 일치시키는 것. Employee.prototype에 People을 할당시키는 것이다.
Employee.prototype = new People();
그런데 이대로는 기존의 Employee.prototype 객체를 완전한 새로운 객체로 교체하는 것이 된다. 그렇기에 일반적인 prototype과 동일한 동작을 하기 위해서는 기능을 다시 부여해주어야한다. 자바스크립트에서는 prototype 객체에 constructor 프로퍼티를 자동으로 생성해주고 있다. 그리고 constructor 프로퍼티에는 생성자 함수가 담겨있다. 이를 만들어주면 되는 것이다.
Employee.prototype.constructor = Employee;
여기까지 진행하면 서로 무관계하던 2개의 클래스가 상하위 관계를 갖게된다. Superclass - Subclass 관계가 생성되는 것이다.
이 과정을 종합적으로 정리하자면 아래와 같은 형태를 갖춘다.
function People(name, age) {
this.name = name;
this.age = age;
};
People.prototype.getName = function() {
return this.name;
};
People.prototype.getAge = function() {
return this.age;
};
function Employee(name, age, position) {
this.name = name;
this.age = age;
this.position = position;
};
Employee.prototype = new People();
Employee.prototype.constructor = Employee;
Employee.prototype.getPosition = function() {
return this.position;
};
그런데 Employee 생성자 함수로 인스턴스를 만들때, 매개변수의 일부가 누락된다면? prototype chaining을 타고 상위 클래스에 있는 프로퍼티를 찾아 값을 가져오게 된다. 그리고 이는 추상적인 개념만을 포함해야하는 클래스의 개념에 부합하지 않는 행위에 속한다.
클래스 상속에서는 각 클래스의 prototype을 [[prototype]]으로 내려받는 존재가 필요한 것 뿐이다. 위와 같은 방법에서는 인스턴스 그 자체를 상속받고 있어 불필요한 프로퍼티들까지 모두 상속되고 있다. 그렇다면 People.prototype을 상속받는 별도의 인스턴스가 존재하고 그 객체에는 메소드만 상속받게 하면 된다.
People과 Employee 사이에 이를 위한 중간 생성자 함수를 하나 만든다. 이름은 Bridge. Bridge의 prototype에 People의 prototype을 연결한 상태에서 인스턴스를 만들면 된다. 상위 클래스의 인스턴스가 아닌, prototype 그 자체를 중간 생성자 함수의 prototype으로 전달하면 되는 것이다.
function People(name, age) {
this.name = name;
this.age = age;
};
People.prototype.getName = function() {
return this.name;
};
People.prototype.getAge = function() {
return this.age;
};
function Employee(name, age, position) {
this.name = name;
this.age = age;
this.position = position;
};
function Bridge() {
};
중간 지점을 위한 빈 생성자 함수 Bridge. 아무런 프로퍼티를 생성하지 않는 비어있는 생성자 함수이다. 이를 이용하여 징검다리를 이용한 완전한 클래스 상속을 시행해보자.
Bridge.prototype = People.prototype;
Employee.prototype = new Bridge();
Employee.prototype.constructor = Employee;
Bridge.prototype에 People.prototype을 덮어씌우고, 이전과 동일한 방법으로 하위 클래스의 상속 관계를 생성하는 것이다. 이는 ES5에서 주로 활용된 완전한 클래스 상속 방법이다.
아니면 아래와 같이 이 과정을 하나의 함수로 만들어 사용해볼 수 있다.
var extendClass = (function() {
function Bridge() {};
return function(Parent, Child) {
Bridge.prototype = Parent.prototype;
Child.prototype = new Bridge();
Child.prototype.constructor = Child;
};
})();
클로저를 이용하여 Bridge 함수를 재활용한 뒤, Superclass - Subclass 관계를 가질 생성자 함수를 매개변수로 넘긴다. 이렇게 되면 완전한 상속 구조가 생성된다. 그리고 상속을 위한 함수를 미리 만들어 필요할 때마다 호출하여 사용한다.
extendClass(People, Employee);
그런데 인스턴스의 프로퍼티 역시 이를 이용하여 더 간단한 구현이 가능해진다. 상위 클래스의 프로퍼티들이 하위 클래스의 프로퍼티와 동일하다면? // 동일한 프로퍼티에 해당하는 부분을 다음과 같은 코드로 바꿔준다.
function People(name, age) {
this.name = name;
this.age = age;
};
function Employee(name, age, position) {
this.superClass(name, age);
this.position = position;
};
var extendClass = (function() {
function Bridge() {};
return function(Parent, Child) {
Bridge.prototype = Parent.prototype;
Child.prototype = new Bridge();
Child.prototype.constructor = Child;
// 아래와 같은 1줄의 코드를 추가하면 된다.
Child.prototype.superClass = Parent;
};
})();
extendClass(People, Employee);
생성자 함수 내부의 this는 인스턴스를 가르킨다.
인스턴스에는 superClass 메소드가 없다. 따라서 prototype chaining를 타고 Employee 생성자 함수로 넘어간다.
ES6 이후에는 개발자가 클래스 상속을 위한 함수를 직접 구현할 필요가 없다. 자바스크립트에서 내장함수로 지원해주기 때문. extends 키워드로 간단하게 상속이 가능하다.
class People {
constructor (name, age) {
this.name = name;
this.age = age;
};
getName() {
return this.name;
};
getAge() {
return this.age;
};
};
class Employee extends People {
constructor (name, age, position) {
super(name, age);
this.position = position;
};
getPosition() {
return this.position;
};
};
정재남 - 코어 자바스크립트.