[modern JS Deep Dive] - 19장 . 프로토타입

유선향·2025년 1월 18일
0

<modern_JS_Deep_Dive>

목록 보기
20/44

객체지향 프로그래밍

  • 자바스크립트는 명령형, 함수형, 프로토 타입 기반 객체지향 프로그램을 지원하는 멀티 패러다임 프로그래밍 언어
  • 절차 지향적 관점에서 벗어나 여러개의 독립적 단위 ( =객체) 의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임
객체
const object = {
	test: 상태를 나타내는 데이터 (프로퍼티),
	getTest(){ //메소드
		동작
	},
};

결론: 객체는 프로퍼티와 메소드를 하나의 논리적인 단위로 묶은 복합적인 자료구조 이며, 객체 지향 프로그래밍은 객체로 프로그램을 표현하려는 패러다임 이다.


상속과 프로토 타입

자바스크립트는 프로토 타입을 기반으로 상속을 구현

function Test(x) {
	this.x = x;
} 

Test.prototype.getAnswer = function () { 
	return
	}
	//Test 생성자 함수가 생성한 모든 인스턴스가 getAnswer 메서드를 공유해서 사용하도록 
	//프로토타입에 추가 
	
// 인스턴스 생성
const test1 = new Test(1)
const test2 = new Test(2)

console.dir( test1) //prototype 참조 확인 가능
console.dir( test2) //prototype 참조 확인 가능 

prototype 객체 (= 프로토 타입 , === 부모 객체)

  • 객체간 상속 구현을 위해 사용
  • 생성자 함수가 생성한 모든 인스턴스는 자신의 프로토 타입, 즉 상위 부모 객체의 모든 프로퍼티와 메소드를 상속
  • 상속받은 하위 객체는 자신의 프로퍼티처럼 자유롭게 사용 가능 test1.getAnswer

모든 객체는 [[prototype]] 이라는 내부슬롯, prototype의 참조 을 가진다.

prototype[[prototype]] 값의 결정

  • 객체 생성 방식에 따라 prototype이 결정 → 내부 슬롯의 값 [[prototype]]prototype의 참조이므로 객체 생성 방식에 따라 값도 저장
  • [[prototype]]은 프로퍼티가 아니므로 직접 접근할 수 있는 수단 x,
  • 아래 접근자 프로퍼티로 간접적으로 접근

__proto__ 접근자 프로퍼티

  • 자신의 [[prototype]] 에 간접적으로 접근
  • 모든 객체가 상속을 통해 접근자 프로퍼티 사용가능
console.dir( test1.__proto__)
  • 상호 참조에 의해 서로가 자신의 프로토 타입이 되는 경우, 즉 프로토 타입 체인이 생성되는 것을 방지함.
  • 프로토타입 체인은 단방향 링크드 리스트로 구현되어야 하는데, 양쪽으로 순환 참조하는 프로토타입 체인이 만들어지면 종점이 존재 하지 않고, 프로토 타입 체인에서 검색시 무한루프에 빠짐

그러나 __proto__ 를 사용할 수 없는 경우가 더러 있기에

Object.getPrototypeOf 메소드(참조획득),

Object.setPrototypeOf 메소드(프로토타입 교체) 사용을 권장

console.dir(Object.getPrototypeOf(test1)); 
Object.setPrototypeOf(test2, { newMethod: () => console.log("Hello") });
console.dir(test2);

함수객체의 프로토타입 프로퍼티

//함수 객체는 prototype 프로퍼티를 소유한다. -> 생성자함수로 사용할 수 있게 하기 위해
(function () {}).hasOwnProperty('prototype') //true

//일반 객체는 prototype 프로퍼티를 소유하지 않는다
({}).hasOwnProperty('prototype') //false

결론 : 따라서 생성자 함수로서 호출 할 수 없는 함수, 즉 non-constructor 인 화살표 함수, 함수형 컴포넌트 등은 prototype 프로퍼티를 소유하지 않는다.


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

리터럴 표기법에 의해 생성된 객체도 프로토 타입이 존재 (생성자 함수로 인식)

그러나 해당 객체가 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 단정할수 없다.

//객체 리터럴
const obj = {}

//함수 리터럴
const add = function(a,b) {return}

//배열 리터럴
const arr = [1,2,3]

//정규 표현식 리터럴
const test = /is/ig

console.log(obj.constructor); //Object.prototype
console.log(add.constructor); //Function.prototype
console.log(arr.constructor); //Array.prototype
console.log(test.constructor); //RegExp.prototype

객체 리터럴 문법을 사용해 엔진이 자동으로 프로토 타입 체인을 따라서 찾은 것이므로, Object 생성자가 직접 호출된 것은 아님.

프로토 타입과 생성자 함수는 단독으로 존재할 수 없기에 가상적인 생성자 함수를 가지면 자동으로 프로토타입도 연결된다.

그러나 리터럴 표기법으로 생성한 객체도 생성자 함수로 생성한 객체와 본질적인 면에서 큰 차이는 없기에, 객체.constructor 를 생성자 함수로 생각해도 무리는 없다.


프로토 타입의 생성 시점

프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성됨.

사용자 정의 생성자 함수와 프로토 타입 생성시점

// 인스턴스 생성
const test1 = new Test(1)
const test2 = new Test(2)

console.dir( test1) //prototype 참조 확인 가능
console.dir( test2) //prototype 참조 확인 가능 

function Test(x) {
	this.x = x;
} 

Test.prototype.getAnswer = function () { 
	return
	}
	
  1. Test 함수는 런타임 이전에 자바스크립트 엔진에 의해 먼저 실행 → 함수객체로 평가 & 프로토 타입도 생성
  2. 이때 생성된 프로토 타입은 prototype 프로퍼티에 바인딩 된다.

결론: 런 타임 이전에 프로토 타입이 생성!


빌트인 생성자 함수와 프로토타입 생성 시점

일반 함수와 마찬가지로 빌트인 생성자 함수가 생성되는 시점에 프로토타입이 생성된다. 모든 빌트인 생성자 함수는 전역 객체가 생성되는 시점(자바스크립트 코드실행시 가장 먼저 생성 ) 에 생성, 생성된 프로토타입은 빌트인 생성자 함수의 prototype 프로퍼티에 바인딩 된다.

결론: 전역객체가 생성되는 시점에 프로토타입이 생성!

프로토 타입 체인

자바스크립트는 객체의 프로퍼티에 접근하려고 할때 해당 객체에 접근하려는 프로퍼티가 없다면 [[prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로토타입의 프로퍼티를 순차적으로 검색한다. 이를 프로토타입 체인이라고 한다 프로토타입 체인은 자바스크립트가 객체지향 프로그래밍의 상속을 구현하는 매커니즘이다.

결론: 프로토타입 체인의 최상위에 위치하는 객체는 언제나 Object.prototype 이다 따라서 모든 객체는 Object.prototype을 상속받는다. Object.prototype을 프로토타입 체인의 종점이라한다.

console.dir( test1.__proto__.__proto__.__proto__) //null

이에 반해 프로퍼티가 아닌 식별자 는 스코프 체인에서 검색한다 따라서 스코프 체인은 식별자 검색을 위한 매커니즘 이라고 할 수 있다.

결론 : 스코프 체인과 프로토타입 체인은 서로 연관없이 별도로 동작하는 것이 아니라 서로 협력하여 식별자와 프로퍼티를 검색하는데 사용된다.


오버라이딩과 프로퍼티 섀도잉

프로토타입이 소유한 프로퍼티를 프로토타입 프로퍼티, 인스턴스가 소유한 프로퍼티를 인스턴스 프로퍼티라고 부른다.

프로퍼티 섀이딩 : 부모 프로토타입의 프로퍼티랑 같은 이름의 프로퍼티를 인스턴스에 추가했을때, 프로토타입 체인을 따라 올라가지 않고 자식 객체에서 찾음 프로토타입 프로퍼티가 가려진다.

또한 하위객체를 통해 프로토 타입의 프로퍼티를 변경 또는 삭제하는것은 불가능 하다.

Test.prototype.sayHello = function () {
	console.log('hi')
	}
test1.sayHello() // hi

delete Test.prototype.sayHello

test1.sayHello() //error

결론: 상속관계에 의해 오버라이딩 & 프로퍼티 섀이딩 되므로, 프로토타입 프로퍼티를 변경 or 삭제시 하위객체에서 프로토 타입 체인으로 접근이 아니라 직접 접근해야한다.


프로토타입의 교체

프로토타입은 생성자 함수 또는 인스턴스에 의해 교체할 수 있다.

생성자 함수에 의한 프로토타입의 교체

프로토 타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴된다.

const Test = (function () {
    function Apple(x) {
    }

    Apple.prototype = {
        Hi() {
            console.log('hi');
        }
    };
    return Apple;
}());

// 인스턴스 생성
const test1 = new Test();

console.dir(test1); 
const Test = (function () {
    function Apple(x) {
    }

    Apple.prototype = {
        Hi() {
            console.log('hi');
        },
        constructor: Apple //constructor 프로퍼티를 되살린다.
    };
   
    return Apple;
}());

// 인스턴스 생성
const test1 = new Test();

console.dir(test1); 

결론: 생성자 함수로 인해 프로토 타입 교체시 constructor가 파괴되므로 프로퍼티에 추가 하여 되살리자!

인스턴스에 의한 프로토타입의 교체

function Test(x) {
	this.x = x;
} 
	
// 인스턴스 생성
const test1 = new Test(1)
const parent = {
sayHi(){
console.log('hi')
}}
Object.setPrototypeOf(test1,parent)

console.dir(test1) 

인스턴스~~ 와 생성자 함수~~ 의 차이

//Test 생성자 함수 자체에 prototype이 변경 된게 아님 
const test2 = new Test(1)
const test3 = new Test(1)
test1.sayHi();

test2.sayHi();
test3.sayHi();

결론 : 프로토타입은 직접 교체하지 않는 것이 좋다. 상속 관계를 인위적으로 설정하려면 직접 상속이 더 편리하고 안전하다. 또는 es6에서 도입된 클래스를 사용해라!

instanceof 연산자

console.log(test1 instanceof Test) //true
console.log(test1 instanceof Object) //

결론: 프로토타입 체인 상에 존재하는지 확인한다.

직접 상속

Object.create 에 의한 직접 상속

명시적으로 프로토타입을 지정하여 새로운 객체를 생성한다.

let TestObj = Object.create(프로토타입으로 지정할 객체)
let TestObj = Object.create(null) //암것도 안담김
console.dir(TestObj)
function Test(x) {
	this.x = x;
} 

Test.prototype.getAnswer = function () { 
	return 
	}
	
// 인스턴스 생성
const test1 = new Test(1)

let TestObj = Object.create(Test.prototype)
console.dir(TestObj) 

new 연산자 없이도 객체를 생성, 프로토타입을 지정 하면서 객체를 생성가능, 객체 리터럴에 의해 생성된 객체도 상속받을 수 있다

결론: esLint 에서는 하지말라고 함

객체 리터럴 내부에서 __proto__ 에 의한 직접 상속

es6에서는 객체 리터럴 내부에서 __proto__ 접근자 프로퍼티를 사용하여 직접 상속을 구현할 수있다.

const Proto = {'어쩌구'}

const obj = {
	저쩌구...,
	__proto_: Proto
}
console.log(Object.getPrototypeOf(obj)===Proto) ; //true

정적 프로퍼티 / 메서드

생성자 함수로 인스턴스를 생성하지 않아도 참조/호출 할 수 있는 프로퍼티/메서드

정적 프로퍼티/메서드는 생성자 함수가 생성한 인스턴스로 참조/호출 할수없다.

function Test(x) {
	this.x = x;
} 

Test.prototype.getAnswer = function () { 
	return
	}

Test.staticProp = 'static prop'	
// 인스턴스 생성
const test1 = new Test(1)

test1.hasOwnProperty('static prop') //false
Test.hasOwnProperty('static prop') //true

결론: 정적 프로퍼티/메소드는 인스턴스로 참조/호출 불가!

0개의 댓글