function User(age, name){
this.age = age;
this.name = name;
}
User.prototype.mesage = function(){
return "I'm SanE";
}
User.prototype.hobby = "baseketball";
const San = new User(26, "SeoSan");
console.log(San.hobby);
console.log(San);
프로토타입 도식
위의 도식은 아래 코드를 나타낸 것이다.
let instance = new Constructor();
위의 도식의 흐름을 따라가면 다음과 같다.
이제 가장 처음에 보여준 코드와 비슷한 코드를 통해 확인해보자.
function User(age, name){
this.age = age;
this.name = name;
}
User.prototype.getName = function(){
return this.name;
}
let san = new User(27, "SeoSan");
console.log(san.__proto__.getName()) // undefined
console.log(san.__proto__ === User.prototype); // true
위의 결과를 예상해보면 왜 첫번째 결과가 undefined
가 나오는지 이해가 안될 수도 있다.
분명 san.__proto__
와 User.prototype
은 서로 같은 곳을 바라보고 있다.
문제의 원인은 this 바인딩이 잘못되어 있기 때문이다.
이번에 설명할 부분이 실행 컨텍스트가 아니라 prototype이기 때문에 간략하게 설명하면
User.prototype.getName()
로 메서드로서 함수가 호출이 되었을 때 this가 가리키는 곳은 "." 앞에 써있는 객체를 바라보기 때문에
san
을 가리키는 것이 아니라 san.__proto__
가 된다.
여기서 그럼 내가 원하는대로 하려면 간단하다 san.__proto__
에 name
을 선언하면 된다.
function User(age, name){
this.age = age;
this.name = name;
}
User.prototype.getName = function(){
return this.name;
}
let san = new User(27, "SeoSan");
san.__proto__.name = "dannysir";
console.log(san.__proto__.getName()) // dannysir
console.log(san.__proto__ === User.prototype); // true
그럼 애초에 this를 san
으로 하고 싶으면 어떻게 할까?
답은 아주 간단하다.
인스턴스에서 바로 메서드를 사용하면 된다.
let san = new User(27, "SeoSan");
let tmp = new User(20, "tmpName");
console.log(san.getName()); // "SeoSan"
console.log(tmp.getName()); // "tmpName"
그런데 이쯤에서 이상할 것이다.
나는 분명 User.prototype
에 getName()
메서드를 생성했다.
그런데 어떻게 `__proto__
없이 바로 메서드를 instance에서 사용하는걸까???
그 이유는 __proto__
가 생략 가능한 프로퍼티이기 때문이다.
그러면 또 복잡해질 것이다. 그럼 san.__proto__.getName()
에서 생략이 가능하다고 했으니까 san.getName()
이랑 결과가 같아야 하는거 아닌가?
이렇게 복잡하게 생각하면 답이 없다. 이런 "생략 가능한 프로퍼티" 라는 개념은 언어를 설계하고 창시한 사람의 아이디어이기 이해를 하지말고 받아들여야 할 것 같다.
여기서 우리가 생각할 것은 하나이다.
__proto__
를 생략하면 this가 인스턴스를 가리키고, 생략하지 않으면 __proto__
자체를 가리킨다는 것이다.
이제 위의 지식을 바탕으로 배열 두개를 크롬에서 찍어보자.
let arr = [1,2,3,4];
console.dir(arr);
console.dir(Array);
먼저 처음 보이는 arr의 결과에서 Prototype열어보면 우리가 배열에 사용하는 모든 메서드들이 들어가 있다.
그런데 Array의 결과를 보면 서로 조금 다른걸 알 수 있다.
prototype에 들어있는 메서드는 같지만, 정적 메서드인 from()
, isArray()
등이 보인다.
이 결과를 그림으로 표현하면 다음과 같이 표현할 수 있을 것이다.
이 그림을 보면 왜 isArray()
같은 정적 메서드를 반드시 Array와 함께 써야하는지 이해할 수 있다.
let arr = [1,2,3,4];
Array.isArray(arr); // true
arr.isArray(); // TypeError
이제 프로토타입 체인이 무엇인지 알아보기 앞서 한가지 미리 확인하자.
위의 사진은 {a:1}
객체를 크롬에 찍어보면 나오는 결과이다.
보면 Prototype 안에 우리가 익숙한 hasOwnProperty()
같은 메서드들이 보인다.
이제 다시 우리가 아까 봤던 배열의 Prototype을 살펴보자.
그럼 다음과 같은 구조로 되어있는걸 알 수 있다.
prototype : Array(0)
저 회색으로 되어 있는 객체 prototype을 열면 우리가 저 위에서 봤던 객체 Prototype과 동일한 내용으로 이루어져 있다는 것을 확인할 수 있다.
왜 그럴까?
그 이유는 바로 prototype 객체가 바로 "Object" 즉 "객체"이기 때문이다.
따라서 모든 객체의 __proto__
에는 Object.prototype
이 연결된다.
이제 우리가 알기 쉽게 그림으로 표현하면 다음과 같다.
이처럼 어떤 데이터의 __proto__
내부에서 다시 __proto__
가 연쇄적으로 이어진 것을 바로
프로토타입 체인이라고 하고 이 체인을 따라서 검색하는 것을 프로토타입 체이닝이라고 한다.
클래스 정의
class User{
...
}
constructor(): 클래스 생성자 함수
class User {
constructor() {
...
}
}
class User {
constructor(){
this.name = "SanE"
}
}
const san = new User();
console.log(san);
class User {
constructor(name){
this.name = name;
}
}
const san = new User("SanE");
console.log(san.name);
class User {
constructor(name){
this.name = name;
}
get_message(){
return "Hello";
}
}
const san = new User("SanE");
console.log(san.get_message());
class Animal {
constructor(){
this.name = name;
}
}
class User extends Animal {
consturctor(name, brand) {
super(name);
this.brand = brand;
}
}
const san = new User("SanE", "jnu");
console.log(san);
class Test1 {
constructor(name){
this.name = name;
}
get_message(){
return "Hello";
}
}
class Test2 extends Test1{
constructor(name, brand){
super(name);
this.brand = brand;
}
get_message(){
return "Hello, World!";
}
}
const san = new Test2("SanE","JNU");
console.log(san.get_message());
class User {
constructor(name){
this.name = name;
}
get_message(){
return "Hello";
}
}
User.prototype.age = 26;
const san = new User("SanE");
console.log(san.name);
console.log(san.OwnProperty.name);
console.log(san.OwnProperty.age);
SanE
true
false
class Position {
constructor(score) {
this.score = score;
this.grade;
}
setGrade(grade) {
this.grade = grade;
}
}
class Student extends Score{
constructor(score, name) {
super(score);
this.name = name;
this.age = 0;
}
setAge(age) {
this.hp = age;
}
}
만약 위와 같이 있다면 기본적으로 Position 클래스의 메서드는 Position의 프로토타입에 들어가게 된다.
그리고 extends를 통해 상속을 진행한다고 가정했을 때 아래와 같이 프로토타입 체인이 이어진다.
Position ------ Position.prototype (setGrade())
|
|
|
|
Student ------ Student.prototype (setAge())