다른 객체지향 언어(Java, C++)들은 class기반 객체지향이지만
JavaScript는 prototype기반 객체지향 언어다.
모든 객체는
__proto__
프로퍼티와constructor
프로퍼티를 가진다.모든 함수는
prototype
프로퍼티를 가진다. (또한 함수도 객체이므로 __proto__
와 constructor
를 가짐)
Function에 의해 생성되었다. 즉,
fn.__proto__ === Function.prototype
fn.constructor === Function
예시
function normal() {};
function Person() {};
// 전부 true
console.log(normal.__proto__ === Function.prototype);
console.log(Person.__proto__ === Function.prototype);
console.log(Function.__proto__ === Function.prototype);
console.log(Object.__proto__ === Function.prototype);
//전부 true
console.log(normal.constructor === Function);
console.log(Person.constructor === Function);
console.log(Function.constructor === Function);
console.log(Object.constructor === Function);
obj.__proto__
는 obj의 생성자 함수의 prototype (obj.constructor.prototype
) 을 가리킨다.
생성자함수.prototype.<멤버이름>
에 정의하면 된다.리터럴로 정의한 객체는 내부적으로 Object
생성자 함수에 의해 생성되고
함수는 내부적 Function
생성자 함수에 의해 생성된다.
JavaScript에서는 객체의 맴버(프로퍼티와 메소드)에 접근할 때,
해당 객체에 해당 맴버가 없으면 __proto__
를 통해 부모 객체 (프로토타입 객체)로 올라가서 검색하는 것을 반복한다.
이를 프로토타입 체인이라 한다.
프로토타입 체인을 도식화한 모습
여기서 맨 오른쪽 세로 라인이 a1객체의 prototype chain이라고 볼 수 있다.
(a1 → Asian Prototype → Person Prototype → Object Prototype → null)
멤버를 참조할때
멤버에 값을 할당할 때
const Person = function Person (first, last) {
this.firstName = first;
this.lastName = last;
}
const p1 = new Person('Gildong', 'Hong');
const p2 = new Person('Sunsin', 'Lee');
console.log(p1.gender); // undefined
console.log(p2.gender); // 'male'
console.log(p1.firstName); // 'Gildong'
console.log(p2.firstName); // 'Sunsin'
console.log(p1.constructor); // ① Person(first, last)
console.log(p2.constructor); // ② Object()
위 코드를 객체형태로 도식화 할 수 있고
console.log출력이 왜 그런지 이해가 되면
prototype 개념을 완벽히 이해한거다.
헷갈리면 아래랑 비교해보자.
const Person = function Person (first, last) {
this.firstName = first;
this.lastName = last;
}
const p1 = new Person('Gildong', 'Hong');
p1.__proto__ = {gender: 'male'};
const p2 = new Person('Sunsin', 'Lee');
console.log(p1.gender); // 'male'
console.log(p2.gender); // undefine
console.log(p1.firstName); // 'Gildong'
console.log(p2.firstName); // 'Sunsin'
console.log(p1.constructor); // ① Object()
console.log(p2.constructor); // ② Person(first, last)
const Person = function Person (first, last) {
this.firstName = first;
this.lastName = last;
}
const p1 = new Person('Gildong', 'Hong');
const p2 = new Person('Sunsin', 'Lee');
const p3 = new Person('Muya', 'Ho');
Person.prototype.printFullName = function () {
console.log(`제 이름은 ${this.firstName} ${this.lastName} 입니다.`);
};
p1.printFullName();
p2.printFullName();
p3.printFullName();
프로토타입 객체에 멤버를 추가하거나 삭제해서 위와 같은 식으로 사용할 수 있다.
Object.prototype.printFullName = function () {
console.log(`제 이름은 ${this.firstName} ${this.lastName} 입니다.`);
};
p1.printFullName();
p2.printFullName();
p3.printFullName();
p1, p2, p3 가 가 결국 Object.prototype으로 연결되기 때문에 (prototype chain)
위 코드도 같은 결과를 출력한다.
결과
Function.prototype.printFullName = function () {
console.log(`제 이름은 ${this.firstName} ${this.lastName} 입니다.`);
};
p1.printFullName();
p2.printFullName();
p3.printFullName();
p1, p2, p3 부터 시작하는 프로토타입 체인에 Function.prototype은 없기 때문에
printFullName()
메소드를 찾을 수 없어서 오류가 난다.
결과
JavaScript에서는 5가지 원시 타입을 제외한 모든 것은 객체
다.
원시타입 종류
하지만 원시타입으로 프로퍼티를 호출할 때 그와 연관된 프로토타입 객체로 연결된다.
아래 예시를 보자.
var str = 'test';
console.log(typeof str); // string
console.log(str.constructor === String); // true
console.dir(str); // test
var strObj = new String('test');
console.log(typeof strObj); // object
console.log(strObj.constructor === String); // true
console.dir(strObj);
// {0: "t", 1: "e", 2: "s", 3: "t", length: 4, __proto__: String, [[PrimitiveValue]]: "test" }
console.log(str.toUpperCase()); // TEST
console.log(strObj.toUpperCase()); // TEST
str
은 분명 object가 아닌 string 타입인데도 불구하고
String으로 생성된 object인 것 마냥 이용할 수 있다.
이처럼 원시 타입도 내장 객체의 프로토타입에 연결된다. (string말고 number 등도 됨.)