자바스크립트는 prototype 기반의 언어이다.
자바스크립트 코드의 대부분은 객체 구조를 띠고 있다. 이 객체들은 Object 라고 하는 대빵 생성자 함수의 instance 객체들이다. 함수, 배열, 우리가 만드는 객체 하나 하나들이 다 이 대빵 Object 생성자 함수
의 instance 객체
이다.
모든 함수는 생성되는 순간 prototype 이라고 불리우는 객체를 속성으로 갖는다. (단, 화살표 함수는 이 prototype 객체가 생기지 않는다. ) 생성자 함수 역시 Object.prototype 이라는 원형 객체를 가지고 있으며, 이 prototype 은 해당 객체가 가지는 모든 메소드 함수들을 담고 있다. 우리가 아무 메소드도 정의하지 않는다면, 항상 디폴트로 가지는 것은 2 가지. constructor
와 __proto__
이다.
생성자 함수의 경우, constructor 는 자기 자신이 되고, 인스턴스 객체의 경우 constructor 는 자신의 생성자 함수를 바라본다. 그리고 인스턴스 객체들은 __proto__
라는 속성을 통해 자신의 생성자 함수의 prototype 객체로 연결된다.
위의 예시에서 보면,
1)
Object
라는 대빵 함수가 존재하고,
2) 내가 만든 임의의 greetings 라는 객체는
3)__proto__
를 통해 대빵 함수의 prototype 에 연결되었다.
프로토타입 체인이란, 그 이름에서 짐작할 수 있듯이 프로토타입들이 줄줄이 엮여있다는 뜻이다.
위의 예시에서, 나는 greetings 라는 객체에 hasOwnProperty 라는 메소드를 할당한 적이 없지만, 그 메소드를 사용할 수 있었다. 어떻게?
내가 객체의 어떤 속성에 접근하고자 할 때, 자바스크립트는 그 속성이 객체 자체 내에 있는지 (즉, own property로 있는지) 먼저 탐색한다. 없다면, __proto__
를 타고 올라가서 생성자 함수의 prototype 내에 그 속성이 있는지 탐색한다. 위의 예시의 경우, greetings 는 hasOwnProperty를 정의한 적 없지만, Object.prototype 은 Object.prototype.hasOwnProperty 라는 속성을 갖고 있으므로 우리는 답을 얻을 수 있었다.
자바스크립트는 우리가 원하는 속성이 있는지 __proto__
를 타고 올라가면서 상위 객체들의 prototype 을 모두 탐색하며, 만약 최종적으로 null 을 만나서 탐색이 종료될 때까지 그 속성을 찾지 못한다면, undefined 를 반환한다.
아래의 경우를 보자.
const iAmFunc = function() {
this.color = "brown";
this.habitat = "mountain";
}
const iAmObj = new iAmFunc();
const iAmObj2 = new iAmFunc();
console.log(iAmObj); // {color: "brown", habitat: "mountain"}
console.log(iAmObj2); // {color: "brown", habitat: "mountain"}
iAmFunc 라는 생성자 함수를 통해 만들어진 인스턴스 객체 iAmObj, iAmObj2 는 생성자함수의 속성인 color, habitat 를 가지고 태어난다. 즉 속성을 상속받은 것이다.
만약 우리가 생성자 함수에 어떤 메소드를 추가한다면, 같은 생성자 함수를 통해 만들어진 모든 instance 들은 (생성자 함수의 prototype 에 연결되어있으므로) 생성자 함수의 메소드를 다같이 공유하게 된다. 단, 반드시 생성자함수의 prototype 에 추가해야 한다. 그래야만 인스턴스 객체들 역시 그 추가된 속성을 바로 사용할 수 있다.
// 프로토타입 체인을 통한 탐색 과정
iAmObj.__proto__ === iAmFunc.prototype
iAmObj.__proto__.__proto__ === Object.prototype
iAmObj.__proto__.__proto__.__proto__ === null
// null 은 정의상 __proto__가 없으므로 여기가 프로토타입의 끝이다.
// 아래와 같이 생성자 함수와 인스턴스 객체 각각에 name 이라는 속성을 추가해보자.
iAmFunc.prototype.name = "bear";
iAmObj.name = "rabbit"
console.log(iAmObj.name) // "rabbit"
// name 이라는 자체 속성이 있는가? 그렇다. 답은 "rabbit".
console.log(iAmObj2.name) // "bear".
// name 이라는 자체 속성이 있는가? 아니오.
// 그렇다면 상위 prototype 객체에 name 이라는 속성이 있는가? 그렇다. 답은 "bear".
console.log(iAmObj2.hasBaby) // undefined.
// "hasBaby" 라는 자체 속성이 있는가? 아니오.
// 그렇다면 상위 prototype 객체에 있는가? 아니오.
// 쭉쭉 타고 올라가서 모든 prototype 체인을 검사하고도 못 찾았으므로 undefined.
iAmObj 의 상위 프로토타입 객체도 name 이라는 속성을 가지고 있었지만, iAmObj 객체 내에 자체적으로 name 이라는 속성을 갖고 있었으므로 prototype 체인 검사를 할 필요가 없었다. 이를 prototype shadowing 이라고 한다. 이 프로토타입 쉐도잉이 함수인 경우에는 method overriding 이라고 불리운다. 아래 예제를 보자.
자바스크립트에서는 어떤 함수든, 객체의 속성값으로 부여될 수 있으며 다른 속성값들과 동일하게 다뤄진다.
var iAmTheBoss = {
num: 7,
tool: function() {
return this.num + 3
}
};
console.log(iAmTheBoss.tool()) // 10;
// 위의 객체를 복사한 새로운 객체가 생겼다.
var hardWorker = Object.create(iAmTheBoss);
console.log(hardWorker); // {}
console.log(hardWorker.__proto__) // { num: 7, tool: function() ... }
console.log(hardWorker.num) // 7;
console.log(hardWorker.tool()) // 10;
// 빈 객체였던 hardWorker 에 num 이라는 자체 속성을 부여했다.
hardWorker.num = 0;
console.log(hardWorker.tool()); // 3;
hardWorker 객체는 바로 생성되었을 때 빈 객체이고, __proto__
를 통해 iAmTheBoss 객체를 바라보고 있으므로 그 안에 정의된 속성들을 사용할 수 있다. hardWorker 내에 자체 num 속성이 생기자, tool 메소드를 사용할 때 상위 객체를 탐색할 필요가 없어졌는데, 이렇게 자체적으로 가진 속성값이 우선하는 경우를 두고, 메소드 오버라이딩
이라고 한다.
💡 참고
메소드 오버라이딩(overriding), 오버로딩(overloading) 의 차이
우선 javascript 는 오버로딩을 지원하지 않는다. 주로 java 언어에서 이 두차이를 많이 비교한다. (관련 Stackoverflow 답변)
오버로딩
은 파라미터만 다르게 해서 같은 이름의 함수를 여러 번 정의할 수 있는 특징이며,오버라이딩
은 하위클래스에서 메소드를 '재정의' 하여 그것을 우선으로 사용하는 특징이다. super class 의 메소드는 위의 예시에서처럼 경우에 따라 사용할 수도 있고, 안 할 수도 있다.
그렇다. 모두 Object.prototype 의 인스턴스 객체들이며, Array.prototype, Function.prototype 에 정의된 메소드들이 있기 때문에 우리가 만드는 배열, 함수에서 그 내장 메소드들을 상속받아 사용해 올 수 있었던 것이다.
var obj = { a: ‘apple’ }
// obj —> Object.prototype —> null
var arr = [‘banana’, ‘cinnamon’ ]
// arr —> Array.prototype —> Object.prototype —> null
function func() { return ‘yo’ }
// func —> Function.prototype —> Object.prototype —> null