[JavaScript] 프로토타입 (prototype)

2022. 7. 20. 20:48WEB/Javascript

# javascript는 prototype기반언어 이다. 

- javascript의 모든 객체들이 메소드와 속성들을 상속받기위한 템플릿으로써 프로토타입 객체(prototype object)를 가진다는의미. 

- 상속되는 속성과 메소드들은 해당 객체가 아니라 prototype속성에 정의 되어 있다.

- 즉 prototype은 상속받은 멤버들이 정의되는 곳이다.(해당 객체가 복사되는것이 아닌 마치 체인을 타고 올라가며 접근하는것 처럼 '참조'하고 있음)

- 프로토타입 객체는 '__proto__ 속성으로 접근 가능한 내장 객체이고,  'prototype 속성'은 상속 시키려는 멤버들이 정의된 객체를 가리킨다.

 

[[Prototype]]은 __proto__로 접근이 가능하고 obj.toString은 prototype chain에 의해 obj.__proto__.constructor 처럼 찾아간다

 

# prototype chain

- prototype객체가 상위 prototype 객체로부터 메소드와 속성을 상속받을 수 있고 이 prototype객체 역시 상위 prototype객체에서 상속받을수 있다. 이를 prototype chain이라 부른다.
- 하위 prototype에서 특정 프로퍼티를 찾을 때 상위 prototype 프로퍼티 까지 찾는것도 prototype chain의 특징이다.
- 따라서 JavaScript는 prototype chain을 통해 상속을 구현한다.

# [[Prototype]]

- JavaScript의 모든 객체에는 숨김 프로퍼티가 존재하고 이를 '[[Prototype]]' 이라 부른다
- 다른 객체를 참조하는 경우 참조 되는 대상을 'prototype'이라 부른다.
- 만약 객체의 프로퍼티를 읽으려고 하는데 해당 프로퍼티가 존재하지 않으면 [[Prototype]]에서 프로퍼티를 찾는다. -> 프로토타입 상속 이라 부른다.

 

#  __proto__

- [[Prototype]] 프로퍼티는 __proto__를 사용하여 값을 설정할 수 있다.

let plants = {
	photosynthesis : true
}

let Rose = {
	color: "red"
}

let flower = {
	petal : 5,
    __proto__ : Rose
}

console.log(Rose.photosynthesis) // undefined

Rose.__proto__ = plants;		/* [[Prototype]]의 프로퍼티 값 할당.*/

console.log(Rose.photosynthesis) // true

- __proto__는 [[Prototype]]의 getter,setter 역할을 한다. 

- Rose에 photosynthesis라는 프로퍼티가 없기 때문에 [[Prototype]]의 프로퍼티를 자동으로 탐색함.

console.dir(Rose)
console.dir(Rose.__proto__)

- [[Prototype]]이 Plants객체를 참조하게 만듬

- 즉 Rose는 Plants를 상속 받는다 라고 할 수 있음. 객체지향형 프로그래밍의 상속과 비슷.

- 마찬가지로 Plants에 함수가 멤버로 있다면 Rose에서도 사용이 가능해진다.

- 근래엔 Object.getPrototypeOf , Object.setPrototypeOf를 사용한다고함.

- __proto__의 값은 객체나 null만 가능하다.

- 하위 체인에서 상위 체인의 프로퍼티를 수정할수 없다. 수정시 해당 체인의 프로퍼티가 추가되는거로 간주한다. -> getter만 가능하다는 뜻

#  for...in 반복문

 

- 상속 프로퍼티도 순회 대상에 포함이 된다.

- hasOwnProperty(key)는 상속프로퍼티를 순회 대상에서 제외할 수 있다.

- Object.keys, Object.values 는 기본적으로 상속 프로퍼티를 제외하고 동작한다.

 

# 내장 객체의 프로토타입

let sampleobj = {};
alert ( sampleobj ) // [object Object]

- sampleobj  = {} 는 sampleobj = new Object() 과 같다. 
- new 키워드를 이용해 Object()함수로 생성자 함수를 호출하면 이 생성자 함수의 prototype은 다양한 메서드가 구현되어있는 객체인 Object의 Object.prototype을 참조한다.(위에 설명한 일반 객체를 가리키는 것과는 다름)

let sampleobj = {
	__proto__ : Object.prototype	//Object를 참조 하는게 아님
}

- sampleobj.toString()을 호출하면 Object.prototype의 프로퍼티인 toString()을 가져오게 된다.

- 배열의 경우도 new Array()로 Array.prototype을 [[Prototype]]이 참조하여 만들어진다.
- Date, Function도 위와 같다.
- 모든 객체 ( Array.prototype포함 )의 상위에는 Object.prototype이 존재하고 그 상위는 null을 가리킨다.

let arr = [1, 2, 3];

arr.__proto__ === Array.prototype				//true
arr.__proto__.__proto__ === Object.prototype	//true



 

# 원시값

- 원시값은 객체가 아니기때문에 실질적으로 프로퍼티가 존재하지 않는다.
- 원시값도 사용하려면 new연산자를 이용해서 할당해줘야하는데 이때는 임시 래퍼 (wrpper)객체가 생성되고, 메서드를 제공하고 난 뒤에는 사라진다.
- null 과 undefined에는 해당하는 래퍼가 존재하지 않는다. 프로토타입도 존재하지 않는다.

 

# 내장 prototype에 메서드 추가

- Array.prototype에 객체에 키를 추가하듯이 프로퍼티를 추가 할 수 있다.
- 이렇게 추가하면 해당 Array.prototype에 의해 생성된 배열은 추가한 프로퍼티를 사용할 수 있게 된다.
- 여러가지 충돌문제로 인해 사용을 금지시 한다.
- 필요시 "메서드 빌리기" 같은 방법을 사용할 수 있다.
let obj = {
  0: "Hello",
  1: "world!",
  length: 2,
};

obj.join = Array.prototype.join;

alert( obj.join(',') ); // Hello,world!​

Array.prototype.join = F // join함수를 F함수로 바꿔버림 -> 배열들의 join메서드들은 F함수가 실행됨 ->프로토타입체인 특성

 

# __proto__ 를 '지양'

let plants = {
	photosynthesis : true
};

let Rose = {};
Rose.__proto__ = plants; //이 과정을

let Rose = Object.create(plants); //이렇게 바꿈.

# 함수의 prototype 프로퍼티

- 생성자 함수의 프로토타입이 객체인 경우에 new연산자를 통해 만든 객체는 생성자 함수의 프로토타입 정보를 이용해 [[Prototype]]을 설정한다
- 생성자 함수(F).prototype으로 사용될 객체를 정의
let plants = {
	photosynthesis : true
};

function rose(name) {
	this.name = name;
}

rose.prototype = plants;	// plants를 참조하는 객체를 생성

let Rose = new rose();  // Rose.__proto__ = photosynthesis

- 생성자 함수에 기본으로 세팅되는 프로퍼티(F.prototype)는 [[Prototype]]과 다릅니다. F.prototype은 new F()를 호출할 때 만들어지는 새로운 객체의 [[Prototype]]을 설정합니다.

 

# 함수의 디폴트 프로퍼티 

- 함수가 참조할 prototype을 할당하지 않으면 기본적으로 constructor 프로퍼티 하나만 있는 객체를 가리킨다.
- constructor 프로퍼티는 함수 자기 자신을 가리킨다. 모든 함수는 기본적으로 가지고 있기 때문에 해당 함수의 생성자를 알고 싶다면 constructor를 확인해 보면 된다.
- constructor : 인스턴스가 초기화될 때 실행하는 생성자 함수.
function Rose() {}

/* 
디폴트 prototype
Rose.prototype = { constructor: Rose };
*/​

 

 

 

- Array는 클래스다. 지금가지 써왔던 배열은 Array클래스의 인스턴스였고  Array.메소드 들은 Array클래스의 prototype에서 가져온것 이다.

- push, slice ... 은 Array.prototype에 구현되어있다

 

class Human {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  sleep() {
    console.log(`${this.name}은 잠에 들었습니다`);
  }
}

let kimcoding = new Human('김코딩', 30);

Human.prototype.constructor === Human; // 결과는?
Human.prototype === kimcoding.__proto__; //결과는?
Human.prototype.sleep === kimcoding.sleep; //결과는?

 

 

# reference

https://ko.javascript.info/prototype-inheritance

https://ko.javascript.info/native-prototypes

https://ko.javascript.info/prototype-methods 

https://ko.javascript.info/function-prototype

https://developer.mozilla.org/ko/docs/Learn/JavaScript/Objects/Object_prototypes