(JS) Prototype

Mirrer·2022년 5월 3일
0

JavaScript

목록 보기
20/24
post-thumbnail

프로토타입 (Prototype)

객체를 상속하기 위하여 Javascript에서 사용하는 방식

프로토타입(Prototype)은 사전적의미로 원형을 뜻하며 실제 제품을 만들기 전 대략적인 형태, 또는 집합체안 비슷한 특징끼리 모아 형태를 만든 것이다.

자바스크립트에서는 Prototype을 활용하여 다양한 객체들간 비슷한 특징들을 하나의 프로토타입으로 만들어 객체지향을 구현한다.

그래서 자바스크립트는 Prototype 기반의 객체지향 프로그래밍 언어라고 불린다.


Object Prototype

자바스크립트에서 객체는 Object라는 공통의 Prototype을 포함하고 있다.

Object를 통해 객체가 가지고 있는 공통적인 속성들을 Prototype으로부터 상속받는다.

즉 모든 객체들은 단 하나의 Object Prototype을 상속한다.

또한 배열도 Object Prototype을 상속받은 Array Prototype을 상속받는다. 이를 통해 객체간 프로토타입 체인을 연결한다.

Prototype 접근 방법

외부에서는 Prototype에 직접 접근이 불가능하다.

그래서 아래와 같은 방법으로 Prototype에 접근한다.

  • __proto__

  • Object.getPrototypeOf()

  • Object.setPrototypeOf()

  • 생성자 함수에서의 prototype접근

const obj1 = {};
const obj2 = {};

console.log(obj1.__proto__ === obj2.__proto__); // true

Property Descriptor

주어진 객체 자신의 속성에 대한 속성 설명자

디스크립터(Descriptor)는 스코프의 렉시컬환경처럼 오브젝트의 상태를 가지고 있다.

각각의 오브젝트는 프로퍼티는 프로퍼티 디스크립터라고 하는 객체로 저장된다.


디스크립터 객체의 속성값

  • value : 에 해당하는 값

  • writable : 값의 수정여부

  • enumerable : 값의 열거여부 (iterator 여부)

  • configurable : 값에 해당하는 속성들을 수정, 삭제 여부

모든 속성의 기본값은 true이다.

const dog = { name: '와우', age: 2};

// 1. 모든 속성값 출력
const descriptors = Object.getOwnPropertyDescriptors(dog);
console.log(descriptors);
// { name: { value: '와우', writable: true, enumerable: true, configurable: true },
//   age: { value: 2, writable: true, enumerable: true, configurable: true } }

// 2. 특정 속성값 출력
const desc = Object.getOwnPropertyDescriptor(dog, 'name');
console.log(desc);
// { value: '와우', writable: true, enumerable: true, configurable: true }

// 3. 특정 속성값 정의 및 수정
Object.defineProperty(dog, 'name', {
  value: '멍멍',
  writable: false,
  enumerable: false,
  configurable: false,
})
console.log(dog.name); // 멍멍
console.log(Object.keys(dog)); // [ 'age' ]
delete dog.name;
console.log(dog.name); // 멍멍

// 4. 모든 속성값 정의 및 수정
const student = {};
Object.defineProperties(student, {
  firstName: {
    value: '영희',
    writable: true, 
    enumerable: true, 
    configurable: true,
  },
  lastName: {
    value: '김',
    writable: true, 
    enumerable: true, 
    configurable: true,
  },
  fullName: {
    get() {
      return `${lastName} ${firstName}`;
    },
    set(name) {
      [this.lastName, this.firstName] = name.split(' ');
    },
    configurable: true,
  },
})
console.log(student); // { firstName: '영희', lastName: '김' }

Prototype의 static함수

  • Object.keys, values, entries : 객체의 키, 값, 혹은 둘다 가져오는 것
const dog = { name: '와우', age: 2};

console.log(Object.keys(dog)); // [ 'name', 'age' ]
console.log(Object.values(dog)); // [ '와우', 2 ]
console.log(Object.entries(dog)); // [ [ 'name', '와우' ], [ 'age', 2 ] ]
  • in연산자, Object.hasOwnProperty() : 특정 오브젝트의 해당 키 존재 유무를 확인
const dog = { name: '와우', age: 2};

console.log('name' in dog); // true
console.log(dog.hasOwnProperty('name')); // true

객체 불변성

더 이상 변경될 수 없게 객체를 동결


객체 불변성을 위한 Object static함수

  • Object.freeze(): 객체를 동결하여 추가, 삭제, 쓰기, 속성 재정의를 금지 (단, 얕은 복사만 해당)
const mirrer = { name: 'mirrer' };
const dog = { name: '와우', age: 2, owner: mirrer };
Object.freeze(dog);

dog.name = '멍멍';
console.log(dog.name); // 와우

dog.color = 'brown';
console.log(dog.color); // undefined

delete dog.name;
console.log(dog.name); // 와우

mirrer.name = 'mirrer2';
console.log(dog.owner.name); // mirrer2
  • Object.seal() : 객체를 밀봉하여 추가, 삭제, 속성 재정의를 금지 (단 값의 수정은 가능)
// const cat = Object.assign({}, dog);
const cat = { ...dog };
Object.seal(cat);

cat.name = '냐옹';
console.log(cat.name); // 냐옹

delete cat.age;
console.log(cat.age); // 2
  • Object.isFrozen, isSealed, isExtensible : 객체의 동결, 밀봉 여부 확인
const mirrer = { name: 'mirrer' };
const dog = { name: '와우', age: 2, owner: mirrer };
Object.freeze(dog);

const cat = { ...dog };
Object.seal(cat);

console.log(Object.isFrozen(dog)); // true
console.log(Object.isSealed(cat)); // true
  • Object.preventExtensions : 객체의 확장, 추가 금지
const tiger = { name: '어흥' };
Object.preventExtensions(tiger);
console.log(Object.isExtensible(tiger)); // false

tiger.name = '어흐응';
console.log(tiger.name); // 어흐응

delete tiger.name;
console.log(tiger.name); // undefined

tiger.age = 2;
console.log(tiger.age); // undefined

프로토타입을 이용한 상속

Object.create, call함수를 이용하여 프로토타입 상속을 구현


프로토타입을 베이스로한 객체지향 프로그래밍에서 상속은 다음과 같이 구현할 수 있다.

  • 상속받을 객체의 PrototypeObject.create함수로 부모와 연결

  • 상속받을 객체의 생성자함수를 Object.call함수로 부모의 생성자 함수와 연결

// 부모 Animal객체
function Animal(name, color) {
  this.name = name;
  this.color = color;
}
Animal.prototype.print = function () {
  console.log(`${this.name} ${this.color}`);
}

// 자식 Dog 객체
function Dog(name, color, owner) {
  // super(name, color);
  Animal.call(this, name, color);
  this.owner = owner;
}
// Dog.prototype = Object.create(Object.prototype);
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.play = () => {
  console.log('같이 놀자옹!!');
}

// 자식 Tiger 객체
function Tiger(name, color) {
  Animal.call(this, name, color);
}
Tiger.prototype = Object.create(Animal.prototype);
Tiger.prototype.hunt = () => {
  console.log('사냥하자!!');
}

const dog1 = new Dog('멍멍', 'brown', 'mirrer');
dog1.play(); // 같이 놀자옹!!
dog1.print(); // 멍멍 brown

const tiger1 = new Tiger('어흥', 'yellow');
tiger1.print(); // 어흥 yellow
tiger1.hunt(); // 사냥하자!!

instanceof 연산자

instanceof 연산자를 사용하여 상속관계를 확인할 수 있다.

instanceof 연산자는 상속관계 여부를 Boolean으로 반환한다.

// 부모 Animal객체
function Animal(name, color) {
  this.name = name;
  this.color = color;
}
Animal.prototype.print = function () {
  console.log(`${this.name} ${this.color}`);
}

// 자식 Dog 객체
function Dog(name, color, owner) { 
  Animal.call(this, name, color);
  this.owner = owner;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.play = () => {
  console.log('같이 놀자옹!!');
}

// 자식 Tiger 객체
function Tiger(name, color) {
  Animal.call(this, name, color);
}
Tiger.prototype = Object.create(Animal.prototype);
Tiger.prototype.hunt = () => {
  console.log('사냥하자!!');
}

const dog1 = new Dog('멍멍', 'brown', 'mirrer');
dog1.play();
dog1.print();

const tiger1 = new Tiger('어흥', 'yellow');
tiger1.print();
tiger1.hunt();

console.log(dog1 instanceof Animal); // true
console.log(dog1 instanceof Dog); // true
console.log(dog1 instanceof Tiger); // false

Object.assign함수

오브젝트는 단 하나의 prototype을 가리킬 수 있다.

하지만 여러개의 함수들을 상속받아 사용하고 싶다면 Object.assign를 사용하여 객체의 프로토타입에 사용할 함수들을 복사할 수 있다.

const play = {
  play: function () {
    console.log(`${this.name} 놀아요!`);
  }
}

const sleep = {
  sleep: function () {
    console.log(`${this.name} 자요ZzZ`);
  }
}

function Dog(name) {
  this.name = name;
}
Object.assign(Dog.prototype, play, sleep);

const dog = new Dog('멍멍');
console.log(dog); // Dog { name: '멍멍' }
dog.play(); // 멍멍 놀아요!
dog.sleep(); // 멍멍 자요ZzZ

Object.assign은 프로토타입뿐만 아니라 클래스에서도 사용가능

const play = {
  play: function () {
    console.log(`${this.name} 놀아요!`);
  }
}

const sleep = {
  sleep: function () {
    console.log(`${this.name} 자요ZzZ`);
  }
}

class Animal {}
class Tiger extends Animal {
  constructor(name) {
    super();
    this.name = name;
  }
}
Object.assign(Tiger.prototype, play, sleep);

const tiger = new Tiger('어흥!');
console.log(tiger); // Tiger { name: '어흥!' }
tiger.play(); // 어흥! 놀아요!
tiger.sleep(); // 어흥! 자요ZzZ

참고 자료

Object prototypes - Web 개발 학습하기 | MDN
모던 자바스크립트 Deep Dive
모던 JavaScript 튜토리얼

profile
memories Of A front-end web developer

0개의 댓글