[Deep dive] 19. 프로토타입

정호·2023년 4월 29일
0

자바스크립트는 명령형, 함수형, 프로토타입 기반, 객체지향 프로그래밍을 지원하는 멀티 패러타임 프로그래밍 언어다.

객체지향 프로그래밍

객체지향 프로그래밍은 여러 독립적 단위, 객체의 집합으로 프로그램을 표현하는 프로그래밍 패러다임

추상화

다양한 속성 중에서 프로그맹에 필요한 속성만 간추려 표현한 것

ex)

// 이름과 주소 속성을 갖는 객체
const person = {
name: 'Lee',
address: 'Seoul'
};

console.log(person); // {name: "Lee", address: "Seoul"}

이름과 주소 속성으로 표현된 객체인 person 을 다른 객체와 구별하여 인식

상속과 프로토타입

자바스크립트는 프로토타입을 기반으로 상속을 구현하여 중복을 제거한다.

// 생성자 함수
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);

// Circle 생성자 함수가 생성한 모든 인스턴스는 부모 객체의 역할을 하는
// 프로토타입 Circle.prototype으로부터 getArea 메서드를 상속받는다.
// 즉, Circle 생성자 함수가 생성하는 모든 인스턴스는 하나의 getArea 메서드를 공유한다.
console.log(circle1.getArea === circle2.getArea); // true

console.log(circle1.getArea()); // 3.141592653589793
console.log(circle2.getArea()); // 12.566370614359172

Circle생성자 함수가 생성한 인스턴스는 자신의 프로토타입, 즉 상위 부모 객체 역할을 하는 Circle.prototype의 모든 프로퍼티와 메서드를 상속한다.

프로토타입 객체

모든 객체는 [[Prototype]]이라는 내부 슬롯을 가지며, 객체 생성 방식에 따라 프로토타입이 결정되고 [[Prototype]]에 저장된다.

1. __proto__ 접근자 프로퍼티

모든 객체는 __proto__ 접근자 프로퍼티를 통해 [[Prototype]] 내부 슬롯에 간접적으로 접근할 수 있다.
Prototype관련 블로깅

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

생성자 함수에 의해 생성된 인스턴스느 프로토타입의 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); // true

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

// me 객체를 생성한 생성자 함수는 Person이다.
const me = new Person('Lee');
console.log(me.constructor === Person); // true

프로토타입의 생성 시점

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

생성자 함수로서 호출할 수 있는 함수 constructor는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 생성된다.

// 함수 정의(constructor)가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다.
console.log(Person.prototype); // {constructor: ƒ}

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

프로토타입 체인

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

// 프로토타입 메서드
Person.prototype.sayHello = function () {
  console.log(`Hi! My name is ${this.name}`);
};

const me = new Person('Lee');

// hasOwnProperty는 Object.prototype의 메서드다.
console.log(me.hasOwnProperty('name')); // true

Person 생성자 함수에 의해 성성된 me 객체는 Object.prototype의 메서드인 hasOwnProperty를 호출할 수 있다.
-> me객체가 Person.prototype뿐 아니라 Object.prototype도 상속받은걸 의미한다.
me객체의 프로토타입은 Person.prototype이다.

Object.getPrototypeOf(me) === Person.prototype; // -> true
Object.getPrototypeOf(Person.prototype) === Object.prototype; // -> true

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

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

  // 프로토타입 메서드
  Person.prototype.sayHello = function () {
    console.log(`Hi! My name is ${this.name}`);
  };

  // 생성자 함수를 반환
  return Person;
}());

const me = new Person('Lee');

// 인스턴스 메서드
me.sayHello = function () {
  console.log(`Hey! My name is ${this.name}`);
};

// 인스턴스 메서드가 호출된다. 프로토타입 메서드는 인스턴스 메서드에 의해 가려진다.
me.sayHello(); // Hey! My name is Lee
  • 프로토타입이 소유한 프로퍼티를 프로토타입 프로퍼티, 인스턴스가 소유한 프로퍼티를 인스턴스 프로퍼티라고 부른다.

    • 프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 프로토타입 체인을 따라 덮어쓰는것이 아니라 인스턴스 프로퍼티로 추가한다.
  • 인스턴스 메서드 sayHello는 프로토타입 메서드 sayHello를 오버라이딩했고 프로토타입 메서드 sayHello는 가려진다.

    상속 관계에 의해 프로퍼티가 가려지는 현상을 프로퍼티 섀도잉이라 한다.

    오버라이딩

    상위 클래스가 가진 메서드를 하위 클래스가 재정의하여 사용

    오버로딩

    함수 이름은 동일하지만 매개변수의 타입, 개수가 다른 메서드를 구현하고 매개변수에 의해 메서드를 구별하여 호출하는 방식


instanceof 연산자

이항 연산자로서 좌변에 객체를 가리키는 식별자 우변에 생성자 함수를 가리키는 식별자를 피연산자로

객체 instanceof 생성자 함수

우변의 생성자 함수의 prototype에 바인딩된 객체가 좌변의 객체의 프로토타입 쳉니 상에 존재하면 true

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

const me = new Person('Lee');

// Person.prototype이 me 객체의 프로토타입 체인 상에 존재하므로 true로 평가된다.
console.log(me instanceof Person); // true

// Object.prototype이 me 객체의 프로토타입 체인 상에 존재하므로 true로 평가된다.
console.log(me instanceof Object); // true

직접 상속

Object.create에 의한 직접 상속

새로운 객체를 생성, 첫 번째 매개변수에 생성할 객체의 프로토타입으로 지정할 객체를 전달, 두번째 매개변수는 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이뤄진 객체 전달

// 프로토타입이 null인 객체를 생성한다. 생성된 객체는 프로토타입 체인의 종점에 위치한다.
// obj → null
let obj = Object.create(null);
console.log(Object.getPrototypeOf(obj) === null); // true
// Object.prototype을 상속받지 못한다.
console.log(obj.toString()); // TypeError: obj.toString is not a function

// obj → Object.prototype → null
// obj = {};와 동일하다.
obj = Object.create(Object.prototype);
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true

// obj → Object.prototype → null
// obj = { x: 1 };와 동일하다.
obj = Object.create(Object.prototype, {
  x: { value: 1, writable: true, enumerable: true, configurable: true }
});
// 위 코드는 다음과 동일하다.
// obj = Object.create(Object.prototype);
// obj.x = 1;
console.log(obj.x); // 1
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true

const myProto = { x: 10 };
// 임의의 객체를 직접 상속받는다.
// obj → myProto → Object.prototype → null
obj = Object.create(myProto);
console.log(obj.x); // 10
console.log(Object.getPrototypeOf(obj) === myProto); // true

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

// obj → Person.prototype → Object.prototype → null
// obj = new Person('Lee')와 동일하다.
obj = Object.create(Person.prototype);
obj.name = 'Lee';
console.log(obj.name); // Lee
console.log(Object.getPrototypeOf(obj) === Person.prototype); // true
const obj = { a: 1 };

obj.hasOwnProperty('a');       // -> true
obj.propertyIsEnumerable('a'); // -> true
profile
열심히 기록할 예정🙃

0개의 댓글