JavaScript
상속, prototype
Java,C
언어는 클래스 기반의 객체 지향 프로그래밍 언어이지만,JavaScript
는 프로토타입 기반 언어이다.- 클래스 기반 객체 지향 언어는 객체 생성 이전에 클래스를 정의하고 이를 통해 객체를 생성하지만, 프로토타입 기반 객체 지향 언어는 클래스 없이도 객체를 생성할 수 있다.
- 자바스크립트의 모든 객체는 자신의 부모 역할을 담당하는 객체와 연결되어 있는데, 상속 개념과 같이 부모 객체의 프로퍼티 또는 메서드를 상속받아 사용할 수 있게 한다.
- 이러한 부모 객체를
prototype
객체라고 한다.
1
2
3
let example = { name: "Choi" };
example.hasOwnProperty("name"); // true
example
이라는 변수에hasOwnProperty
라는 메서드는 없지만, 자바스크립트의 모든 객체는[[Prototype]]
이라는 인터널 슬롯을 가지며 상속을 구현하는데 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const car = {
wheels: 4,
drive() {
console.log("drive");
},
};
const bmw = {
color: "red",
navigation: 1,
wheels: 4,
drive() {
console.log("drive");
},
};
const benz = {
color: "black",
wheels: 4,
drive() {
console.log("drive");
},
};
const audi = {
color: "green",
wheels: 4,
drive() {
console.log("drive");
},
};
- 위의 코드에서
wheels, drive
는 모든 객체에 공통적으로 들어가는 프로퍼티이다. - 이렇게 공통된 프로퍼티를 프로토타입에 추가하여 상속시킬 수 있다.
- 상속은 계속 이어질 수 있는데 이를 프로토 체인이라고 한다.
- 상속된 키와 값과 관련된 나오지 않는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bmw.__proto__ = car;
benz.__proto__ = car;
audi.__proto__ = car;
bmw; // {color: "red", navigation: 1}
bmw.drive; // drive
const x5 = {
color: "white",
};
x5.__proto__ = bmw;
for (p in x5) {
console.log(p);
}
// color
// name
// navigation
// wheels
// drive
생성자 함수 활용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const car = {
wheels: 4,
drive() {
console.log("drive");
},
};
const BMW = function (color) {
// this.wheels = 4;
this.navigation = 1;
this.color = color;
// this.drive(){console.log("drive");};
};
const x5 = new BMW("red");
const z4 = new BMW("yellow");
x5.__proto__ = car;
z4.__proto__ = car;
BMW.prototype.stop = function () {
console.log("stop");
};
instanceof
- 생성자 함수를 활용하여 새로운 객체를 생성해낼 때, 생성된 객체는 생성자의 인스턴스라고 불려진다.
- 인스턴스인지를 확인하기 위해서 사용된다.
instanceof
를 사용해서 생성자와 인스턴스를 구별할 수 있고, 객체가 생성자로부터 생성된 것인지를 확인하여boolean
값으로 나타낸다.
1
2
z4; // BMW {color: "yellow"}
z4 instanceof BMW; // true
constructor
- 생성된 객체의 생성자를 가르킨다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
z4.constructor === BMW; // true
// prototype을 줄이기 위해 아래와 같이 코드를 작성하면
// constructor가 사라지기 때문에 프로퍼티를 하나씩 추가하는 것이 좋다.
BMW.prototype = {
// constructor: BMW
// 이렇게 수도로 명시해주면 해결할 수 있다.
wheels: 4,
drive() {
console.log("drive");
},
navigation: 1,
stop() {
console.log("stop");
},
};
z4.constructor === BMW; // false
클로저 활용
- 상속시킨 프로퍼티에 직접적인 접근을 방지하기 위해 클로저를 활용할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const BMW = function (color) {
// this.wheels = 4;
this.navigation = 1;
this.color = color;
// this.drive(){console.log("drive");};
};
const z4 = new BMW("yellow");
z4.color = "red"; // BMW{navigation: 1, color: "red"}
// 클로저 활용
const BMW = function (color) {
// this.wheels = 4;
this.navigation = 1;
const c = this.color;
this.getColor = function () {
console.log(c);
};
// this.drive(){console.log("drive");};
};
Class
- ES6에 추가된 스펙이다.
- 객체를 만들어주는 생성자 메서드인
constructor
가 있으며,new
를 통해 호출한다. - 클래스 내에 정의한 메서드는 클래스의 프로토타입에 저장된다.
class
가 아닌 생성자 함수는new
없이 실행하여도undefined
로 동작하지만,class
는new
없이 사용할 경우 타입 에러가 발생한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const User = function (name, age) {
this.name = name;
this.age = age;
this.showName = function () {
console.log(this.name);
};
};
// {name: name, age: age, showName: function}
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
showName() {
console.log(this.name);
}
}
// {name: name, age: age}
// __proto__: {constructor: class User, showName: function}
- 생성자 함수는 프로토타입으로 상속이 가능하고,
class
는extends
를 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Cat {
constructor(color) {
this.color = color;
this.wheels = 4;
}
drive() {
console.log("drive");
}
stop() {
console.log("stop");
}
}
class BMW extends Car {
park() {
console.log("park");
}
}
const z4 = new BMW("blue");
// {color: "blue", wheels: 4}
// __proto__: Car
// constructor: class BMW
// park: function
// __proto__: class Car
// drive: function
// stop: function
메서드 오버라이딩
- 만약 동일한 메서드가 존재하면 덮어쓰게 되지만 부모의 메서드도 그대로 사용하고 싶다면
super
을 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Car {
constructor(color) {
this.color = color;
}
drive() {
console.log("drive");
}
stop() {
console.log("stop");
}
}
class BMW extends Car {
park() {
console.log("park");
}
stop() {
console.log("off");
}
}
const z4 = new BMW("blue");
z4.stop(); // off
class BENZ extends Car {
park() {
console.log("park");
}
stop() {
super.stop();
console.log("off");
}
}
const e = new BENZ("green");
e.stop(); // stop, off
오버라이딩
- 상속관계가 있는 부모 클래스에서 이미 정의된 메서드를 자식 클래스에서 다시 정의하는 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Car {
constructor(color) {
this.color = color;
}
drive() {
console.log("drive");
}
stop() {
console.log("stop");
}
}
class BMW extends Car {
// 만약 새로운 메서드를 정의하고자 constructor을 쓴다면
constructor() {
this.navigation = 1;
}
park() {
console.log("park");
}
}
const z4 = new BMW("blue");
// Must call supter constructor in dervied class before accessing "this"
// constructor에서 this를 사용하기 전에 반드시 super을 사용하여 부모 생성자를 호출해야 한다.
- 이는
constructor
로 빈 객체를 만들고,this
를 사용하여 프로퍼티를 만드는데,extends
로 만들어진 자식 클래스는 이러한 과정을 건너뛰기 떄문에super
키워드로 부모 클래스의constructor
을 실행해줘야 한다. - 또한 자식 클래스의
constructor
에 동일한 인수를 받는 작업을 해주어야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Car {
constructor(color) {
this.color = color;
}
}
class BMW extends Car {
constructor() {
super();
this.navi = 1;
}
}
const z4 = new BMW("blue");
// {color: undefined, navi: 1}
class BENZ extends Car {
constructor(color) {
super(color);
this.navi = 1;
}
}
const e = new BENZ("red");
// {color: "red", navi: 1}
- 자식 생성자에
constructor
가 없을 때, 코드는 없지만 자바스크립트는 부모 생성자를 그대로 받아와 사용한다. - 이때
constructor
와super
없이 사용할 경우, 빈contructor
로 동작된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 기본 동작
class BMW extends Car {
// constructor(...agr){
// super(...agr);
// }
park() {
console.log("parking");
}
}
// 자식 constructor
class BMW extends Car {
constructor() {
this.park = function () {
console.log("parking");
};
}
}