무브라더

[JavaScript] prototype 정리 본문

Programming/JavaScript

[JavaScript] prototype 정리

동스다
반응형
SMALL

※ 모던자바스크립트 책을 보고 이해가 어려웠던 부분만 정리한 내용입니다. 틀린 내용이 있다면 댓글 달아주시면 수정하겠습니다!

 

자바스크립트는 프로토타입 기반의 객체지향 프로그래밍 언어다.

 

ES6에서 js에도 클래스를 제공하여 클래스와 와 생성자 함수 모두 프로토타입 기반의 인스턴스를 생성하지만

정확히 동일하게 동작하지는 않는다.

 

자바스크립트를 이루고 있는 거의 모든것은 객체이며 원시 타입의 값을 제외한 나머지 값들은 모두 객체이다.

 

자바스크립트에서 객체는 key와 value를 가지는 존재이며 생성자 함수 또한 객체이므로 속성과 값을 가진다.

 

상속과 프로토타입

 

자바스크립트에서의 상속은 프로토타입 기반으로 구현을 한다.

 

예제코드

// 생성자 함수
function Circle(radius) {
  this.radius = radius;
}

// Circle 생성자 함수가 생성한 모든 인스턴스가 이 메서드를 사용할 수 있도록
// 프로토타입에서 관리한다.
// 프로토타입은 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

console.log(circle1.getArea());
console.log(circle2.getArea());

getArea() 를 상위 객체인 프로토타입으로부터 상속받아 사용한다.

- circle1, circle2의 프로토타입은 Circle.prototype

 

프로토타입 객체

 

- 객체의 리터럴로 만들어진 객체의 프로토타입 -> Object.prototype

 

- 생성자 함수에 의해 생성된 객체의 프로토 타입 -> 생성자 함수의 프로토타입 프로퍼티에 바인딩된 객체

 

- 개발자는 [[Prototype]] 에 직접 접근 할 수는 없지만, __proto__ 라는 접근자 프로퍼티를 통해 간접적 접근이 가능함

 

- __proto__ 접근자 프로퍼티는 상속을 통해 사용됨 ~> 객체가 소유한 프로퍼티가 아닌 Object.prototype 의 프로퍼티

 

 

* __proto__를 통해 프로토 타입에 접근하는 이유

 

[[Prototype]] 에 직접 접근하여 조작하면 두 객체간 순환참조 되게 만들어 무한루프에 빠질 수 있다. 하지만 __proto__를 사용하면 순환참조가 되지 않게 타입에러를 발생시키기 때문에 __proto__를 사용하여 조작한다.

 

* 하지만 __proto__ 를 통해 접근하는 것도 권장하지는 않는다!!

 

직접 상속을 통해 Object.prototype 을 상속받지 않는 객체에서는 사용 할 수 없기 때문이다. 그럼 어떻게 접근하면 되는가? Object.getPrototypeOf 메소드와 Object.setPrototypeOf 메소드 사용을 권장한다.

 

 

프로토타입의 생성시점

 

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

 

console.log(Person.prototype); // {constructor: f}

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

이 예제를 보면 함수선언문이 함수 호이스팅이 일어나 먼저 실행되어 함수 객체로 만들어지고 위의 콘솔로그에 값이 찍히게 된다. 이때 생성된 프로토타입은 생성자 프로퍼티만을 갖는 객체이며 자신의 프로토타입인 Object.prototype을 갖는다.

 

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

프로토타입은 추상 연산 OrdinaryObjectCreate에 전달되는 인수에 의해 결정된다.

 

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

객체 리터럴의 경우 OrdinaryObjectCreate가 호출되면 여기에 Object.prototype이 인수로 전달 된다. --> 따라서 객체 리터럴의 프로토타입은 Object.prototype이다.

 

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

Object 생성자 또한 인수없이 함수를 호출하게 되면 빈 객체가 생성되고 OrdinaryObjectCreate가 호출되어 Object.prototype이 인수로 전달되기 때문에 생성된 객체의 프로토타입은 Object.prototype이다.

 

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

생성자 함수로 인스턴스 생성시 OrdinaryObjectCreate가 호출되면서 이 경우에 이수는 생성자 함수 prototype 프로퍼티에 바인딩 되어있는 객체이다. (함수.prototype)

하지만 사용자 정의 생성자 함수는 Object.prototype이 가지고 있는 빌트인 메서드가 없고 constructor프로퍼티만 가지고 있게 된다.

따라서 특정 기능 상속을 위해서는 직접 구현해주어야 한다.

 

 

프로토타입의 교체

프로토타입을 동적으로 변경할 수 있다. --> 객체 간의 상속 관계를 동적으로 변경 가능

 

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

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

Person.prototype = {
  sayHello() {
    console.log(`Hi! My name is ${this.name}`);
  }
};

const me = new Person('Lee'); //객체리터럴 할당

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

Person 생성자 함수의 프로토타입을 객체 리터럴을 할당하게 되면 원래 constructor를 가지고 있어야할 Person.prototype의 프로퍼티가 바뀌게 된다.
따라서 me 객체의 constructor는 Person.prototype의 프로토타입인 Object.prototype이 가지고 있는 constructor인 Object 이다.

 

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

Object.setPrototypeOf(me, parent); // parent는 프로토타입으로 교체할 객체

 

setPrototypeOf 메서드를 사용하여 프로토타입을 교체하게 되면 위의 생성자 함수로 프로토타입이 교체된것 처럼 constructor가 Object를 가리킨다.
다른점은 Person 생성자 함수의 prototype 프로퍼티가 교체된 프로토타입을 가리키느냐 안가르키느냐에 차이가 있다.

 

어찌됐든 프로토타입 교체를 통해 상속 관계를 동적으로 변경하는 것은 번거로우니 웬만하면 직접 교체하지말자~!

 

직접 상속

Object.create에 의한 직접 상속

 

Object.create 메서드도 추상 연산 OrdinaryObjectCreate를 호출하는데 첫 번째 매개변수로 생성할 객체의 프로토타입으로 지정할 객체와 두 번째 매개변수는 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이뤄진 객체를 전달한다.(두 번째는 생략가능)

Object.create 메서드의 장점

  • new 연산자 없이 객체 생성 가능
  • 프로토타입을 지정하면서 객체 생성 가능
  • 객체 리터럴에 의해 생성된 객체도 상속 받을 수 있음.
// 프로토타입이 null인 객체, 즉 프로토타입 체인의 종점에 위치하는 객체 생성
const obj = Object.create(null);
obj.a = 1;

console.log(obj.hasOwnProperty('a')); // TypeError

console.log(Object.prototype.hasOwnProperty.call(obj,'a')); // true

다음과 같이 프로토타입 체인의 종점에 위치하는 객체가 Object.prototype의 빌트인 메서드를 직접 호출할 수 있기 때문에 객체가 직접 호출하는 것을 권장하지 않는다.
따라서 아래와 같이 Object.prototype.hasOwnProperty.call(obj, 'a')와 같은 메서드를 사용하여 호출한다.

 

 

* for in 문의 값과 Object.keys의 값은 다르게 나온다.

 

for in 문은 상속받은 프로토타입의 프로퍼티의 열거 값이 true인 것만 열거해준다. Object.keys 는 객체 고유의 프로퍼티 키만 보여준다.

 

for in 문의 순서는 보장되지 않는다.

 

* 객체 내 메소드와 함수의 차이?

 

  • 함수가 메소드를 아우르는 포괄적인 용어이다.
  • 함수는 객체로부터 독립적이며, 메소드는 객체에 종속적이다.
  • 메소드는 거의 모든 면에서 함수와 동일하지만, 아래의 2가지 포인트에서 다른 점이 있다.
    1.  메소드는 호출된 객체에 암시적으로 전달된다.
    2.  메소드는 클래스 안에 있는 data를 조작할 수 있다.
  • 기본적으로 두 용어의 뜻은 동일하나 '객체(클래스)로부터 독립적인가 아닌가'가 함수와 메소드를 나누는 기준이다.
  • 객체 내 메소드가 객체 리터럴이 되면 더이상 메소드가 아니다. 즉 생성자 함수의 프로토타입에 속하지 않는다.

 

반응형
LIST
Comments