Skip to content

Latest commit

 

History

History
374 lines (235 loc) · 16.5 KB

File metadata and controls

374 lines (235 loc) · 16.5 KB

프로토타입

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

객체지향 프로그래밍

  • 속성: 실체의 특징이나 성질을 나타냄. 이를 통해 실체를 인식하고 구별 가능
  • 추상화: 프로그램에 필요한 속성만 간추려 내어 표현하는 것

객체는 속성을 통해 여러 개의 값을 하나의 단위로 구성한 복합적인 자료구조이며, 객체지향 프로그래밍은 독립적인 객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임이다.

객체지향 프로그래밍은 객체의 상태를 나타내는 데이터와 상태 데이터를 조작할 수 있는 동작을 하나의 논리적인 단위로 묶어 생각한다.

상속과 프로토타입

상속(inheritance)은 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것을 말한다.

자바스크립트는 프로토타입을 기반으로 기존의 코드를 적극적으로 재사용하여 불필요한 중복을 제거하도록 객체 간 상속을 구현한다.

생성자 함수가 생성할 모든 인스턴스가 공통적으로 사용할 프로퍼티나 메서드를 프로토타입에 미리 구현해 두면 생성자 함수가 생성할 모든 인스턴스는 별도의 구현 없이 상위(부모) 객체인 프로토타입의 자산을 공유하여 사용할 수 있다.

프로토타입 객체

모든 객체는 하나의 프로토타입을 가지며 모든 프로토타입은 생성자 함수와 연결되어 있다.

__proto__ 접근자 프로퍼티

모든 객체는 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입, 즉 [[Prototype]] 내부 슬롯에 간접적으로 접근할 수 있다.

__proto__ 는 접근자 프로퍼티다.

Object.prototype의 접근자 프로퍼티인 __proto__ 는 getter/setter 함수라고 부르는 접근자 함수를 통해 [[Prototype]] 내부 슬롯의 값인 프로토타입을 취득하거나 할당한다.

__proto__ 접근자 프로퍼티를 통해 프로토타입에 접근 -> 내부적으로 __proto__ 접근자 프로퍼티의 getter 함수인 [[Get]] 호출

__proto__ 접근자 프로퍼티를 통해 새로운 프로토타입을 할당하면 __proto__ 접근자 프로퍼티의 setter 함수인 [[Set]] 호출

const obj = {};
const parent = { x: 1 };

// getter 함수인 get __proto__가 호출되어 obj 객체의 프로토타입을 취득
obj.__proto__;

// setter 함수인 set __proto__가 호출되어 obj 객체의 프로토타입을 교체
obj.__proto__ = parent;

console.log(obj.x); // 1

__proto__ 접근자 프로퍼티는 상속을 통해 사용된다.

__proto__ 접근자 프로퍼티는 Object.prototype의 프로퍼티이므로 모든 객체는 상속을 통해 Object.prototype.__proto__ 접근자 프로퍼티를 사용할 수 있다.

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

프로토타입 체인은 단방향 링크드 리스트로 구현되어야 하기 때문에 상호 참조에 의해 순환 참조 프로토타입 체인이 생성되는 것을 방지하기 위해서 사용한다.

__proto__ 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장하지 않는다.

직접 상속을 통해 Object.prototype을 상속받지 않는 객체를 생성할 수도 있기 때문에 __proto__ 접근자 프로퍼티를 사용할 수 없는 경우가 있다.

__proto__ 접근자 프로퍼티 대신 프로토타입의 참조를 취득하고 싶은 경우에는 Object.getPrototypeOf 메서드를 사용하고, 프로토타입을 교체하고 싶은 경우에는 Object.setPrototypeOf 메서드를 사용할 것을 권장한다.

함수 객체의 prototype 프로퍼티

함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다.

구분 소유 사용 주체 사용 목적
__proto__ 접근자 프로퍼티 모든 객체 프로토타입의 참조 모든 객체 객체가 자신의 프로토타입에 접근 또는 교체하기 위해 사용
prototype 프로퍼티 constructor 프로토타입의 참조 생성자 함수 생성자 함수가 자신이 생성할 객체(인스턴스)의 프로토타입을 할당하기 위해 사용

프로토타입의 constructor 프로퍼티와 생성자 함수

모든 프로토타입은 constructor 프로퍼티를 가지며 constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킨다.

리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입

리터럴 표기법에 의해 생성된 객체도 프로토타입이 존재하지만 리터럴 표기법으로 생성된 객체의 경우 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수는 객체를 생성한 생성자 함수가 아닌 Object 생성자 함수를 가리킨다.

객체 리터럴이 평가될 때는 추상 연산 OrdinaryObjectCreate를 호출하여 빈 객체를 생성하고 프로퍼티를 추가하도록 정의되어 있다.

리터럴 표기법으로 생성된 객체는 생성 과정에서 차이가 있지만 결국 객체로서 동일한 특성을 갖기 때문에 프로토타입의 constructor 프로퍼티를 통해 연결되어 있는 생성자 함수를 리터럴 표기법으로 생성한 객체를 생성한 생성자 함수로 생각해도 된다.

리터럴 표기법 생성자 함수 프로토타입
객체 리터럴 Object Object.prototype
함수 리터럴 Function Function.prototype
배열 리터럴 Array Array.prototype
정규 표현식 리터럴 RegExp RegExp.prototype

프로토타입의 생성 시점

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

사용자 정의 생성자 함수와 프로토타입 생성 시점

생성자 함수로서 호출할 수 있는 함수인 constructor는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다.

생성된 프로토타입은 언제나 Object.prototype이다.

빌트인 생성자 함수와 프로토타입 생성 시점

모든 빌트인 생성자 함수는 전역 객체가 생성되는 시점에 생성되며 이 시점에 프로토타입도 함께 생성된다.

생성된 프로토타입은 빌트인 생성자 함수의 prototype 프로퍼티에 바인딩된다.

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

객체는 어떤 생성 방식으로 생성해도 추상 연산 OrdinaryObjectCreate에 의해 생성된다는 공통점이 있다.

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

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

객체 리터럴에 의해 생성된 객체의 프로토타입은 Object.prototype이다.

Image

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

Object 생성자 함수에 의해 생성되는 객체의 프로토타입은 Object.prototype이다.

객체 리터럴 방식은 객체 리터럴 내부에 프로퍼티를 추가하지만 Object 생성자 함수 방식은 일단 빈 객체를 생성한 이후 프로퍼티를 추가한다는 객체 생성 방식의 차이가 있다.

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

생성자 함수에 의해 생성되는 객체의 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체다.

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

const me = new Person('Lee');

Image

사용자 정의 생성자 함수 Person과 더불어 생성된 Person.prototype의 프로퍼티는 constructor 뿐이기 때문에 Person.prototypedㅔ 프로퍼티를 추가해 하위(자식) 객체가 상속받을 수 있도록 해야 한다.

프로토타입 체인

자바스크립가 객체의 프로퍼티(메서드 포함)에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색하는 것을 프로토타입이라 한다.

프로토타입 체인은 자바스크립트가 객체지향 프로그래밍의 상속을 구현하고 검색을 위한 메커니즘이다.

Object.prototype은 프로토타입 체인의 최상위에 위치하는 프로토타입 체인의 종점(end of prototype chain)이라 한다.

프로퍼티가 아닌 식별자는 스코프 체인에서 검색한다.

스코프 체인과 프로토타입 체인은 서로 연관없이 별도로 동작하는 것이 아니라 서로 협력하여 식별자와 프로퍼티를 검색하는 데 사용된다.

오버라이딩과 프로퍼티 섀도잉

상속 관계에 의해 프로퍼티가 가려지는 현상

프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 프로토타입 체인을 따라 프로토타입 프로퍼티를 검색해 프로토타입 프로토타입 프로퍼티를 덮어쓰는 것이 아니라 인스턴스 프로퍼티로 추가한다.

프로토타입의 교체

프로토타입은 객체 간의 상속 관계를 동적으로 교체할 수 있다.

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

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

    // 생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 객체 리터럴로 교체
    Person.prototype = {
        sayHello(){
            console.log(`Hi! My name is ${this.name}`);
        }
    };

    return Person;
}());

const me = new Person('Lee');

이처럼 프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴된다.
이 연결은 교체한 객체 리터럴에 constructor 프로퍼티를 추가하여 프로토타입의 constructor 프로퍼티를 되살린다.

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

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

    return Person;
}());

const me = new Person('Lee');

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

__proto__ 접근자 프로퍼티를 통해 이미 생성된 객체의 프로토타입을 교체할 수 있다.

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

const me = new Person('Lee');

// 프로토타입으로 교체할 객체
const parent = {
    sayHello() {
        console.log(`Hi! My name is ${this.name}`);
    }
};

// me 객체의 프로토타입을 parent 객체로 교체한다.
Object.setPrototypeOf(me, parent);
// 위 코드는 me.__proto__ = parent; 와 동일하게 동작한다.

me.sayHello();

이 방식 또한 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴된다.
그러나 생성자 함수에 의한 교체와 달리 생성자 함수의 prototype 프로퍼티가 교체된 프로토타입을 가리키지 않는다.

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

const me = new Person('Lee');

// 프로토타입으로 교체할 객체
const parent = {
    // constructor 프로퍼티와 생성자 함수 간의 연결을 설정
    constructor: Person,
    sayHello() {
        console.log(`Hi! My name is ${this.name}`);
    }
};

// 생성자 함수의 prototype 프로퍼티와 프로토타입 간의 연결을 설정
Person.prototype = parent;

// me 객체의 프로토타입을 parent 객체로 교체한다.
Object.setPrototypeOf(me, parent);
// 위 코드는 me.__proto__ = parent; 와 동일하게 동작한다.

me.sayHello();

// constructor 프로퍼티가 생성자 함수를 가리킨다.
console.log(me.constructor === Person); // true
console.log(me.constructor === Object); // false

// 생성자 함수의 prototype 프로퍼티가 교체된 프로토타입을 가리킨다.
console.log(Person.prototype === Object.getPrototypeOf(me));

instanceof 연산자

객체 instanceof 생성자 함수

우변의 생성자 함수의 prototype에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에
존재하면 -> true
존재하지 않으면 -> false

생성자 함수에 의해 프로토타입이 교체되어도 생성자 함수의 prototype 프로퍼티와 프로토타입 간의 연결을 파괴되지 않으므로 instanceof는 아무런 영향을 받지 않는다.

직접 상속

Object.create에 의한 직접 상속

첫 번째 매개변수: 생성할 객체의 프로토타입으로 지정할 객체 두 번째 매개변수: 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이뤄진 객체

첫 번째 매개변수에 전달한 객체의 프로토타입 체인에 속하는 객체를 생성한다.

장점

  • new 연산자가 없이도 객체를 생성할 수 있다.
  • 프로토타입을 지정하면서 객체를 생성할 수 있다.
  • 객체 리터럴에 의해 생성된 객체도 상속받을 수 있다.

단점

  • 모든 객체의 종점은 Object.prototype 메서드여야 하는데 Object.create 메서드를 통해 프로토타입 체인의 종점에 위치하는 객체를 생성할 수 있다.

객체 리터럴 내부에서 __proto__ 에 의한 직접 상속

const myProto = { x: 10 };

// 객체 리터럴에 의해 객체를 생성하면서 프로토타입을 지정하여 직접 상속받을 수 있다.
const obj = {
    y: 20,
    // 객체를 직접 상속받는다.
    // obj -> myProto -> Object.prototype -> null
    __proto__: myProto
};

/* 위 코드는 아래와 동일
const obj = Object.create(myProto, {
    y: {value: 20, writable: true, enumerable: true, configurable: true }
});
*/

console.log(obj.x, obj.y); // 10 20
console.log(Object.getPrototypeOf(obj) === myProto); // true

정적 프로퍼티/메서드

생성자 함수로 인스턴스를 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메서드
생성자 함수가 자신의 프로퍼티/메서드를 소유하는 것이다.

정적 프로퍼티/메서드는 생성자 함수가 생성한 인스턴스로 참조/호출할 수 없다.

만약 인스턴스/프로토타입 메서드 내에서 this를 사용하지 않는다면 그 메서드는 정적 메서드로 변경할 수 있다.

프로퍼티 존재 확인

in 연산자

객체 내에 특정 프로퍼티가 존재하는지 확인

key in object

in 연산자는 확인 대상 객체의 프로퍼티뿐만 아니라 확인 대상 객체가 상속받은 모든 프로토타입의 프로퍼티를 확인한다.

Reflect.has 메서드도 in 연산자와 동일하게 동작

Object.prototype.hasOwnProperty 메서드

인수로 전달받은 프로퍼티 키가 객체 고유의 프로퍼티 키인 경우에만 true를 반환하고 상속받은 프로퍼티 키인 경우 false를 반환한다.

프로퍼티 열거

for...in 문

객체의 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서 프로퍼티 어트리뷰트 [[Emumerable]]의 값이 true인 프로퍼티를 순회하며 열거

for (변수선언문 in 객체) {...}

[[Emumerable]]는 프로퍼티의 열거 가능 여부를 나타낸다.

프로퍼티 키가 심벌인 프로퍼티도 열거하지 않는다.

배열의 경우는 상속받은 프로퍼티를 포함하지 않기 위해 일반적인 for문이나 for...of 문 또는 Array.prototype.forEach 메서드를 권장한다.

Object.keys/values/entries 메서드

객체 자신의 고유 프로퍼티만 열거하기 위해 사용

Object.keys는 객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환한다.

Object.values 메서드는 객체 자신의 열거 가능한 프로퍼티 값을 배열로 반환한다.

Object.entries 메서드는 객체 자신의 열거 가능한 프로퍼티 키와 값의 쌍의 배열을 배열에 담아 반환한다.