프로토타입

  • 객체의 부모역할을 하는 객체
  • 자식 객체에게 프로퍼티와 메서드를 상속

프로토타입 사용이유

  • 생성자 함수로 인스턴스 생성할 때, 중복 프로퍼티, 메서드 생성해서 자원이 낭비됨
  • 만약 10개의 인스턴스 생성하면 동일한 메서드가 10개 생성되는 셈
  • 프로토타입을 활용해 상속하여 불필요한 중복 제거 가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 생성자 함수로 사용한 경우 >> 메서드 중복되어 자원낭비
function Circler(radius) {
    this.area = radius;
    this.getArea = function() {
        return Math.PI * this.radius ** 2;
    }
}
 
// 인스턴스 2개 생성
const circle1 = new Circle(1);
const circle2 = new Circle(2);
 
// 동일한 역할을 하는 메소드지만, 각각 인스턴스가 소유해서 자원이 낭비
console.log(circle1.getArea === circle2.getArea); // false
 
 
// prototype 사용할 경우 >> 상속통해 중복제거 가능
function Circle(radius) {
    this.area = radius;
}
Circle.prototype.getArea = function() {
    return Math.PI * this.radius ** 2;
}
 
// 인스턴스 2개 생성
const circle1 = new Circle(1);
const circle2 = new Circle(2);
 
// 하나의 메서드를 공유
console.log(circle1.getArea === circle2.getArea); // true
cs

프로토타입 체인

  • 프로토타입이 한 방향으로 연결되어있는 상속구조
  • 정확히 말하면 단방향 링크드 리스트
  • 객체의 프로퍼티, 메서드에 접근할때 없으면, 프로토타입 체인 따라서 부모 프로퍼티 확인
  • 프로토타입 체인의 최상위 객체 : Object.prototype
  • 모든 객체는 Object.prototype의 프로퍼티와 메서드 사용 가능

프로토타입, 생성자 함수 관계

  • 프로토타입
    • 객체의 __proto__로 접근 가능
      • __proto__ : 프로토타입에 접근하는 내부 접근자, __proto__ 참조
    • constructor 메서드를 가짐 : 생성자 함수
    • __proto__ 프로퍼티 : Object.prototype
  • 생성자 함수
    • prototype 프로퍼티 : Prototype 객체
    • __proto__ 프로퍼티 : Function.prototype
    • 프로토타입 생성 시점 : 생성자 함수 생성될때 생성

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

  • 오버라이딩 : 프로토타입에 정의된 프로퍼티, 메서드를 객체에서 새롭게 추가
    • 객체에 추가된 프로퍼티 호출됨
    • 프로토타입 프로퍼티를 덮어쓰는것이 아닌, 객체에 새로운 프로퍼티가 추가
  • 프로퍼티 섀도잉 : 객체에 새로운 프로퍼티, 메서드 추가할때, 프로토타입의 것이 가려지는 현상

출처

  • 모던 자바스크립트 Deep Dive, Ch 19

__proto__ 란?

  • 객체에서 __proto__를 통해 프로토타입에 접근 가능
  • 프로토타입은 내부슬롯 [[Prototype]]으로 존재
  • __proto__ : 접근자 프로퍼티로 [[Prototype]]에 접근
    • 접근자 프로퍼티 : 다른 프로퍼티나 내부슬롯을 읽거나 저장할때 사용
  • 사용하는 이유
    • 서로 프로토타입을 참조해서 프로토타입 체인이 꼬여버리는 현상을 방지하기 위해
    • 프로토타입 검색할때 무한 루프에 빠져버리게됨
    • 접근자 프로퍼티 내에서 서로를 참조하는지 체크
  • 직접 사용하는 것은 권장되지 않음
    • Object.getPrototypeOf 사용 : 프로토타입 가져올때
    • Object.setPrototypeOf 사용 : 프로토타입 지정할때
    • 이유 : 모든 객체가 __proto__를 가지고 있지 않음
      • __proto__는 Object.prototype의 접근자 프로퍼티
      • Object.prototype을 상속받지 않을경우 사용 불가능
      • 예시 : null을 상속받아 생성한 객체는 사용 불가

출처

  • 모던 자바스크립트 Deep Dive, Ch 19

직접상속

  • 프로토타입을 지정해서, 프로토타입을 상속한 객체를 생성
  • Object.create
    • 첫번째 매개변수 : 프로토타입
    • 두번째 매개변수 : 생성할 객체의 프로퍼티 키와 디스크립터 객체 전달
  • 프로퍼티 정의가 번거로움
  • 객체 내부에서 proto 설정
    • 프로퍼티 쉽게 설정 가능
1
2
3
4
5
6
7
8
9
10
11
12
// 직접상속 : Object.create
const obj = Object.create(Object.prototype, {
    x : {value:1, writable: true, enumerable : true, configurable: true}
})
 
// 직접상속 : 객체 내부 __proto__
const myProto = { x : 10 };
const obj = {
    y: 20,
    __proto__ : myProto
};
 
cs

출처

  • 모던 자바스크립트 Deep Dive, Ch 19

정적 프로퍼티, 메서드

  • 정적은 static을 의미, 자바에서 클래스에서 static으로 정의할 경우 인스턴스 생성하지 않아도 사용 가능한 개념과 유사
  • 생성자 함수로 인스턴스 생성하지 않아도 사용할수 있는 프로퍼티, 메서드
  • 생성자 함수도 객체이므로, 프로퍼티, 메서드 소유 가능
  • 단, 생성자 함수로 생성한 인스턴스로는 사용 불가
  • 예시 : Object
    • Object.create : Object 생성자 함수의 정적 메서드
    • Object 상속받은 객체에서는 호출 불가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
예시
// 생성자 함수
function Person(name) {
    this.name = name;
}
 
// 정적 프로퍼티, 메서드 추가
Person.staticProp = ‘static prop’;
Person.staticMethod = function() {
    console.log(‘static method’);
}
 
Person.staticProp(); // static prop
Person.staticMethod(); // static method
 
// 인스턴스에서 호출
const me = new Person(‘Han’);
me.staticProp(); // Error
 
 
cs

출처

  • 모던 자바스크립트 Deep Dive, Ch 19

객체 생성방식

  • 리터럴 : 일반적이고 간단

    1
    var a = {b : ‘c’, d : ‘e’}
    cs

     

  • 생성자 함수 : 오늘 알아볼것

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function a() {
     
    this.b = ‘c’;
     
    this.d = ‘e’;
     
    }
     
    var a = new a();
    cs

 

생성자 함수

  • new와 함께 호출하여 객체를 생성하는 함수

  • new없이 호출하면 생성자 함수가 아닌 일반 함수로 동작

  • 종류 : Object, String, Number, Boolean, Function, Array, Date, RegExp, Promise 등

  • 사용하는 이유

    • 객체 리터럴의 문제점 : 동일한 객체 여러번 생성할때 비효율적
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      const a1 {
       
      num : 123,
       
      printnum() {return this.num};
       
       }
       
      console.log(a1.printnum()); // 123
       
       
       
      const b1 {
       
      num : 456,
       
      printnum() {return this.num};
       
       }
       
      console.log(b1.printnum()); // 456
      cs
    • 생성자 함수를 사용할 경우 프로퍼티 구조 동일한 객체를 간편하게 생성 가능
    • 자바의 클래스 개념과 유사
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      function a(num) {
       
      this.num = num;
       
      this.printnum = function() {return this.num};
       
      }
       
       
       
      const a1 = a(123);
       
      const b1 = a(456);
       
       
       
      console.log(a1.printnum()); // 123
       
      console.log(b1.printnum()); // 456
      cs

생성자 함수, 인스턴스 생성 과정

  • 인스턴스 생성과 this 바인딩
    • 빈 인스턴스가 생성
    • this는 빈 인스턴스에 바인딩
      • 앞에 new 붙이지 않으면, this는 window를 가르킴(일반 함수로 호출되기 때문)
      • 그래서 함수와 생성자 함수를 구분하기 위해서 생성자 함수는 첫글자를 대문자로함(파스칼 케이스라고 부름)
    • 위 과정은 생성자 함수 코드가 실행되기 전에 실행됨
      • 어려운 말로, 런타임 이전에 실행
  • 인스턴스 초기화
    • 생성자 함수 코드 한줄씩 실행되며, 인스턴스를 초기화
  • 인스턴스 반환
    • return 따로 명시하지 않아도 this를 반환
    • 만약 this아닌 다른 객체를 return 하면, this가 아닌 다른 객체를 return
    • this 아닌 primitive 반환하면 무시
      • primitive : String, Number 등
      • 이러면 꼬이게됨, 일반적으로 return을 생략해서 사용
  • 예시
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    function Circle(radius) {
     
    // 1. 빈 인스턴스 생성 및 this에 바인딩
     
     
     
    // 2. 코드 한줄씩 실행되며 인스턴스 초기화
     
    this.radius = radius;
     
    this.getDiameter = function() {
     
        return 2 * this.radius;
     
    };
     
     
     
    // 3.this를 return
     
    };
     
    cs

생성자 함수로 만든 객체가 일반 객체와 다른점

  • 일반 객체는 호출할 수 없지만 함수는 호출 가능
    • 일반 객체에게 없는, 함수로서 동작하기 위한 내부슬롯과 내부 메소드를 가짐
    • 내부슬롯, 내부메소드 : 외부로 노출되진 않지만, 자바스크립트 내부에서 동작하는 객체, 메소드
    • 내부슬롯
      • [[Environment]]
      • [[FormalPrameters]]
    • 내부 메소드
      • [[Call]] : 일반함수로서 호출되면 실행
      • [[Construct]] : new 연산자와 함께 호출되면 실행

생성자 함수로 객체 생성이 불가능한 함수

  • 함수 내부 메소드로 [[Construct]]가 없는것은 생성자로 함수로 객체 생성이 불가능
  • 객체 생성이 가능한 함수
    • constructor로 부름
    • 함수 선언문 : function Circle() {~~}
    • 함수 표현식 : var Circle = function() {~~}
    • 클래스(클래스도 함수)
  • 객체 생성이 불가능한 함수
    • ES6 메서드 축약 표현 :
      const obj = {
      x() {}
      }
    • 화살표 함수 : const Circle = () => {};

생성자 함수가 일반 함수와 다른점

  • this
    • 생성자 함수는 this가 인스턴스를 가르키지만,
  • 일반 함수의 this는 window를 가르킴
    • 그래서 함수와 생성자 함수를 구분하기 위해서 생성자 함수는 첫글자를 대문자로함(파스칼 케이스라고 부름)
  • new.target : 생성자 함수로서 호출되었는지 확인 가능
  • 생성자 함수로 호출되면(new 연산자로 호출되면), new.target은 함수 자신을 가르킴
    • 일반 함수로 호출되면 new.target은 undefined
    • 일반 함수로 호출되어도 생성자 함수로 호출되도록 사용하기 위해 활용됨
      1
      2
      3
      4
      5
      6
      7
      8
      9
      function Circle(radius) {
          if(!new.target) {
              return new Circle(radius); // 일반 함수로 호출되었을경우 생성자로 다시 호출
      }
       
      this.radius = radius;
      this.getDiameter = function() {return 2*this.radius;};
       
      }
      cs
    • 스코프 세이프 생성자 패턴
      • new.target은 ES6부터 도입해서, 비교적 최신 버전에서만 지원
      • 그래서 new.target 대신, this instanceof Circle 로 사용하기도 함

this

  • this는 기본적으로 window

  • 객체 메서드 안에서 this는 객체를 가르킴

    • 객체 메서드 : 객체 안에 있는 함수

    • 객체의 메서드 호출할 때 내부적으로 this를 바꿔줌

    • 단, 객체의 메서드를 호출한 것이 아닌, 객체의 메서드를 변수에 저장해서 호출할 경우 객체가 아닌 window를 가르킴

      1
      2
      3
      4
      5
      6
      7
      var obj = {
          a : function() {console.log(this)}
      };
      obj.a(); // obj
       
      var a2 = obj.a; 
      a2(); // window
      cs
  • 생성자 함수로 만든 객체에서 this는 인스턴스 자신을 나타냄

  • 이벤트 리스너

    • this : 이벤트 발생한 객체
    • 바뀐것이기 때문에 외울수밖에 없음
    1
    2
    3
    document.body.onclick = function(){
    console.log(this); // <body>
    }
    cs
  • 제이쿼리

    • 내부적으로 this를 클릭한 객체로 바꿔줌
    1
    2
    3
    $(‘div’).on(‘click’,function(){
        console.log(this);
    })
    cs
    • click 이벤트 안에서 메소드 나타날경우, this는 window가르킴

      • 함수의 this는 기본적으로 window라는 원칙을 따름
      • 이때 이벤트 호출한 객체를 저장하기 위해 var that = this와 같이 새로운 변수에 저장해서 사용하는 방법이 있음
    1
    2
    3
    4
    5
    6
    7
    $(‘div’).on(‘click’,function(){
        console.log(this); // div
    function inner() {
        console.log(‘inner’,this); // inner window
    }
    });
     
    cs

내부 슬롯, 내부 메서드

  • ECMAScript에서 사용하는 의사 프로퍼티(pseudo property), 의사 메서드(pseudo method)
  • ECMAScript에서 이중 대괄호로 감싸서 표현
  • 실제로 동작하지만, 개발자가 접근할 수 없음
    • 자바스크립트 엔진의 내부 로직
    • 일부 내부 슬롯, 내부 메서드에 한하여 간접적으로 접근 가능
  • 예시
    • 모든 객체 [[Prototype]] 내부 슬롯 직접 접근 불가능, proto로 접근 가능
1
2
3
const o = {};
o.[[prototype]] // Error
o.__proto__ // Object.prototype
cs

프로퍼티 어트리뷰트

  • 객체에서 프로퍼티 생성할 때, 프로퍼티 상태 나타내는 어트리뷰트도 함께 생성
  • 내부슬롯이어서 직접 접근 불가능
  • Object.getOwnPropertyDescriptor 사용해서 간접적 확인 가능
1
2
3
4
const person = {name:’Lee’};
console.log(Object.getOwnPropertyDescriptor(person, ‘name’));
// {value : ‘Lee’, writable : true, enumerable: true, configurable: ture}
 
cs
  • 종류(데이터 프로퍼티의 경우)
    • 데이터 프로퍼티 : 일반적 프로퍼티, 데이터 프로퍼티 외에 접근자 프로퍼티도 있음
      • [[value]] : 값
      • [[writable]] : 갱신 가능여부
      • [[enumerable]] : 열거 가능 여부
      • [[configurable]] : 재정의 가능 여부
    • 갱신과 재정의의 차이 :
      • 갱신 가능할경우, 해당 프로퍼티를 덮어 쓸 수 있음
      • 재정의 가능할 경우, 삭제하거나 재정의 불가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Object.defineProperty(person, ‘firstName’, {
    value:’Jango’
}); // value만 명시해줄경우, 나머지 속성들 false
console.log(Object.getOwnPropertyDescriptor(person, ‘firstName’);
// {value:’Jango’, writable: false, enumerable: false, configurable : false};
 
person.firstName = ‘Dingo’; // 갱신 불가능, writable = false이므로
// 에러발생하진 않고 무시됨
 
delete person,firstName; // 삭제 불가능, configurable = false이므로
// 에러 발생하진 않고 무시됨
 
Object.defineProperty(person, ‘lastName, {enumerable:true}) 
// 재정의 불가능, configurable = false이므로
// 에러 발생
 
cs

프로퍼티 종류

  • 데이터 프로퍼티 : 일반적 프로퍼티
    • 앞의 프로퍼티 어트리뷰트를 가짐
  • 접근자 프로퍼티 : 값을 갖지 않고, 다른 프로퍼티 읽거나 저장할때 사용
    • 프로퍼티 어트리뷰트
      • [[get]] : 데이터 프로퍼티 값읽을때 호출
      • [[set]] : 데이터 프로퍼티 값 저장할때 호출
      • [[enumerable]] : 데이터 프로퍼티 enumerable과 일치
      • [[configurable]] : 데이터 프로퍼티 configurable과 일치
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const person = {
    // 데이터 프로퍼티
    firstName : ‘Jango’,
    lastName : ‘Han’
 
    // 접근자 프로퍼티
    get fullName() {
        return `${this.firstName} ${this.lastName}`;
    }
set fullName(name) {
        [this.firstName, this.lastName] = name.split(‘ ‘);
    }
}
 
console.log(person.fullName); // Jango Han
person.fullName = ‘HY L’
console.log(person.firstName); // HY
console.log(person.lastName); // L
cs

출처

  • 모던 자바스크립트 Deep Dive[이웅모 지음], Ch.16

+ Recent posts