브라우저 콘솔에서 다음과 같이 객체 리터럴을 만들어보자.
const myObj = {
city: "Madrid",
greet(){
console.log(`Greetings from ${this.city}`);
},
};
위의 객체는 city
속성과 greet()
메소드를 가지고 있다.
콘솔차에서 myObj
뒤에 .
를 입력하면 모든 멤버들의 이름이 리스트로 팝업될 것이다.
보면 알겠지만, city
와 greet
말고도 많은 멤버들이 있다는 것을 확인할 수 있다.
이 멤버들은 무엇이며, 어디에서 왔을까?
자바스크립트의 모든 객체는 프로토타입이라 불리는 내장된 속성을 가지고 있다.
프로토타입은 그 자체로 하나의 객체이기 때문에, 프로토타입은 또 자신만의 프로토타입을 가질 것이고, 이렇게 쭉쭉 이어지는 것을 프로토타입 체인이라 불린다.
프로토타입 체인은 프로토타입이 null인 객체에 도달하면 끝난다.
프로토타입을 가리키는 객체의 속성을 참조할때는
.prototype
가 아니라.__proto__
로 한다.
물론 이는 표준이 아니지만 실제로 모든 브라우저에서 사용할 수 있다.
객체의 프로토타입에 접근하는 표준 방법은Object.getPrototypeOf()
메서드를 이용하는 것이다.
어떠한 객체의 속성에 접근한다고 가정해보자.
찾고자 하는 속성이 객체 자체에서 찾을 수 없을 경우 그 객체의 프로토타입에서 속성을 검색한다.
여전히 속성을 찾을 수 없을 경우 프로토타입의 프로토타입에서 검색하고, 속성을 찾았거나 체인의 끝에 도달하면 검색이 멈춘다.
속성을 못찾은채로 끝나면 undefined
가 반환된다.
예를 들어 myObj.toString()
을 호출했다 가정해보자.
toString
이 myObj
에 있는지 찾는다.
찾을수 없으므로 myObj
의 프로토타입에서 toString
을 찾는다.
toString
을 찾았고 호출한다.
myObj
의 프로토타입이 뭘까?
Object.getPrototypeOf(myObj);
이것은 Object.prototype
이라는 객체이며, 모든 객체가 기본적으로 가지고 있는 가장 최상위 프로토타입이다.
Object.prototype
의 프로토타입이 null
이므로 프로토타입 체인의 끝이다.
객체의 프로토타입이 항상 Object.prototype
인 것은 아니다.
const myDate = new Date();
let object = myDate;
do {
object = Object.getPrototypeOf(object);
console.log(object);
} while (object);
// Date.prototype
// Object { }
// null
myDate
의 프로토타입이 Date.prototype
객체이고, 그것의 프로토타입이 Object.prototype
이라는 것을 보여준다.
실제로, myDate.getMonth()
와 같은 메서드를 호출할 때는 Date.prototype
에 정의된 메서드를 호출하는 것이다.
객체의 프로토타입에 정의된 멤버이름과 같은 이름으로 객체에 정의하면 어떻게 될까.
const myDate = new Date(1995, 11, 17);
console.log(myDate.getYear()); // 95
myDate.getYear = function () {
console.log("something else!");
};
myDate.getYear(); // 'something else!'
getYear()
을 호출하면 브라우저는 먼저 myDate에서 해당 이름을 가진 멤버를 찾고 찾지 못할 경우에만 프로토타입을 확인한다.
따라서 myDate에 정의된 버전의 getYear()
가 호출된다.
이를 shadowing the property 라고 부른다.
자바스크립트에는 객체의 프로토타입을 설정하는 다양한 방법이 있다.
그중 Object.create()
와 constructor를 이용하는 두 가지 방법을 알아보자.
Object.create()
메서드는 새 객체를 만들고 새 객체의 프로토타입으로 사용할 객체를 지정할 수 있다.
const personPrototype = {
greet() {
console.log("hello!");
},
};
const carl = Object.create(personPrototype);
carl.greet(); // hello!
위의 코드는 personPrototype
객체를 프로토타입으로 하는 새 객체를 생성해서 carl 변수에 할당하는걸 보여준다.
자바스크립트에서의 모든 함수는 prototype
이라는 속성을 가지고 있따.
constructor로써 함수를 호출하게 되면 이 속성은 새로 생성된 객체의 프로토타입으로 설정된다.
따라서 constructor의 prototype
을 설정하면 constructor로 생성된 모든 객체에 대해 prototype
이 프로토타입으로 적용된다.
const personPrototype = {
greet() {
console.log(`hello, my name is ${this.name}!`);
},
};
function Person(name) {
this.name = name;
}
Object.assign(Person.prototype, personPrototype);
// or
// Person.prototype.greet = personPrototype.greet;
위의 코드에서 Person
함수의 prototype
속성을 personPrototype
객체로 정의하기 위해 Object.assign
을 이용했다.
Object.assign()
메서드는 두 번째 매개변수부터 마지막 매개변수까지의 값들을 첫 번째 매개변수에 덮어씌우는 역할을 한다.
첫 번째 매개변수 자체를 반환한다.const o1 = { a: 1, b: 1, c: 1 }; const o2 = { b: 2, c: 2 }; const o3 = { c: 3 }; const obj = Object.assign({}, o1, o2, o3); console.log(obj); // { a: 1, b: 2, c: 3 }
아래와 같이 단순히 객체를 대입하면 되지않나라는 의문점이 들 수 있다.
Person.prototype = personPrototype
함수의 prototype
속성에는 해당 함수를 참조하는 constructor
속성이 들어있다.
만약 단순히 객체를 대입해서 덮어쓰기 해버리면 constructor
속성이 지워지기때문에 따로 추가해줘야 한다.
아니면 객체를 대입하는 것이 아닌, 객체의 멤버를 추가해주면 된다.
Person.prototype.greet = personPrototype.greet
Object.assign()
메서드를 이용하면 일일이 멤버를 추가하지 않고 한큐에 가능하다.
새로 생성된 객체는 personPrototype
객체가 가지고 있는 greet()
메서드를 사용할 수 있다.
const reuben = new Person("Reuben");
reuben.greet(); // hello, my name is Reuben!
Person constructor로 만들어진 객체는 두 가지 멤버를 가진다.
name
속성 : constructor에서 설정되므로 Person
객체에 직접 표시된다.
greet()
메서드 : prototype
에서 설정된다.
일반적으로 데이터 속성은 constructor(생성자)에 정의되어 있고 메서드는 프로토타입에 정의되어 있다.
메서드는 생성자로 생성된 모든 객체에 대해 동일한 반면, 데이터 속성은 각 객체에 고유한 값을 가지도록 해야하기 때문이다.
여기서 name
과 같은 객체에 직접 정의된 속성을 Own properties라고 하며, 정적 메서드인 Object.hasOwn()
를 사용하여 확인할 수 있다.
const irma = new Person("Irma");
console.log(Object.hasOwn(irma, "name")); // true
console.log(Object.hasOwn(irma, "greet")); // false
Object.hasOwnProperty()
메서드를 이용해도 되지만, 가능한 경우Object.hasOwn()
을 사용하는 것이 좋다.
이유는 모르지만 MDN에서 그렇게 권장한다.
프로토타입은 자바스크립트의 강력하고 매우 유연한 기능으로 코드를 재사용하고 객체를 결합할 수 있다.
특히 프로토타입으로 상속을 구현할 수 있다.
상속은 프로그래머로 하여금 시스템의 일부 객체가 다른 객체의 더 전문화된 버전이라는 생각을 표현할 수 있게 해주는 객체 지향 프로그래밍 언어의 기능이다.
[참고] : MDN