프로토타입을 교체하는 것이 가능할까요? 가능합니다. 하지만 그럴 경우 기존의 생성자 함수의 프로토타입 객체가 가지고 있던 constructor 프로퍼티는 사라지게 되므로 이때 생성자 함수에 의해 생성된 인스턴스가 상속받는 프로토타입 객체는 생성자 함수의 프로토타입 객체가 아닌, 그 위에 최상위 객체이자 constructor 프로퍼티를 가지고 있는 Object.prototype이 됩니다.
즉 생성자 함수와 생성자 함수의 프로토타입 객체 간 연결이 끊어지게 될 뿐만 아니라 생성자함수의 프로토타입 프로퍼티가 기존의 프로토타입 객체의 프로퍼티나 메서드를 상속 받던 다른 인스턴스의 사용성에 문제가 생기게 되는 것이죠.
그러므로 프로토타입의 변경은 코드 테스트나 기존의 라이브러리의 확장을 위한 용도로 사용되기에 일반적인 상황에서 프로토타입을 변경하는 것은 권장되지 않습니다.
function Person(name, age) { // 생성자 함수 생성 this.name = name; this.age = age; } const me = new Person('John'); // 인스턴스 생성 // Person의 프로토타입에 객체 리터럴의 값을 할당하면 기존에 프로토타입 프로퍼티에 존재하던 객체(constructor 프로퍼티를 소지하고 있던)는 다른 객체에 의해 참조 될 수 있으나, 아무도 참조를 하지 않을 경우 가비지 컬렉터에 의해 소거됩니다. Person.prototype = { sayHello: function() { console.log(`Hello, my name is ${this.name}`); } }; const you = new Person('Alice'); me.sayHello(); // 에러 발생, 인스턴스 me의 경우 생성자 함수 Person의 프로토타입 프로퍼티에 할당된 객체에 sayHello 메서드가 없는 상태이기 때문. you.sayHello(); // 생성자 함수의 프로토타입 프로퍼티에 객체 리터럴 형태의 함수 값이 할당 되었으므로 "Hello, my name is Alice" 출력 가능 console.log(me.constructor === Person); // Person의 프로토타입이 변경 되기 전인 상태에서 생성자 함수의 프로토타입은 constructor를 가지고 있으므로 true를 반환 console.log(you.constructor === Object); // Person의 프로토 타입이 객체 리터럴로 바뀐 시점에는 프로토타입 프로퍼티에 할당 된 객체에 constructor가 없는 상태이므로 자동적으로 그 상위 부모인 Object의 프로토타입 객체가 가진 constructor에 의해 Object.prototype과 연결되게 되므로 true를 반환. console.log(you.constructor === me.constructor); // 프로토타입 체이닝에 따라 두 프로토타입은 다르기 때문에 false를 반환.
만약 실수나 얘기치 않은 상황으로 프로토타입이 변경되었을 경우, 프로토타입에 constructor 키워드로 프로퍼티와 생성자 함수간의 연결을 설정하는 방법으로도 해결이 가능합니다.
function Person(name, age) { this.name = name; this.age = age; } > const me = new Person('John'); > Person.prototype = { constructor: Person, // 프로토타입 프로퍼티를 해당 객체에 정의하고 값으로 Person 생성자 함수를 지정하면 생성자 함수와 해당 객체는 연결이 되어 이 객체는 프로토타입의 객체가 됩니다. sayHello: function() { console.log(`Hello, my name is ${this.name}`); } }; > const you = new Person('Alice'); > me.sayHello(); // 에러 발생, sayHello 메서드를 찾을 수 없음 you.sayHello(); // "Hello, my name is Alice" 출력 > // me와 you의 프로토타입 객체는 서로 다르더라도 해당 객체들이 가지고 있는 constructor 프로퍼티의 값으로 같은 생성자 함수인 Person을 가리키고 있기 때문에 true를 반환합니다. console.log(me.constructor === Person); // true; console.log(you.constructor === Person); // true; console.log(me.constructor === you.constructor); // true; // 두 인스턴스의 프로토타입은 다르므로 false를 반환합니다. console.log(Object.getPrototypeOf(me)); console.log(Object.getPrototypeOf(you)); console.log(Object.getPrototypeOf(you) === Object.getPrototypeOf(me)); >
이처럼 생성자 함수 내부에서 선언한 메서드나 프로퍼티, 혹은 같은 레벨의 프로토타입에서 선언된 메서드나 프로퍼티는 new 키워드로 생성된 해당 인스턴스들이 공유 및 사용 가능하지만, 생성자 함수와 같은 레벨(전역 레벨)에 있는 생성자 함수의 프로퍼티와 메서드는 인스턴스가 참조하거나 호출하지 못합니다.
이는 같은 레벨에서 구현된 생성자 함수의 프로퍼티와 메서드는 생성자 함수가 소유하여 직접 사용함을 의미함으로 좀 더 광범위하게 (내장 객체들 처럼) 사용이 가능함을 의미하기도 합니다.
이는 생성자 함수에 의해서 만들어진 프로퍼티와 메서드라 하더라도 이들이 클래스(생성자 함수) 자체에 속하지 인스턴스에 속하고 있는 것이 아니기 때문이며, 프로토타입 체이닝 선상에서 봤을때 이들 정적 메서드와 프로퍼티들은 인스턴스의 프로토타입 체이닝 경로에 존재하지 않아 검색이 안되기 때문입니다.
이러한 정적 메서드나 프로퍼티의 장점은, 클래스(생성자 함수)가 직접적으로 생성하는 것이기에 인스턴스를 생성하지 않고도 사용될 수 있으며, 상속이 된 메서드(prototype)더라 하더라도 this 키워드를 참조하고 있지 않다면 그 요소들 또한 정적 메서드나 프로퍼티로 바꾼 후 재사용 할 수 있기도 합니다.
// ① 정적 메서드와 정적 프로퍼티들을 호출 가능한 코드 예시) function Person(name){ this.name = name; } Person.prototype.sayHello = function(){ console.log(`Hi! im ${this.name}`) } Person.staticProp = `이건 정적 프로퍼티 입니다.` Person.staticMethod = function(){ console.log(`이건 정적 메서드 입니다.`) }; const me = new Person('Re_Go'); let thisIs = Person.staticProp; // 생성자 함수의 정적 메서드와 프로퍼티는 전역 변수에도 할당 가능하고 Person.staticMethod(); // 직접 호출도 가능하며 console.log(thisIs); // 빌트인 메서드에서도 사용 가능하나 me.staticMethod(); // 인스턴스만 사용 못합니다!