모던 자바스크립트 Deep Dive - 19장

박상은·2021년 10월 2일
0

요약

1. 프로토타입과 상속

아직 프로토타입은 어떤것이다라고 한줄로 정의할 수 없어서 예시를 들어보겠다.
일단 프로토타입을 유용하게 사용한 경우를 보여주겠다

function Circle(r){
  this.r = r;
  
  getArea(){
    return Math.PI * this.r ** 2;
  }
}

const c1 = new Circle(2);
const c2 = new Circle(4);
const c3 = new Circle(6);

위 코드에서 보면 r이라는 변수는 각 객체마다 다른 값을 가지지만, getArea()는 this값을 제외하고는 같은 행위를 하고 있다.
위처럼 같은 행위를 하는 메서드는 각각의 객체마다 가지는 것보단, 공통으로 가지고 공유해서 사용하는 것이 메모리적으로 이득이다.

이럴때 사용할 수 있는것이 프로토타입이다.

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

Circle.prototype.getArea = function(){
  return Math.PI * this.r ** 2;
}

const c1 = new Circle(2);
const c2 = new Circle(4);
const c3 = new Circle(6);

위처럼 선언하면 __proto__를 통해서 Circle.prototype의 프로퍼티를 공유해서 사용할 수 있다.

1.1 프로토타입관련 용어 정리

  1. __proto__: [[Prototype]]에 간접접근하는 프로퍼티이다.
  2. [[Prototype]]: 객체에게 상속할 메서드나 변수들을 저장해놓는 내부 슬롯이다. ( 직접접근불가능 )
  3. prototype: 생성자함수가 생성할 인스턴스의 프로토타입을 가리킨다.

1.2 prototype과 __proto__

[[Constructor]]가 없는 함수를 제외한 나머지 함수들은 모두 prototype프로퍼티를 가진다.

  • prototype프로퍼티는 생성자함수가 생성할 객체의 프로토타입을 가리킨다.
  • __proto__프로퍼티는 생성된 객체의 프로토타입을 가리킨다. ( [[Get]], [[Set]]을 가짐 )

즉, 정리를 해보면 prototype__proto__는 같은 값을 가리킨다.
하지만 사용하는 주체가 서로 다름을 이해해야한다.

prototype은 인스턴스의 프로토타입을 할당하기 위해서 사용하고,
__proto__는 인스턴스에서 자신의 프로토타입에 접근 or 교체하기위해 사용한다.

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

const p = new Person("john");

Person.prototype === p.__proto__;	// true
// 위에서 말한대로 서로 같은 값이지만 사용하는 주체와 용도가 다르다.

// prototype은 자신이 생성한 객체에서 공유할 메서드나 값들을 저장하는데 사용한다.
Person.prototype.say = function () { console.log("Hello", this.name) };

// __proto__는 아래처럼 수정하거나 조회할 때 사용하는데 거의 직접적으로 사용할 일은 없다.
p.__proto__ = Person.prototype;

단, __proto__는 직접적으로 사용하는 것을 권장하지 않는다
getPrototypeOf(), setPrototypeOf()를 이용한 호출 및 변경을 권장한다.

1.3 constructor

모든 prototypeconstructor프로퍼티를 가지며, constructor프로퍼티는 생성자함수를 가리킨다.

1.4 prototype과 constructor 생성시점

함수객체가 생성되는 시점에 두 개의 값이 바인딩된다.

1.5 객체생성방법들

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

const person = { name: "john" };	// 1
const person = new Object();		// 2
const person = new Person("john");	// 3

// 모두 추상연산 OrdinaryObjectCreate가 호출된다.
// 인수로 연결할 prototype이 들어가고, prototype과 constructor를 바인딩해준다.

1.6 프로토타입 체인과 스코프 체인

현재 [[Prototype]]인 프로토타입에서 찾아보고 없다면, 연결된 체인의 상위 프로토타입인 [[Prototype]]에서 찾는 것을 반복한다. ( [[Prototype]]null일 때 까지반복 )

  • 프로토타입체인의 종점인 Object[[Prototype]]null이다.
function Person(name){
  this.name = name;
}

function foo(){
  const person = new Person("john");	// 3
  person.hasOwnProperty("name");		// true
}

person의 프로토타입 체인은 Person -> Object순으로 만들어져있다.

  • 일어나는 일
    1. 스코프체인에서 person변수를 찾는다. ( 전역스코프 -> foo스코프 순서로 찾음 )
    2. hasOwnProperty()를 프로토타입체인에서 찾는다 ( Person -> Object 순서로 찾음 )

즉, 식별자는 스코프체인에서 찾고, 프로퍼티는 프로토타입체인에서 찾는다.

1.7 메서드 오버라이딩과 메서드 섀도잉

  • 오버라이딩 : 객체에서 메서드 재정의 하는 것
  • 섀도잉 : 객체에서 메서드를 재정의해서 원래 메서드가 가려지는 현상`

1.8 프로토타입 교체

프로토타입을 다른 객체로 변경하는 것을 말합니다.

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

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

  // 핵심은 여기.. prototype을 직접 다른 객체로 변경함
  Person.prototype = {
    // constructor: Person,
    say() {
      console.log("Hello, ", this.name);
    }
  }

  return Person;
})();

const person = new Person("john");

// constructor는 원래 자동으로 엔진이 넣어주지만 직접 prototype을 변경했을 경우에는 들어가있지 않다.
// 따라서 프로토타입체인에 검색에 의해서 상위 체인에 존재하는 Obejct의 constructor가 호출된다.
person.constructor;		// Object

1.8.2 객체의 프로토타입 교체

function Person(name){
  this.name = name;
}
const person = new Person("john");
const parent = {
  // constructor: Person,
  say() {
    console.log("Hello,", this.name);
  }
}
// 객체의 프로토타입 교체
Object.setPrototypeOf(person, parent);
// 위와 같은 역할을 하지만 비권장
// person.__proto__ = parent;

1.8.3 생성자 함수의 프로토타입교체와 객체의 프로토타입 교체의 차이

생성자 함수가 프로토타입을 교체한 경우에는 생성자함수.prototype은 교체된 프로토타입을 가리킴
하지만 객체의 프로토타입을 교체한 경우에는 생성자함수.prototype이 교체된 프로토타입을 가리키지 않음

1.9 instanceof 연산자

객체 instanceof 생성자 함수 형태로 사용함
생성자 함수에 바인딩된 prototype이 객체의 프로토타입체인에 존재하면 true

1.10 직접 상속

Object.create(프로토타입 [, 디스크립터 객체]) 형식으로 작성

const person = Object.create(Object.prototype,{ name: { value: "john", writable: true, enumerable: true, configurable: true } })
// 위와 아래는 같은 역할
const person = { name: "john" };

// Object를 상속받지 않는 즉, 프로토타입체인에 존재하지않는 객체
const obj = Object.create(null);
obj.toString();		// not define function error

// 위처럼 모든 객체가 Object를 상속받지 않으므로 객체에서 Object의 빌트인 메서드를 사용하는 것은 좋지않음
// 따라서 아래처럼 사용하는 것이 좋음
Object.prototype.toString.call(obj);

2. 정적 프로퍼티/메서드

생성자 함수 자체에서만 접근이 가능한 프로퍼티나 메서드를 의미한다.
생성자 함수도 결국은 객체이기 때문에 가능한 것임

대표적으로 Object.create(), Object.keys() 등이 정적 메서드임

function Person(name){
  this.name = name;
}
Person.staticProps = "staticProps";
Person.staticMethod = function(){
  console.log("static method");
  // this는 당연히 사용불가능
}

const person = new Person("john");
person.staticMethod();		// error
Person.staticMethod();		// static method

3. in연산자

객체가 특정 프로퍼티를 가지는지 확일할 수 있는 연산자
단, 현재 객체만이 아닌 프로토타입체인내에 특정 프로퍼티가 존재하는지 확인합니다.
해당 객체에만 존재하는지 확인하려면 Object.prototype.hasOwnProperty()를 사용하면 됩니다.

const person = { name: "john" };

"name" in person;		// true;
"toString" in person;		// true;
Object.prototype.hasOwnProperty.call(person, "toString");		// false

4. 유용한 정적 메서드

Object.keys(), Object.values(), Object.entries()

마무리

  • __proto__gettersetter로 접근이 가능하다. 즉, 변경도 가능하다.
  • __proto__prototype에 의해서 상속된 값이다.
  • __proto__는 순환참조를 체크한다.
function Person(name){
  this.name = name;
}
const person = new Person("john");

// prototype은 [[Prototype]]의 참조값이다.
// __proto__은 [[Prototype]]의 접근자 프로퍼티이다.
Person.prototype.constructor === Person;	// true
person.__proto__ === Person.prototype;		// true

0개의 댓글