
Js 는 Prototype 기반 언어로, 모든 객체는 숨겨진 [[Prototype]] 내부 슬롯을 가지고 있다.
이는 부모 역할을 하는 또 다른 객체를 참조한다.
이 링크를 따라가면서 프로퍼티나 메서드를 탐색하는 과정을 Prototype Chain 이라고 한다.
const student = { name: "Lee", score: 90 }
// student 에는 hasOwnProperty 가 없음
console.log(student.hasOwnProperty("name")) // true
.위 코드가 정상적으로 동작하는 이유는 student 객체에서 hasOwnProperty 를 찾지 못하면 [[Prototype]] 이 가르키는 Object.prototype 에서 메서드를 찾기 때문이다.
객체 생성 방식은 크게 3가지가 있는데,
| 생성 방식 | 내부적으로 호출되는 생성자 | 프로토타입 객체 |
|---|---|---|
| 객체 리터럴 | Object() | Object.prototype |
| Object 생성자 | Object() | Object.prototype |
| 생성자 함수 사용 | 사용자 정의 생성자 | 생성자함수.prototype |
객체 리터럴 {}을 사용하면 내부적으로 Object() 생성자가 호출된다.
따라서 객체 리터럴의 프로토타입은 항상 Object.prototype 이다.
const person = { name: "Lee", gender: "male" }
console.log(person.__prototype__ === Object.prototype) // true
console.log(Object.prototype.constructor === Object) // true
생성자 함수로 객체를 만들면 인스턴스의 [[Prototype]] 은 생성자 함수의 prototype을 가르킨다.
function Person(name) {
this.name = name
}
const foo = new Person("Lee")
console.log(foo.__prototype__ === Person.prototype)
console.log(Person.prototype.__proto__ === Object.prototype)
프로토타입 객체도 일반 객체이므로 프로퍼티나 메서드를 자유롭게 추가할 수 있다.
이때 추가된 내용은 체인에 반영된다.
Person.prototype.sayHello = function() {
console.log(`Hi! my name is ${this.name}`)
}
foo.sayHello()
원시 타입(문자열, 숫자 등) 은 객체가 아니지만, 마치 객체처럼 동작한다.
이는 원시값을 일시적으로 객체래퍼(String, Number 등)로 변환하기 때문이다.
let str = "test"
console.log(str.toUpperCase()) // TEST
이는 내부적으로 String.prototype 을 참조하기 때문에 가능한 동작이다.
프로토타입은 동적으로 변경할 수 있다.
단, 변경 시점 이전에 생성된 객체에는 영향을 주지 않는다.
function Person(name) {
this.name = name
}
const foo = new Person("Lee")
Person.prototype = { gender: "male"}
const bar = new Person("Kim")
console.log(foo.gender) // undefined
console.log(bar.gender) // "male"
프로토타입 변경 전 인스턴스는 기존 프로토타입을 따르고 변경 이후 인스턴스는 새 프로토타입을 따른다.
Person.prototype.gender = "male"
const foo = new Person("Lee")
console.log(foo.gender) // "male"
foo.gender = "female" // 프로토타입이 아닌 foo 자체에 프로퍼티 추가
console.log(foo.gender) // "female"