자바스크립트 Prototype 완벽 정리

adam2·2019년 11월 2일
51
post-thumbnail

🤔Prototype,,, 잘 아세요?

자바스크립트를 사용하면 누구가 프로토타입을 알 것이고, 공부했을 것이다.
하지만 "프로토타입이 뭔지 설명해줘! " 라는 친구의 물음에 선뜻 설명해줄 수 있는 사람은 많지 않을 것이다..
클래스 비스무리한거 같기도 하고,, 뭐 상속? 받는다고 하던데,,, 프로토타입 체이닝도 중요하다고 하고...
그런데 이제 class문법 지원하는데 알아야 하나? 라는 생각이 들것 같기도 하다...~(는 내가 그랬음)~

You Don't Konw JS라는 책을 최근에 읽고 있는데 프로토타입에 관한 내용이 정말 그 어떤 블로그 글보다 자세하게 (하지만 길게) 설명되어 있어 책을 기반으로 나름대로 학습한 것들을 덧붙여 적어보았다.


[[Prototype]] 링크

자바스크립트 객체는 Prototype이라는 내부 프로퍼티가 존재한다. 거의 모든 객체가 생성 시점에 이 프로퍼티에 null이 아닌 값이 할당된다.

우선 너무나도 자연스러운 이 코드를 살펴보자.

    const woody = {
    	riding:true
    }
    
    woody.riding // true

woody.riding 처럼 객체 프로퍼티를 참조할 경우 [[Get]]이 호출되어 객체 내부에 해당 프로퍼티가 존재하는지 탐색한다. 그렇다면 객체 내부에 없는 프로퍼티를 호출하게 되면 어떻게 될까??

[[Get]]은 객체 내부에서 해당 프로퍼티를 찾지 못하면 바로 [[Prototype]]링크를 따라가 프로퍼티를 탐색한다. 모든 일반 객체의 최상위 프로토타입 연쇄는 내장 Object.prototype이고 이 지점에서도 찾지 못하면 탐색이 종료된다. (undefined 반환)

    const buzz = {
    flying:true
    }
    
    const woody = Object.create(buzz);
    // 우디가 날 수 있게 되었다!
    woody.flying; // true

woody는 buzz와 [[Prototype]]이 링크되었다. woody내부에는 flying이라는 프로퍼티가 없지만 연결된 buzz에서 해당 프로퍼티를 찾아 그 값을 반환한 것이다.

Untitled-d97e46ed-9a42-48ed-8157-084269d64d5c.png

for...in루프에서도 객체를 순회할 때 prototype 연결을 통해 탐색 가능한 프로퍼티라면 모두 열거한다.

    for(let i in woody){
    	console.log(`${i}를 발견`)
    }
    // flying를 발견

클래스 함수

    function Foo(){
    
    }

함수를 정의하면 함수만 생성되는 것이 아니라 Prototype Object도 같이 생성이 된다.

image.png

Prototype Object는 기본속성으로 constructor__proto__를 가지고 있다. (따라서 정확히 말하자면 Foo.prototype 객체가 프로토타입을 의미하는것은 아니다. constructor도 가지고 있으니! )

  • constructor는 내가 선언한 생성자 함수(Foo)를 가리킨다. new 키워드와 함께 함수를 호출할 경우 constructor함수를 실행하고 부수효과로 객체가 생성된다.
    생성자 함수가 아니라 함수를 생성하는 호출이라고 생각하는것이 맞다
  • prototype은 생성자 함수에 정의한 모든 객체가 공유할 원형이다.
  • proto는 [[Prototype]]링크이다. 생성자 함수에 정의해두었던 prototype을 참조한다.
const f = new Foo();
Object.getPrototypeOf(f) === Foo.prototype; // true

image.png

Untitled-f837623b-c1d2-4ff0-ab2b-1fba6dd88352.png

image.png

prototype은 생성자 함수에 사용자가 직접 넣는 거고, __proto__ 는 new를 호출할 때 prototype을 참조하여 자동으로 만들어진다.
생성자 함수에는 prototype에, 생성자로부터 만들어진 객체에는 __proto__에 생성자의 prototype이 들어간다.

new Foo()로써 만들어진 모든 객체(f)는 결국 Foo.prototype 객체와 내부적으로 [[Prototype]]링크로 연결된다.
결국 fFoo 는 상호 연결된 두개의 객체가 된다.

Untitled-2a35c021-b5ac-44aa-8880-43a0eb1f5480.png

new 연산자

객체지향언어에서 클래스를 인스턴스화 하기 위해 new 연산자를 사용한다. 그리고 자바스크립트에서도 두개의 객체를 연결하기 위해 new연산자를 사용한다.

하지만 전자의 경우 클래스로부터 작동을 복사하여 새로운 객체를 만드는 것이고, 후자의 경우 복사 과정 없이 그저 두 객체를 연결한 것이 전부이다.
따라서 자바스크립트의 new 키워드는 우리가 흔히 아는 new키워드와 다르게 동작한다는걸 알 수 있다.

js에서 new연산자는 결국 새 객체를 다른 객체와 연결하기 위한 간접적인 우회 방법이고, Object.create()를 이용해 직접적으로 객체를 연결해주는 방법도 있다.

[[Prototype]] 체계를 흔히들 프로퍼타입 상속이라고 부른다. 하지만 상속은 기본적으로 복사를 수반하지만, 자바스크립트는 객체 프로퍼티를 복사하지 않는다. 따라서 프로퍼타입 상속은 의미를 더 햇갈리게 만드는 단어의 조합이고, 위임이야말로 자바스크립트 객체-연결 체계를 훨씬 더 정확하게 나타내는 용어라고 할 수 있다.

생성자

앞 예제에서 new를 붙여 Foo함수를 호출하여 객체가 "생성"되는 현장을 목격했으니 Foo를 생성자라고 믿고 싶은 욕망을 버리기란 쉽지 않다.

하지만 Foo는 생성자가 아니다. 그저 함수일 뿐이다.

함수는 결코 생성자가 아니지만 new를 붙여 호출하는 순간 이 함수는 생성자 호출을 한다. new키워드는 일반 함수 호출 도중에 원래 수행할 작업 외에 객체 생성이라는 잔업을 더 부과하는 지시자인 것이다.

    function nothing(){
    	console.log(`그저 함수입니다`)
    } 
    
    const a = new nothing();   
    // "그저 함수입니다"
    a;  // {}

image.png

평범한 함수인 nothing을 new로 호출함으로써 객체가 생성 되고 부수효과로 생성된 그 객체를 a에 할당하는 것이다. 이것을 보통 생성자(constructor) 호출 이라고 부르지만 nothing함수 자체는 생성자가 아니다.

즉, 자바스크립트는 앞에 new를 붙여 호출한 함수를 모두 생성자라고 할 수 있다. 함수는 결코 생성자가 아니지만 new를 사용하여 호출할 때에만 생성자 호출이다.

그렇다면 객체를 객체와 연결해야 하는 이유는 뭘까? 그렇게 함으로써 어떤 장점이 있는걸까?

// this를 사용
function Toy(name){
	this.name = name;
  this.battery = 100;
  this.charge = function(){
  	battery += 10;
    console.log(`charging is finished. battery is ${this.battery}`)
  }
}

const woody = new Toy('woody');
const buzz = new Toy('buzz');

image.png

// prototype 사용
function Toy(name){
  this.name = name;
  this.battery = 100;
}
Toy.prototype.charge = function(){
  	this.battery += 10;
    console.log(`charging is finished. battery is ${this.battery}`)
  }

const woody = new Toy('woody');
const buzz = new Toy('buzz');

image.png

prototype은 모든 객체가 공유하고 있어서 한 번만 만들어지지만, this에 넣은 것은 객체 하나를 만들 때마다 메소드도 하나씩 만들어지기 때문에 불필요한 메모리 낭비가 발생한다.

정리


객체에 존재하지 않는 프로퍼티를 접근하려 시도하면 해당 객체의 내부 [[Prototype]] 링크를 따라 다음 수색 장소를 결정한다. 모든 일반 객체의 최상위 프로토타입 연쇄는 내장 Object.prototype이고 이 지점에서도 찾지 못하면 탐색이 종료된다.

두 객체를 서로 연결짓는 가장 일반적인 방법은 함수 호출 시 new키워드를 앞에 붙이는 것이다.
new키워드는 일반 함수 호출 + "객체" 생성이라는 잔업을 더 부과하는 지시자이다.
const f = new Foo()를 실행하면 Foo 함수가 실행되고, 객체가 생성되어 변수 f
에 할당된다.

typeof f // object
typeof Foo //function
  • constructor는 내가 선언한 생성자 함수(Foo)를 가리킨다. new 키워드와 함께 함수를 호출할 경우 constructor함수를 실행하고 부수효과로 객체가 생성된다.
    생성자 함수가 아니라 함수를 생성하는 호출이라고 생각하는것이 맞다
  • prototype은 생성자 함수에 정의한 모든 객체가 공유할 원형이다.
  • proto는 [[Prototype]]링크이다. 생성자 함수에 정의해두었던 prototype을 참조한다.

참고

3개의 댓글

comment-user-thumbnail
2020년 5월 24일

프로토타입링크부분에 자바스크립트 객체는 prototype 프로퍼티를 가진다. 보다는 자바스크립트 함수는 prototype을 가지고 모든 객체는 proto 프로퍼티를 가진다. 라고 표현하는게 더 정확하지 않을까요?

거의모든 객체라는 표현이 혼동을 일으킬수있을꺼같아요!!
저도 잘 모르지만 제가아는 지식에서 질문드려봐요 ㅎㅎ

1개의 답글
comment-user-thumbnail
2021년 2월 21일

명료하게 작성해주셔서 이해하기 아주 용이했습니다. 감사합니다.

답글 달기