javaScript/js.ProtoType1

프로토타입(1)

1. 객체지향 프로그래밍 (OPP, Object Oriented Programming)

  • 객체들의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임
  • 상태 데이터와 동작을 하나의 논리적 단위로 묶음 복합적인 자료구조
  • 객체의 상태 데이터 - 프로퍼티(property), 동작 - 메소드(method)
  • 각각의 객체는 자신의 고유한 기능을 수행하면서 다른 객체와 관계성을 갖을 수 있다.

2. 상속과 프로토타입

  • 객체의 프로퍼티 또는 메소드를 다른 객체가 상속받아 그대로 사용할 수 있다.
  • 프로토타입을 기반으로 상속을 구현하여 불필요한 중복을 제거한다.
  • 동일한 생성자 함수에 의해 생성된 모든 인스턴스가 동일한 메소드를 중복 소유하는 것은 메모리를 불필요하게 낭비한다. -> 인스턴스를 생성할 때마다 메소드를 생성하므로 퍼포먼스에도 악영향을 준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//생성자 함수
function Circle(radius) {
this.radius = radius;
}
//Circle 생성자 함수가 생성한 모든 인스턴스가 공유할 수 있도록 getArea 메소드를 프로토 타입에 추가한다.
// 프로토타입은 Circle 생성자 함수의 prototype 프로퍼티에 바인딩되어있다.
Circle.prototype.getArea = funciton = () {
return Math.PI * MAth.pow(this.radius, 2);
};

//인스턴스 생성
const circle1 = new Circle(1);
const circle2 = new Circle(2);

//Circle 생성자 함수가 생성한 모든 인스턴스는 부모 객체의 역할을 하는 프로토타입
//Circle.prototype로부터 getArea 메소드를 상속받는다.
//즉, Circle 생성자 함수가 생성하는 모든 인스턴스는 하나의 getArea 메소드를 공유한다.
console.log(circle1.getArea === circle2.getArea); // true
console.log(circle1.getArea()); // 3.14~
console.log(circle2.getArea()); // 12.56~
  • getArea 메소드는 단 하나만 생성되어 프로토타입인 Circle.prototype의 메소드로 할당
  • Circle 생성자 함수가 생성하는 모든 인스턴스는 getArea 메소드를 상속받아 사용할 수 있다.
  • 자신의 상태를 나타내는 radius 프로퍼티만을 개별적으로 소유하고 내용이 동일한 메소드는 상속을 통해 공유하여 사용하는 것이다.

3. 프로토타입 객체

  • 모든 객체는 [[Prototype]] 이라는 내부 슬롯을 갖고, 생성될 때 [[Prototype]]내부 슬롯의 값으로 프로토타입의 참조를 저장한다.
  • 모든 객체는 하나의 프로토타입을 갖으며 프로토타입은 객체의 생성 방식에 의해 결정된다.
  • 프로토타입은 객체이거나 null. 모든 프로토타입은 생성자 함수와 연결되어 있다.
  • 객체와 프로토타입과 생성자 함수는 서로 연결되어 있다.

3.1 proto 접근자 프로퍼티

  • 모든 객체는 proto 접근자 프로퍼티를 통해 자신의 프로토타입, 즉 [[Prototype]] 내부 슬롯에 접근할 수 있다.
  • 모든 객체는 프로토타입을 가리키는 [[Prototype]] 내부 슬롯에 접근하기 위해 proto 접근자 프로퍼티를 사용할 수 있다.
  • 내부 슬롯은 프로퍼티가 아니다. proto 접근자 프로퍼티를 통해 간접적으로 [[Prototype]] 내부 슬롯의 값, 즉 프로토타입에 접근할 수 있다.
  • Object.prototype.proto 는 접근자 프로퍼티이다.
1
2
3
4
5
6
7
8
const obj = {};
const parent = { x: 1 };
// getter 함수인 get __proto__가 호출되어 obj 객체의 프로토타입을 취득
obj.__proto__;
// setter함수인 set __proto__가 호출되어 obj 객체의 프로토타입을 교체
obj.__proto__ = parent;

console.log(obj.x); //1
  • get proto 는 [[GetPrototypeOf]] 내부 메소드를 호출하여 자신의 프로토타입을 취득

  • set proto 은 [[SetPrototypeOf]] 내부 메소드를 호출하여 새로운 프로토타입을 할당한다.

  • proto 접근자 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아니라 Object.prototype의 프로퍼티이다. 모든 객체는 상속을 통해 Object.prototype.proto 접근자 프로퍼티를 사용할 수 있다.

  • 프로토타입에 접근하기 위해 접근자 프로퍼티를 사용하는 이유는 상호 참좀에 의해 프로토타입 체인이 생성되는 것을 방지하기 위함이다.

    1
    2
    3
    4
    5
    const parent = {};
    const child = {};

    child.__proto__ = parent;
    parent.__proto__ = child; //TypeError: Cyclic __proto__ value
  • 프로토타입 체인은 단방향 링크드 리스트로 구현되어야 한다.

  • 순환 참조적인 프로토타입 체인이 만들어지면 프로토타입 체인 종점이 존재하지 않기 때문에 무한루프에 빠진다.

  • proto 접근자 프로퍼티 대신 프로토타입의 참조를 취득할 경우는 Object.getPrototypeOf메소드, 프로토타입을 교체하는 경우는 Object.setPrototypeOf 메소드를 사용하는 것을 권장

    1
    2
    3
    4
    5
    6
    7
    8
    const obj = {};
    const parent = { x: 1 };
    //obj 객체의 포로토타입을 취득
    Object.getPrototypeOf(obj); //obj.__proto__;
    //obj 객체의 프로토타입을 교체
    Object.getPrototypeOf(obj, parent); //obj.__proto__ = parent;

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

3.2 함수 객체의 prototype 프로퍼티

  • 함수 객체는 proto 접근자 프로퍼티 이외에 prototype 프로퍼티도 소유한다.

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

  • 생성자 함수로서 호출할 수 없는 함수, Arrow, Method인 함수 (non-constructor)는 프로토타입이 생성되지 않으며 prototype 프로퍼티도 소유하지 않는다.

  • 모든 객체가 가지고 있는 proto 접근자 프로퍼티와 함수 객체만이 가지고 있는 prototype 프로퍼티는 동일한 프로토타입을 가리킨다. (프로퍼티를 사용하는 주체는 다르다)

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

  • 모든 프로토타입은 constructor 프로퍼티를 갖는다.
  • constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킨다.
  • 연결은 생성자 함수가 생성될 때, 즉 함수 객체가 생성될 때 이루어진다.
    1
    2
    3
    4
    5
    6
    7
    8
    //생성자 함수
    function Person(name) {
    this.name = name;
    }
    const me = new Person('Jung');

    //me 객체의 생성자 함수는 Person이다.
    console.log(me.constructor === Person); // true
  • me 객체에는 constructor 프로퍼티가 없지만 me 객체의 프로포타입인 Person.prototype에 constructor 프로퍼티가 있다. 상속받아서 사용할 수 있다.

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

생성자 함수에 의해 생성된 인스턴스는 프로토타입의 constructor 프로퍼티에 의해 생성자 함수와 연결된다. 이때 생성자 함수는 인스턴스를 생성한 생성자 함수이다.

1
2
3
4
5
6
7
8
9
10
11
12
//obj 객체를 생성한 생성자 함수는 Object이다.
const obj = new Object();

//add 함수 객체를 생성한 생성자 함수는 Function이다.
const add = new Function('a', 'b', 'return a+b');

//생성자 함수
function Person(name) {
this.name = name;
}
//me 객체를 생성한 생성자 함수는 Person이다.
const me = new Person('Jung');
  • 리터럴 표기법에 의해 생성된 객체도 프로토타입이 존재.
  • 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 단정할 수는 없다.

5. 프로토타입의 생성 시점

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

  • constructor는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다.
  • 생성된 프로토타입은 생성자 함수의 protytype 프로퍼티에 바인딩된다.
  • 생성된 프로토타입은 constructor 프로퍼티만을 갖는 객체이다.
  • 생성된 프로토타입의 프로토타입은 Object.prototype이다.
  • non-constructor는 프로토타입이 생성되지 않는다.

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

  • 빌트인 생성자 함수도 일반 함수와 마찬가지로 빌트인 생성자 함수가 생성됟는 시점에 프로토타입이 생성
  • 모든 빌트인 생성자 함수는 전역객체가 생성되는 시점에 생성된다. 이때, 프로토타입도 생성된다.
  • 생성된 프로토타입은 빌트인 생성자 함수의 prototype 프로퍼티에 바인딩된다.
1
2
3
// 전역객체 window는 브라우저에 종속적이므로 아래 코드는 브라우저 환경에서 실행해야한다.
// 빌트인 객체인 Object는 전역 객체 window의 프로퍼티이다.
window.Object === Object // true
  • 객체가 생성되기 이전에 생성자 함수와 프로토타입은 이미 객체화되서 존재함
  • 이후 생성자 함수 또는 리터럴 표기법으로 객체를 생성하면 프로토타입은 생성된 객체의 [[prototype]] 내부 슬롯에 할당된다. -> 생성된 객체는 프로토타입을 상속받는다.

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

객체 생성방법 5가지

  • 객체 리터럴, Object 생성자함수, 생성자함수, Object.create 메소드, 클래스

  • 추상 연산 ObjectCreate에 의해 생성된다는 공통점을 갖는다.

    • 추상연산 ObjectCreate은 런타임에 새로운 객체를 생성하고, 필수적으로 자신이 생성할 객체의 프로토타입을 인수로 전달 받는다.
    • 자신이 생성할 객체에 추가할 프로퍼티 목록은 옵션으로 전달할 수 있다.
    • ObjectCreate 는 빈 객체를 생성한 후, 객체에 추가할 프로퍼티목록(internalSlotList)이 인수로 전달된 경우, 프로퍼티를 객체에 추가한다.
    • 인수로 전달받은 프로토타입을 자신이 생성한 객체의 [[Prototype]] 내부슬롯에 할당한 다음, 생성한 객체를 반환한다.
    • 프로토타입은 추상연산 ObjectCreate에 전달되는 인수(proto)에 의해 결정된다.

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

자바스크립트 엔진은 객체 리터럴을 평가하여 객체를 생성할 때, ObjectCreate를 호출한다.
이때 ObjectCreate에 전달되는 프로토타입은 Object.prototype이다.

1
2
3
4
const obj =  { x: 1 };
//객체 obj는 Object.prototype을 상속받는다.
console.log(obj.constructor === Object); // true
console.log(obj.hasOwnProperty('x')); //true

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

  • 명시적으로 Object 생성자 함수를 호출하여 객체를 생성하면 빈 객체가 생성된다.
  • Object 생성자 함수를 호출하면 객체 리터럴과 마찬가지로 ObjectCreate를 호출한다.
  • 이때, ObjectCreate에 전달되는 프로토타입은 Object.prototype이다.
    즉, Object 생성자 함수에 의해 생성되는 객체의 프로토타입은 Object.prototype이다
    1
    2
    3
    4
    5
    6
    const obj = new Object();
    obj.x = 1;

    //객체 obj는 Object.prototype을 상속받는다.
    console.log(obj.constructor === Object); //true
    console.log(obj.hasOwnProperty('x')); //true
  • 객체 리터럴과 Object 생성자 함수에 의한 객체 생성 방식의 차이는 프로퍼티를 추가하는 방식에 있다.
    • 객체 리터럴 방식 - 객체 리터럴 내부에 프로퍼티를 추가
    • Object 생성자 함수 방식 - 빈 객체 생성 후 프로퍼티를 추가

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

  • new 연산자와 함께 생성자 함수를 호출하여 인스턴스를 생성하면 다른 객체 방식처럼 ObjectCreate를 호출한다.
  • ObjectCreate에 전달되는 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩 되어있는 객체이다.
  • 생성자 함수에 의해 생성되는 객체의 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩 되어있는 객체이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Person(name) {
this.name = name;
}

//프로토타입 메소드
Person.prototype.sayHello = function () {
console.log('Hi! My name is ${this.name}');
};

const me = new Person('Jung');
const you = new Person('Lee');

me.sayHello(); //Hi! My name is Jung
you.sayHello(); //Hi! My name is Lee
  • 프로토타입 Person.prototype에 프로퍼티를 추가하여 하위 객체가 상속받을 수 있다.
  • 프로토타입은 객체. 일반 객체처럼 프로토타입에 프로퍼티를 추가/삭제할 수 있다.
  • 추가/삭제된 프로퍼티는 프로퍼티 체인에 즉각 반영된다.

7. 프로토타입 체인

  • 자바스크립트는 객체의 프로퍼티에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 proto 접근자 프로퍼티가 가리키는 링크를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다.
  • 프로토타입 체인의 최상위에 위치한 객체는 언제나 Object.prototype이다.
  • Object.prototype의 프로토타입, 즉 [[Prototype]] 내부 슬롯의 값은 null이다.
  • 프로토타입 체인의 종점인 Object.prototype에서도 프로퍼티를 검색할 수 없는 경우, undefined를 반환한다.
  • 스코프체인과 프로토타입체인은 서로 협력하여 식별자와 프로퍼티를 찾아낸다.

8. 캡슐화

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const Person = (function () {
//생성자 함수
function Person(name) {
this.name = name;
}
//프로토타입 메소드
Person.prototype.sayHello = function () {
console.log(`Hi! My name is ${this.name}`);
};
//생성자 함수 반환
return Person;
}());

const me = new Person('Jung');
  • 즉시 실행 함수를 사용하여 생성자 함수와 프로토타입을 확장하는 코드를 하나의 함수 내에 모을 수 있다.

  • 캡슐화(encapsulation)는 정보의 일부를 외부에 감추어 은닉하는 것을 말한다.

  • 외부에 공개할 필요가 없는 일부를 외부에 노출하지 않도록 감추어 정보를 보호, 객체간의 상호 의존성(결합도)를 낮추는 효과

  • 프로퍼티를 캡슐화 예시

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    const Person = (function () {
    let _name = ''; //자유 변수이며 private함

    // 생성자 함수
    function Person(name) { _name = name; }

    // 프로토타입 메소드
    Person.prototype.sayHello = function () {
    console.log(`Hi! My name is ${_name}`);
    };

    //생성자 함수를 반환
    return Person;
    }());

    const me = new Person('jung');

    me._name = 'Lee';
    me.sayHello(); //Hi My name is jung
    //_name은 지역 변수이므로 외부에서 접근하여 변경 불가

    9. 오버라이딩과 프로퍼티 쉐도잉

오버라이딩(Overriding)
상위 클래스가 가지고 있는 메소드를 하위 클래스가 재정의하여 사용하는 방식

오버로딩(Overloading)

  • 함수의 이름은 동일하지만 매개변수의 타입 또는 개수가 다른 메소드를 구현하고 매개변수에 의해 메소드를 구별하여 호출하는 방식.
  • 자바스크립트는 오버로딩을 지원하지 않지만 arguments 객체를 사용하여 구현할 수 있다.

프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 프로토타입 체인을 따라 프로토타입 프로퍼티를 검색하여 프로토타입 프로퍼티를 덮어쓰는 것이 아니라 인스턴스 프로퍼티로 추가한다.
호출했을때, 프로토타입 메소드는 인스턴스 메소드에 의해 가려진다.

  • 하위 객체를 통해 프로토타입의 프로퍼티를 변경 또는 삭제하는 것은 불가능하다.
  • 프로토타입 프로퍼티를 변경 또는 삭제하려면 프로토타입에 직접 접근해야한다. (하위객체 통하지말고)
You forgot to set the qrcode for Alipay. Please set it in _config.yml.
You forgot to set the qrcode for Wechat. Please set it in _config.yml.
You forgot to set the business and currency_code for Paypal. Please set it in _config.yml.
You forgot to set the url Patreon. Please set it in _config.yml.
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×