CH19 프로토타입

liz·2023년 3월 16일
0

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

자바스크립트의 모든 객체는 자신의 부모 역할을 담당하는 객체와 연결되어있다. 이때의 부모 객체를 프로토타입객체 또는 줄여서 프로토타입이라 한다.

19.1 객체지향 프로그래밍

프로그램을 명령어 또는 함수의 목록으로 보는 전통적 명령형 프로그래밍의 절차지향적 관점에서 벗어나 객체(여러 개의 독립적 단위)의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임을 말한다.

  • 속성을 통해 여러 개의 값을 하나의 단위로 구성한 복합적인 자료구조(추상화됨)을 객체라고 한다.
//이름과 주소 속성을 갖는 객체
const person = {
    name: 'Lee',
    address: 'Seoul'
};

console.log(person);
  • 객체지향 프로그래밍은 객체의 상태를 나타내는 데이터(property)와 상태 데이터를 조작할 수 있는 동작(method)을 하나의 논리적 단위로 묶어 생각한다. 따라서 객체는 상태 데이터와 동작을 하나의 논리적 단위로 묶은 복합적인 자료구조라고 할 수 있다.
const circle = {
    radius: 5, //반지름
    getDiameter() { //원의 지름
        return 2 * this.radius;
    },
    getPerimeter() { //원의 둘레
        return 2 * Math.PI * this.radius;
    },
    getArea() { //원의 넓이
        return Math.PI * this.radius;
    }
};

console.log(circle);

console.log(circle.getDiameter); //[Function: getDiameter]
console.log(circle.getPerimeter); //[Function: getPerimeter]
console.log(circle.getArea); //[Function: getArea]

19.2 상속과 프로토타입

  • getArea() 메서드가 중복 생성되고 중복 소유된다
//생성자함수
function Circle(radius) {
    this.radius = radius;
    this.getArea = function () {
        return Math.PI * this.radius ** 2;
    };
}

//반지름이 1인 인스턴스 생성
const circle1 = new Circle(1);
//반지름이 2인 인스턴스 생성
const circle2 = new Circle(2);

//Circle 생성자 함수는 인스턴스를 생성할 때마다 동일한 동작을 하는 
//getArea 메서드를 중복 생성하고 모든 인스턴스가 중복 소유한다.
//getArea 메서드는 하나만 생성해 모든 인스턴스가 공유해서 사용하는 것이 바람직
console.log(circle1.getArea === circle2.getArea); //false

=> 상속을 통해 불필요한 중복 제거

function Circle(radius) {
    this.radius = radius;
}

//Circle 생성자함수가 생성한 모든 인스턴스가 getArea 메서드를 
//공유해 사용할수 있도록 프로토타입에 추가한다. 
//프로토타입은 Circle 생성자함수의 prototype프로퍼티에 바인딩되어있다.
Circle.prototype.getArea = function () {
    return Math.PI * this.radius ** 2;
};

//인스턴스 생성
const circle1 = new Circle(1);
const circle2 = new Circle(2);

console.log(circle1.getArea === circle2.getArea); //true

Circle 생성자 함수의 인스턴스는 부모인 Circle.prototype의 모든 프로퍼티와 메서드를 상속받는다.
=> radiuss 프로퍼티만 개별 소유하고 공통 메서드는 상속받아서 사용해 코드 재사용성 높아짐

19.3 프로토타입 객체

19.3.1 proto 접근자프로퍼티

1 proto는 접근자 프로퍼티다.

const obj = {};
const parent = { x: 1 };

//getter 함수인 get __proto__가 호출되어 obj 객체의 프로토타입을 취득
obj.__proto__;
//setter 함수인 set __proto__가 호출되어 obj 객체의 프로토타입을 교체
obj.__proto__ = parent;

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

2 proto 접근자 프로퍼티는 상속을 통해 사용된다.

const person = { name: 'Lee' };

//person 객체는 __proto__ 프로퍼티를 소유하지 않는다.
console.log(person.hasOwnProperty('__proto__')); //false

//__proto__ 프로퍼티는 모든 객체의 프로토타입 객체인 Object.prototype 의 접근자 프로퍼티다.
console.log(Object.getOwnPropertyDescriptor(Object.prototype, '__proto__'));
/* { get: [Function: get __proto__], set: [Function: set __proto__],
  enumerable: false, configurable: true} */

//모든 객체는 Object.prototype의 접근자 프로퍼티 __proto__를 상속받아 사용할 수 있다.
console.log({}.__proto__ === Object.prototype); //true

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

const parent = {};
const child = {};

child.__proto__ = parent;
parent.__proto__ = child; //TypeError: Cyclic __proto__ value

프로토타입은 단방향 링크드 리스트로 구현되어야 한다. 즉, 검색방향이 한쪽으로만 흘러가야 한다. 그렇지 않으면 체인 종점이 존재하지 않기 때문에 무한 루프에 빠지게 된다.

4 proto 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장하지 않는다.

//obj는 프로토타입 체인의 종점. 따라서 Object.__proto__를 상속받을 수 없다.
const obj = Object.create(null);

//obj는 Object.__proto__를 상속받을 수 없다.
console.log(obj.__proto__);

//-> __proto__ 보다 Object.getPrototypeOf 메서드를 사용하는 편이 좋다.
console.log(Object.getPrototypeOf(obj));

프로토타입을 교체하고 싶은 경우에는 Object.setPrototypeOf 메서드를 사용할 것이 권장된다.

const obj = {};
const parent = { x: 1 };

//obj 객체의 프로토타입을 취득
Object.getPrototypeOf(obj); //obj.__proto__ 
//obj 객체의 프로토타입을 교체
Object.setPrototypeOf(obj); //obj.__proto__ = parent;

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

19.3.2 함수객체의 prototype 프로퍼티

함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다.

//함수 객체는 프로토타입 프로퍼티 소유
(function () { }).hasOwnProperty('prototype'); //true

//일반 객체는 프로토타입 프로퍼티 무소유
({}).hasOwnProperty('propertype') //false

prototype 프로퍼티는 생성자 함수가 생설할 객체(인스턴스)의 프로토타입을 가리킨다.

//1. 화살표 함수는 non-constructor
const Person = name => {
    this.name = name;
}

//non-constructor는 prototype 프로퍼티를 소유하지 않는다.
console.log(Person.hasOwnProperty('prototype')); //false

//non-constructor는 프로토타입을 생성하지 않는다.
console.log(Person.prototype); //undefined

//2. 메서드 축약 표현으로 정의한 메서드는 non-constructor이다.
const obj = {
    foo() { }
};

console.log(obj.foo.hasOwnProperty('prototype')); //false
console.log(obj.foo.prototype); //false
  • 모든 객체가 가지고있는(상속받은) proto접근자 프로퍼티와 함수 객체만이 가지고 있는 prototype 프로퍼티는 결국 동일한 프로토타입을 가리키지만, 사용하는 주체가 다르다.
    • __proto__접근자 프로퍼티 : 객체가 자신의 프로토타입에 접근 또는 교체하기 위해 사용
    • prototype 프로퍼티 : 생성자 함수가 자신이 생성할 객체(인스턴스)의 프로토타입을 할당하기 위해 사용
function Person(name) {
    this.name = name;
}

const me = new Person(('Kook'));

//Person.protytope과 me.__proto__는 동일한 프로토타입을 가리키다.
console.log(Person.prototype === me.__proto__); //true

19.3.3 프로토타입의 constructor 프로퍼티와 생성자함수

모든 프로토타입은 constructor 프로퍼티를 갖는다. 이 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리키며, 이 연결은 생성자 함수가 생성될 때, 즉 함수 객체가 생성될 때 이뤄진다.

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

const you = new Person('Lee');

console.log(me.constructor === Person); //true

you객체가 constructor 프로퍼티를 통해 생성자 함수와 연결되고, you 객체는 Person.prototype(부모)에서 constructor 프로퍼티를 상속받아 사용할 수 있다.

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

생성자 함수에 의해 생성된 인스턴스는 프로토타입의 constructor 프로퍼티에 의해 생성자 함수와 연결된다. 이떄 constructor 프로퍼티가 가리키는 생성자 함수는 인스턴스를 생성한 생성자 함수다.

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

//add 함수 객체를 생성한 생성자 함수는 Function이다.
const add = new Function('a', 'b', 'return a + b');
console.log(add.constructor === Function); 

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

constructor 속성 : 어떤 생성자 객체를 통해 생겨난 인스턴스 인지를 알려주는 역할. 즉 생성자 객체를 반환한다.

  • 리터럴 표기법으로 객체를 생성하는 것과 같이, new 연산자와 함께 생성자 함수를 호출해 인스턴스를 생성하지 않는 객체 생성 방식도 있다.
//객체 리터럴
const obj = {};

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

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

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

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

//obj 객체는 Object 생성자 함수로 생성한 객체가 아니라 객체 리터럴로 생성했다.
const obj = {};

// 하지만 obj 객체의 생성자 함수는 Object 생성자 함수이다.
console.log(obj.constructor === Object); //true

결론: 프로토타입의 constructor 프로퍼티를 통해 연결되어있는 생성자 함수를 리터럴 표기법으로 생성한 객체를 생성한 생성자 함수로 생각해도 무리는 없다.

출처: https://velog.io/@minj9_6/JavaScript-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%8502

19.5 프로토타입의 생성시점

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

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

19.6 객체 생성 방식과 프로토타입의 결정

19.6.1 객체 리터럴에 의해 생성된 객체의 프로토타입

19.6.2 Object 생성자 함수에 의해 생성된 객체의 프로토타입

19.6.3 생성자함수에 의해 생성된 객체의 프로토타입

19.7 프로토타입 체인

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

19.9 프로토타입의 교체

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

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

19.10 instanceof 연산자

19.11 직접 상속

19.11.1 Object.create에 의한 직접상속

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

19.12 정적 프로퍼티/메서드

19.13 프로퍼티 존재 확인

19.13.1 in 연산자

19.13.2 Object.prototype.hasOwnProperty 메서드

19.14 프로퍼티 열거

19.14.1 for...in 문

19.14.2 Object.keys/values/entries 메서드

profile
프론트엔드 개발자 준비중

1개의 댓글

comment-user-thumbnail
2023년 3월 18일

리즈님 저 스터디 방장입니다!
힘든거 때문이시라면 다시 들어오세요 여태까지 잘하셨어요 지금 포기하기엔 아쉽네요

답글 달기