관리 메뉴

흰둥씨의 개발장

[자바스크립트] 딥다이브) 프로토타입 본문

BoOk/JS deep dive

[자바스크립트] 딥다이브) 프로토타입

돈워리비해삐 2023. 11. 19. 13:18

자바스크립트는 명령형, 함수형, 프로토타입기반 객체지향 프로그래밍을 지원하는 멀티 패러다임 프로그래밍 언어 
자바스크립트는 거의 모든 것이 객체임
ㄴ> 원시타입 값 제외한 나머지 값들(함수, 배열, 정규표현식, 객체...)는 모두 객체임 
 

  - 1) 객체 지향 프로그래밍 

ㄴ여러개의 독립단위(=객체)의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임 
ㄴ실제 세계의 사물이나 개념을 인식하는 철학적 사고를 프로그래밍에 접목하려는 시도에서 시작함 


- 객체 (object) : 속성을 통해 여러개의 값을 하나의 단위로 구성한 복합적인 자료구조 
                           상태 데이터(property)와 상태데이터를 조작하는 동작(method)을 묶어서 하나의 논리 단위로 묶어둔 것 


- 속성 (attribute / property) : 실체의 특징이나 성질, 속성으로 실체를 인식 구별할 수 있음


- 추상화 (abstraction) : 다양한 프로그램에서 필요한 속성만 간추려내어 표현하는 것
 

  - 2) 상속과 프로토타입

- 상속 (Inheritance) : 어떤 객체의 프로퍼티나 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것 
자바 스크립트는 프로토타입을 기반으로 상속을 구현하여 불필요한 중복을 제거함(기존코드를 재사용하여 중복없앰)

function myName (name){  //생성자 함수 
    this.name = name;
    this.sayhi = () => {
    	return 'Hi ' + this.name;
        }
   }
   
const name1 = new myName('흰둥씨');
const name2 = new myName('짱구');

console.log(name1.sayhi === name2.sayhi);  //false

//생성자함수에 의해 생성된 모든 인스턴스가 동일한 sayhi()메서드를 
//각자의 메모리에 담아 새로 생성하기 때문에 
//메모리 낭비임 => 이를 상속을 통해서 불필요한 중복제거 가능함
//상속으로 메모리를 절약해보자 (JS의 상속은 프로토타입 기반으로 구현한다)

function myName(name){  //생성자 함수 
	this.name = name;
   }

// myName 생성자함수에 넣었던 sayhi()메서드를 빼서, 
// 모든 인스턴스가 공유할 수 있게 프로토 타입에 추가하기 
myName.prototype.sayhi = function (){
	return 'Hi ' + this.name;
    };
    
//인스턴스 생성
const name1 = new myName('흰둥씨');
const name2 = new myName('짱구');

console.log(name1.sayhi === name2.sayhi);  //true

ㄴmyName생성자 함수가 생성한 모든 인스턴스는 자신의 프로토타입(= 상위 객체 역할)의 모든 프로퍼티와 메서드를 상속받음
ㄴ생성자 함수로 생성한 모든 인스턴스가 똑같은 메서드 공유해야 한다면 프로토타입으로 구현하자 (프로토타입 = 재사용할 것)

 
  - 3) 프로토타입 객체 

프로토 타입? 어떤 객체의 상위(부모) 객체의 역할 하는 객체, 객체 간 상속을 구현하기 위해 사용됨
모든 객체는 [[Prototype]] 내부 슬롯을 가짐(내부 슬롯에 직접접근X , 간접접근O)
ㄴ 모든 객체는 하나의 프로토타입을 가짐 
ㄴ[[Prototype]]의 값은 프로토타입의 참조
ㄴ프로토타입은 객체 생성 방식에 의해 결정(생성자 함수 : 함수prototype속성에 바인딩된 객체 / 객체리터럴 : Object .prototype)
ㄴ결정된 프로토타입은 [[Prototype]]에 저장됨


  3-1) __proto__ : 접근자 프로퍼티
ㄴ 자신의 프로토타입, 자신의 [[Prototype]] 내부에 접근 가능
  **접근자 프로퍼티는 값을 갖지 않고, 값을 읽거나 저장할 때 사용하는 접근자 함수([[Get]], [[Set]])로 구성됨 


모든 객체는 상속을 통해 Object.prototype.__proto__사용가능 (__proto__접근자 프로퍼티는 Object.prototype의 프로퍼티임)

const obj = {};
cosnt objA = {a:1};

obj.__proto__;         //get__proto__가 호출되어 obj의 프로토타입을 취득
obj.__proto__ = objA;  //set__proto__가 호출되어 obj의 프로토타입 교체

 

 __proto__접근자 프로퍼티를 통해 프로토타입에 접근하는 이유 : 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지 하기위해 

const 부모 = {};
const 자식 = {};

자식.__proto__ = 부모; //자식의 프로토타입을 부모로 설정
부모.__proto__ = 자식; //TypeError : __proto__가 순환참조 하지 못하도록 에러 발생시킴

//__proto__접근자 프로퍼티를 통해 프로토타입에 접근, 교체하도록 처리됨
//프로토타입 체인은 단방향 링크드 리스트로 구현되어야 함

 

__proto__접근자 프로퍼티를 코드내에서 직접 사용하는 것은 권장하지 않음(모든 객체가 접근자 프로퍼티를 사용할수 있는 것은 아님)

const obj = Object.create(null);
const objA = { a: 1 };

console.log(obj__proto__); //undefined 
//obj는 프로토타입 체인의 종점이기 때문에, Object.__proto__를 상속받을 수 없음 

//__proto__대신 프로토타입의 참조를 취득하고 싶은경우 
//Object.getPrototypeof메서드 사용권장
//교체시 Object.setPrototypeof메서드 사용권장

Object.getPrototypeof(obj);  //프로토타입 취득 
Object.setPrototypeof(obj, objA);  //obj객체의 프로토타입을 교체

console.log(obj.a);  //1

 
 

3-2) 함수객체의 prototype프로퍼티 
ㄴ함수 객체만 소유하는 prototype프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 말한다
    생성자 함수로 호출할수 없는 non-constructor(화살표함수, ...)는 prototype을 소유하지 않고, 프로토 타입생성하지 않음

const 화살표함수 = (화살표) => {this.화살표 = 화살표;}
console.log(화살표함수.hasOwnProperty('prototype'); // false
console.log(화살표함수.prototype); // undefined

const obj = {
	foo(){} //es6 메서드 축약으로 정의한 메서드는 non-constructor
}
console.log(obj.foo.hasOwnProperty('prototype'); // false
console.log(obj.foo.prototype); // undefined


ㄴ모든 객체가 소유하는 __proto__접근자 프로퍼티와 함수객체가 가지는 prototype프로퍼티는 동일한 프로토타입을 가리킴
    (사용주체는 다름)

  __proto__접근자 프로퍼티 prototype프로퍼티
소유 모든 객체 Constructor
프로토타입의 참조 프로토타입의 참조
사용주체 모든 객체 생성자 함수
사용목적 객체가 자신의 프로토타입에
접근or 교체 하기 위해사용
생성자 함수가 자신이 생성할 객체의 프로토타입을
할당하기 위해 사용
function 생성자함수(name) { this.name = name; };

const 생성된객체 = new 생성자함수("폴 아트레이데스");

console.log(생성자함수.prototype === 생성된객체.__proto__); // true

 

3-3) 프로토타입의 constructor 프로퍼티와 생성자 함수

function 생성자함수(name) { this.name = name; };

const 생성된객체 = new 생성자함수("폴 아트레이데스");

console.log(생성된객체.constructor === 생성자함수); // true
//"생성된객체"는 프로토타입인 생성된객체.prototype의 constructor프로퍼티를 "생성자함수"로부터 상속받아 사용가능

 

  - 4) 리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입

const obj = new Object(); // obj객체를 생성한 생성자 함수는 Object
console.log(obj.constructor == Object) // true;

const add = new Function('a', 'b', 'return a + b');
console.log(add.constructor == Function) // true;

리터럴 표기법에 의해 생성된 객체도 프로토타입이 존재하지만,
constructor프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 단정할수 없다.

하지만, 사실상 리터럴에 의해 생성된 객체, 함수, 배열, 정규식은 생성자함수의 프로토 타입과 같다고 생각하여도 무리없다. 
(객체, 함수, 배열, 정규식으로서 동일한 특성을 갖기 때문에...)

 

  - 5) 프로토타입의 생성 시점

프로토 타입은 생성자 함수가 생성되는 시점에 더불어 생성됨(프로토타입과 생성자 함수는 언제나 pair로 존재하기 때문)