What is Prototype?
Prototype의 사전적 의미
프로토타입이라는 단어 자체를 살펴보자. prototype은 "원형"이라는 뜻을 가지고 있다. 즉 완성품이 아니라, 초기에 대략적인 디자인이나 기능을 구현해 놓은 모델을 말한다.
a first, typical or preliminary model of something, especially a machine, from which other forms are developed or copied. ex) "Mercedes is prototyping a car sunroof which changes from clear to tinted"
JS에서 Prototype의 의미
JavaScript에서 Prototype이란, 어떤 객체가 다른 객체가 가지고 있는 특징/성질을 상속받는 메커니즘 그 자체를 가리키기도 하고, 모든 JS 객체가 기본적으로 가지고 있는 속성(property)를 가리키기도 한다. 이 속성(prototype)의 값 또한 객체이기 때문에 property로 prototype을 가지고 있으며, 이것을 prototype chain이라고 한다. prototype의 값이 null을 가리킬 때, 연쇄적인 체인이 끝난다.
1. Prototypes are the mechanism by which JavaScript objects inherit(receive) features from one another.
2. Every object in JavaScript has a built-in property, which is called its prototype. The prototype is itself an object, so the prototype will have its own prototype, making what's called a prototype chain. The chain ends when we reach a prototype that has null for its own prototype.
+) 대부분의 다른 프로그래밍 언어는 class를 이용해서 객체 지향을 지원한다. modern JS의 class는 prototype을 감싼 것 뿐인 syntatic sugar이다.
Prototype as Built-in obj
+) JS에서는 배열도 객체이다.
Array 챕터에서 모든 배열 데이터에서 사용할 수 있는 유용한 속성과 메서드에 대해 다루었다. 우리가 모든 배열에 어떤 공통된 속성이나 메서드를 이용할 수 있는 것은, 모든 배열이 그 속성과 메서드를 가지고 있는 prototype이라는 객체에 대한 참조(reference)를 가지고 있기 때문이다. 즉 prototype이라는 하나의 객체를, 모든 array가 공통적으로 참조하고 있다. 따라서, Array의 prototype에 어떤 프로퍼티나 메서드를 직접 추가하면, 모든 array에서 이를 이용할 수 있을 것이다.
arr1.__proto__ === arr2.__proto__ // true
Array.prototype.yell = function() {
console.log(`OH!!!!! LENGTH IS ${this.length}!!!!`);
}
arr1.yell(); // this === Array.arr1
arr2.yell(); // this === Array.arr2
예시를 배열로 가지고 왔지만, JS의 모든 객체가 prototype을 가지고 있다. 예를 들어, DOM 요소는 html 요소를 JS 객체로 변환한 것이므로, DOM 요소도 prototype을 가지고 있다.
+) prototype(template) vs. __proto__(reference to the prototype) | prototype은 객체 리터럴로 표현되는 실제 객체이고, __proto__는 이 prototype에 접근하기 위한 reference이다. arr1.__proto__ = prototype 이라는 실제 객체
OOP | Object Oriented Programming
객체지향프로그래밍은 프로그래밍 패러다임 중 하나로, 프로그래밍 패러다임은 코드를 작성하고 어플리케이션을 구조화/디자인하는 패턴을 의미한다. OOP가 지향하는 패턴은, 어플리케이션에 포함되는 것들을 성격에 따라 나뉘는 특정한 객체 내부에서 관리하는 것이다. JS에서 어떻게 OOP를 구현할 수 있을까?
Factory funtion
// factory function
function colorMaker(r, g, b) {
const color = {};
color.r = r;
color.g = g;
color.b = b;
color.rgb = function () {
// this === color obj
const { r, g, b } = this;
return `rgb(${r}, ${g}, ${b})`;
};
color.hex = function () {
const { r, g, b } = this;
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
};
return color;
}
const colorRed = colorMaker(255, 0, 0);
const colorGreen = colorMaker(0, 255, 0);
colorRed.hex(); // #ff0000;
colorRed.hex === colorGreen.hex // ✅ false!
- colorMaker 함수는 실행될 때 마다 color 객체를 만들고, 이를 반환한다.
- rgb, hex 함수는 color 객체의 메서드로, colorMaker를 호출해서 객체가 만들어 질 때 마다 함께 만들어진다. 즉 똑같은 일을 수행하는 함수가 매번 다시 만들어 진다.
- colorRed.hex === colorGreen.hex 의 평가식은 false 를 반환한다.
Constructor function + new keyword
// constructor function
function Color(r, g, b) {
this.r = r;
this.g = g;
this.b = b;
}
Color.prototype.rgb = function() {
const {r, g, b} = this;
return `rgb(${r}, ${g}, ${b})`;
}
Color.prototype.hex = function () {
const { r, g, b } = this;
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
};
const fac = Color(255, 0, 0); // factory, this => window
const red = new Color(255, 0, 0); // constructor, this => red
const green = new Color(0, 255, 0); // this => green
red.hex();
red.rgb === green.rgb // ✅ true!
constructor 함수는 (암시적으로) 텅 빈 객체를 만들고, 그 객체에 프로퍼티를 만든 뒤 반환한다는 점에서 factory 함수와 비슷하게 동작한다. 그러나 new 키워드와 함께 factory 함수 보다 효율적으로 사용할 수 있다. 다음은 new 키워드가 하는 일이다.
- 텅 빈 JS 객체를 만든다(편의상 이 객체를 newInstance 라고 부르자).
- constructor 함수의 prototype과, newInstance의 [[Prototype]] 객체를 연결한다.
- constructor 함수에 인자를 대입해서 실행하면, this가 가리키는 것은 newInstance가 된다(binding).
- return 키워드 없이도, 1번에서 만들어진 텅 빈 객체에 프로퍼티를 추가해서 반환한다.
Class
class는 constructor function에서 했던 것과 똑같은 일을 구현하는 syntatic sugar이다.
basic
class Color {
constructor(r, g, b) {
this.r = r;
this.g = g;
this.b = b;
}
rgb() {
const {r, g, b} = this;
return `rgb(${r}, ${g}, ${b})`;
}
}
const red = new Color(255, 0, 0);
- constructor | new 키워드와 함께 class 함수를 호출 하면, 바로 호출되는 함수
- prototype에 직접 메서드를 연결하지 않고, 클래스 안에서 메서드를 관리할 수 있다.
extension
class Pet {
constructor(name, age) {
this.name = name;
this.age = age;
}
eat() {
return `${this.name} is eating`;
}
}
class Cat extends Pet {
meow() {
return `${this.name}: meow!!!`;
}
}
class Dog extends Pet {
constructor(name, age, breed) {
super(name, age);
this.breed = breed;
}
bark() {
return `${this.name}: woof!!!`;
}
}
const timothy = new Cat('timothy', 4);
timothy.eat();
- extends | 서로 다른 클래스가 비슷한 속성이나 메서드를 가지고 있다면, 각각의 클래스에서 따로 정의하는 것이 아니라, 상위 클래스에서 해당 속성/메서드를 공유할 수도 있다.
- Cat 클래스 함수가 실행될 때, constructor가 포함되어 있지 않으므로 부모 class인 Pet에서 constructor를 실행한다. eat 함수를 실행할 때에도, Cat 클래스에는 eat이 포함되어 있지 않으므로 Pet의 eat을 실행하게 된다. 만약 Cat 클래스 내부에 eat이 정의되어있다면, Cat의 eat을 실행하게 된다.
- super | 하위 클래스에서 상위 클래스의 property에 더해서 새로운 property를 추가하고 싶을 때, 사용할 수 있다.
access Propery | getter & setter
객체 내부의 method 앞에 get과 set이라는 키워드를 사용하면, 그 method를 각각 getter와 setter라고 부른다. 그 이름에서 알 수 있듯이 getter는 무언가를 가져오는, settter는 무언가를 설정하는 듯한 느낌을 준다. getter와 setter는 언제, 왜 사용하는 걸까?
- getter | method 앞에 get이라는 키워드를 붙인 getter는 method가 객체 내부의 property를 단순히 조합하여 반환할 때, 즉 어떤 동작을 한다기 보다는 state(property)를 반환하는 역할을 할 때 사용한다.
- 아래 예제 코드에서 성과 이름 property를 조합해서 fullName을 반환하는 함수에 get 키워드를 이용하였다.
- 함수를 호출하는 것임에도 불구하고, 어떤 값에 접근하여 그 값을 읽는 것처럼 사용한다.
- 즉, student.fullName()이 아니라 student.fullName 으로 접근한다.
class Student {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
const student = new Student('taylor', 'swift');
student.fullName; // taylor swift
- setter | set 키워드를 붙인 method는 프로그래머가 어떤 property에 값을 할당, 즉 값을 setting하면 실행되는 함수이다.
- setter는 반드시 하나 이상의 인자(어떤 property에 할당되면 실행할 건지)를 받아와야 한다.
- setter는 프로그래머가 이상한 값을 할당하지 않도록 도와줄 수 있다.
- 예를 들면 프로그래머가 객체의 나이 property를 음수 값으로 할당하려고 하면, 나이를 0으로 만드는 setter 함수를 작성할 수 있다.
- 만약 get 키워드가 붙어있는 함수와 동일한 함수에 set을 설정하면, get 함수의 반환값이 자동으로 setter의 인자가 된다.
class Student {
constructor() {
//...
}
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
set fullName(value) { // value is return of fullName()
if (typeof value === 'number') {
console.log("name's data type should be string");
this.firstName = '';
this.lastName = '';
}
}
}
const student = new Student('taylor', 'swift');
student.fullName = 22; // console => name's data type should be string
student.fistName // ''
Access Modifier | public, private, protected
OOP의 핵심은 "외부에서" 어떤 프로퍼티와 메소드를 변경할 수 있는지(public), 없는지(private)를 결정하는 것이다.
coffee machine을 하나의 객체로서 프로그래밍 한다고 가정해 보자. machine을 이용하는 사람들(외부)의 취향에 따라 물의 양(프로퍼티)은 달라질 수 있으므로, 물의 양은 public한 값이 될 수 있다. 그러나 machine에서 받을 수 있는 물의 최댓값은 private한 값이 된다. 외부에서 함부로 바꿔도 되는 값이 아니기 때문이다. 하지만 물의 최댓값이 궁금해서 그 값을 읽고 싶을 수는 있다. 이 때에는 클래스 내부에 물의 최댓값을 알려주는 getter 함수를 public으로 만들 수 있다.
+) JS에서는 기본적으로 public과 private을 제공하지만 대부분의 프로그래밍 언어에서는 protected까지 지원한다. protected는 자식 객체에 한해서, 부모 객체로 부터 물려받은 property와 method를 변경할 수 있게 만드는 키워드이다.
- public | 지금까지 코드에 나타낸 property와 method는 모두 public이다. 특별한 키워드를 사용하지 않으면 모두 public하다.
- private | # 키워드를 이용하면 외부에서는 접근(read and write)할 수 없다.
+) Object prototype의 유용한 함수들
- Object.keys(obj) | obj에 있는 모든 key를 요소로 갖는 배열을 반환한다. keys뿐만 아니라 values와 entries를 사용할 수 있다.
- 'string' in obj | obj 안에 문자열에 해당하는 key가 있는지, 없는지를 확인하고 boolean을 반환한다.
- obj.hasOwnProperty('string') | obj 안에 문자열에 해당하는 key가 있는지, 없는지를 확인하고 boolean을 반환한다.
object의 모든 프로퍼티(key: value)는 "property descriptor"라는 객체 형태로 저장된다.
- Object.getOwnPropertyDescriptors(obj) | obj의 프로퍼티가 저장되어있는 descriptor(설명자, 기술자)를 모두 반환한다.
- writable | value를 수정할 수 있는지, 아닌지를 저장
- enumerable | value를 열거(iterate)할 수 있는지, 아닌지를 저장
- false라면, Object.keys(obj)를 실행했을 때 배열에 해당 프로퍼티가 포함되지 않는다.
- configurable | writable, enumerable을 수정할 수 있는지, 아닌지와 해당하는 프로퍼티의 descriptor 자체를 삭제할 수 있는지 아닌지를 저장
- Object.defineProperty(obj, key, {value : ~, writable: t/f, enumerable: t/f, configurable: t/f}) | 해당하는 프로퍼티의 속성을 재정의할 수 있다.
18.4부터~
'Study > JavaScript' 카테고리의 다른 글
Request & Response | API, JSON, fetch... (0) | 2022.04.17 |
---|---|
This (0) | 2022.04.05 |
Scope, Execution context, Closure (0) | 2022.04.04 |
Asynchronous | callBack, promise, async & await (0) | 2022.04.02 |
Call stack & Task que (0) | 2022.04.01 |