2021/1/14 Prototype chain ⛓

메소드를 검색할 때 마다 보이던 prototype의 정체는 대체 무엇이었을까👻

프리코스를 수강하며 숱한 메소드를 MDN에 검색해보았다.
메소드를 검색하면 위 사진과 같이 늘 'Array.prototype.~'이런 식으로 작성된 제목을 볼 수 있었다.
맨 앞의 Array는 말 그대로 메소드의 대상이 될 배열이고, 프로토타입 뒷 부분은 내가 쓸 메소드였다. 중간의 prototype은 미들네임 같이 중요하지 않아서 삭제하는 건가보다 생각하고 넘겼었는데... prototype은 대체 뭐였던 걸까.

먼저 prototype과 __proto__에 대해 알아보자! 👀

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

let khan = new Dog ('khan')

Dog.prototype.bark = function() {
  console.log('bowwow')
}

기본적으로 자바스크립트에서는 함수 역시 속성을 갖는 객체이다. 때문에 우리는 Dog라는 함수의 prototype이라는 속성안에 bark라는 메소드를 만들어줄 수 있다. 그리고 프로토타입에 포함된 메소드를 우리는 khan을 통해서도 호출할 수 있게 된다.

Dog의 프로토타입은 khan의 __proto__를 통해 찾아볼 수 있다. (생성자의 프로토타입은 인스턴스의 __proto__와 같은 것!)

배열 메소드에 대해서도 같은 방식으로 접근해보자!🚶🏻‍♂️

위와 같이 임의의 arr를 선언 할당해주고 나서 arr.__proto__를 출력해보면 자바스크립트의 모든 배열의 생성자는 Array()라는 것을 알 수 있다. 그래서 아래와 같은 식으로 빈 배열을 생성해줄 수도 있다.

let arr = new Array()

모쪼록, 이제 우리는 arr.__proto__를 통해 Array.prototype을 살펴볼 수 있다. Array의 prototype에는 위 이미지와 같이 우리가 사용해왔던 수많은 메소드들이미 내장되어있다! 그래서 우리는 직접 만들지 않은 메소드들을 사용할 수 있었던 것이었다.

그리고 그동안 MDN에서 보았던 Array.prototype.slice()에서 보았던 prototype은 바로 우리가 사용해왔던 수많은 배열들의 constructor 함수의 prototype 속성 안의 메소드를 실행하는 것이었던 것!

정리하자면,

  • prototype은 원형객체(original form)이며,
  • constructor는 생성자 (instance가 초기화될 때 실행하는 함수),
  • 그리고 proto는 상위를 표시하는 속성!

Object.create

주류 객체지향언어에서는 서브클래스가 슈퍼클래스를 상속받지만,
자바스크립트에서는 서브객체가 슈퍼객체를 상속받을 수 있다.
즉, 전혀 다른 두 객체를 부모자식관계로 엮어버릴 수 있다는 것이다.

// [출처] 생활코딩
let superObj = {superVal: 'super'}
let subObj = {subVal: 'sub'}
subObj.__proto__ = superObj;// 서브오브젝트를 슈퍼오브젝트의 자식으로 만든다

console.log(subObj.subVal) // 'sub'
console.log(subObj.superVal) // 'super'

subObj.superVal = 'sub';

console.log(superObj.superVal)//'super'

__proto__ 를 직접적으로 사용하지 않는 대체 기능이 있다.
바로 Object.create()

// [출처] 생활코딩
let superObj = {superVal: 'super'}
// superObj를 부모로 하는 객체를 만들어!
let subObj = Object.create(superObj);
// 새로운 객체를 만드는데, superObj를 부모로 하는 새 객체.

subObj.subVal = 'sub';
// 위와 같은 코드가 완성된다.

(Object.create를 지원하지 않는 브라우저에서 이런 기능이 동작하도록 하는 폴리필이라는 기능이 있다고함...)

instanceof 연산자

instanceof 연산자는 생성자의 prototype 속성이 객체의 프로토타입 체인 어딘가 존재하는지 판별합니다. [출처] MDN

이해한대로 적어보면, __proto__로 거슬러 올라가서 나오냐 안나오냐... 여부를 출력해주는 연산자

//instanceof 예시 [출처] MDN
function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
const auto = new Car('Honda', 'Accord', 1998);

console.log(auto instanceof Car);
// expected output: true

console.log(auto instanceof Object);
// expected output: true

ES6 class 키워드와 super 키워드

Class는 ECMA Script6부터 제공하기 시작한 문법이다. 때문에 Class를 이해하기 위해서는 ES6 이전에는 자바스크립트에서 어떻게 객체를 만들어냈는지 살펴볼 필요가 있다.

ES6 이전에 자바스크립트는 생성자, construction function을 통해서 객체를 생성했다. 결국 ES6의 Class 문법은 생성자함수의 대체재라고 이해할 수 있다. Class는 완전히 새로운 것이 아니고 원래 있던 기능을 더 깔끔?하게 써주는 문법인 것. 때문에 ES6이전의 생성자 함수와 비교해서 Class를 이해할 수 있다.

ES6 이전 문법으로 객체만들기

function Animal(name, leg, habitat) { // dog와 bird의 생성자 함수
  this.name = name
  this.leg = leg
  this.habitat = habitat
}
// 이 때 생성자함수가 하는 역할은 1) 객체 만들기, 2) 객체 초기상태 세팅

Animal.prototype.sound = function() {
  console.log('d@sga^)&*ddnbxz^()rg!!!!')
}  // 3) 프로토타입에 메소드 생성

let dog = new Animal('Max', 4, 'land');
dog.sound = function(){
  console.log('bow wow!!!!')
}

let bird = new Animal('Koel', 2, 'sky');
dog.sound() // bow wow!!!!
bird.sound() // d@sga^)&*ddnbxz^()rg!!!!

ES6 이후 Class문법으로 객체만들기


class Animal { // 위에서 보았던 1)에 해당하는 단계, 객체를 만든다.
  constructor(name, leg, habitat){
    this.name = name;
    this.leg = leg;
    this.habitat = habitat;
  } // 2)에 해당하는 단계, 객체의 초기설정을 만든다!
    // class는 실행하면서 자동으로 내부의 constructor를 실행한다.
  sound() {
    console.log ('d@sga^)&*ddnbxz^()rg!!!!')
  } // 3)에 해당하는 단계. 프로토타입에 메소드 지정. class안에 함께 작성해줄 수 있다는 것이 다른 점. 물론 밖에 적어도 작동은 함.
}

let dog = new Animal('Max', 4, 'land');
dog.sound = function(){
  console.log('bow wow!!!!')
}
// 특정 인스턴스만 다른 메소드를 쓰게 해주고 싶다면, 그냥 밖에서 추가해주면 된다. 자바스클립트는 먼저 인스턴스 안에 메소드가 있는지 확인하고, 없으면 인스턴스의 클래스 안에 메소드가 있나 확인한다.

let bird = new Animal('Koel', 2, 'sky');
dog.sound() // bow wow!!!!
bird.sound() // d@sga^)&*ddnbxz^()rg!!!!
// 똑같이 작동한다!

class에 메소드를 추가해주고 싶을 때는 어떻게 할까? 상속👍

클래스가 라이브러리이거나 다른 사람이 짠 코드거나, 추가하고 싶은 메소드를 아주 드물게 사용할 계획인 경우... 동시에 중복을 피하고 싶을 때! 상속을 사용할 수 있다

class Animal {
  constructor(name, leg, habitat){
    this.name = name;
    this.leg = leg;
    this.habitat = habitat;
  } 
  sound() {
    console.log ('d@sga^)&*ddnbxz^()rg!!!!')
  }
}

//extends를 써서 확장판을 만들어줄 수 있다.
class SleepingAnimal extends Animal{
  sleep(){
    console.log('zzz')
  }
}

let dog = new SleepingAnimal('Max', 4, 'land')
dog.sleep() // zzz;

상속의 장점?은 우선 중복을 피할 수 있으며, 원본의 메소드를 수정하면 클래스를 상속하는 모든 객체에서 동시다발적으로 수정을 반영한다는 점이다.

만약 확장판에서 변수를 하나 더 받아오고 싶다면? super🧸

class Animal {
  constructor(name, leg, habitat){
    this.name = name;
    this.leg = leg;
    this.habitat = habitat;
  } 
  sound() {
    return 'd@sga^)&*ddnbxz^()rg!!!!'
  }
}

class SleepingAnimal extends Animal{
  constructor(name, leg, habitat, enemy){
    super(name, leg, habitat);
    this.enemy = enemy;
  }
  sound() {
    console.log (`when they meet ${this.enemy}: ` + super.sound()) // [주의] 변수쓸 때 여기 this무조건 써줘야 함...
  }
}

let dog = new SleepingAnimal('Max', 4, 'land', 'cat')
dog.sound() // when they meet cat: d@sga^)&*ddnbxz^()rg!!!!

결국 super도 중복을 피하기 위해 고안됐다고 볼 수 있겠다.
부모가 이미 갖고 있는 기능을 대체해주는 아이가 바로 super이다.
super() : 부모클래스의 생성자 호출됨
super. : 부모클래스

부모가 이미 갖고 있는 기능과 나도 갖고 있는 기능의 공통적인 부분을 제거 하고 super를 대신 쓴다.
1) super쓰고 뒤에 소괄호 붙으면 부모클래스의 생성자 호출됨
2) super쓰고 뒤에 .쓰면 부모클래스 자체

🎏 유용한 사이트

  • https://babeljs.io/ (모든 버전의 js에서 돌아가는 코드로 변환가능. 컴파일러.)

메모✍

상속하고 받는 관계
부모객체의 프로토타입이 정의해놓은 메소드를 쓸 수 있는 것은 상속기능을 이용했기 때문이다!!!!
프로토타입은 할당이 가능하다. (assignable)

좋은 웹페이지 즐겨찾기