[TIL] 자바스크립트 객체 지향 기본기

Jeris·2023년 4월 13일
0

코드잇 부트캠프 0기

목록 보기
28/107

Topic

객체 지향 프로그래밍, 추상화, 캡슐화, 상속, 다형성



What I Learned

객체와 클래스

  1. 객체 지향 프로그래밍이란?

    • 객체 지향 프로그래밍(Object-Oriented Programming, OOP): 컴퓨터 프로그램을 여러 개의 독립된 단위인 객체들의 모임으로 파악하고자 하는 컴퓨터 프로그래밍의 패러다임
    • 객체의 구성
      • 프로퍼티(property): 변수
      • 메소드(method): 함수
    • 절차 지향 프로그래밍(Process-Oriented Programming, POP): 데이터 구조와 이에 수반하는 동작들을 분리하는 컴퓨터 프로그래밍의 패러다임
  2. 객체 만들기 1-1: Object-Literal

    • Object-Literal 중괄호를 쓰고 그 안에 프로퍼티와 메소드를 나열하는 것
      const user = {
        email: 'chris123@google.com',
        birthdate: '1992-03-21',
        buy(item) {
          console.log(`${this.email} buys ${item.name}`);
        },
      };
  3. 객체 만들기 1-2: Factory function

    • factory function 객체를 생성해서 리턴하는 함수

      function createUser(email, birthdate) {
        const user = {
          email,
          birthdate,
          buy(item) {
            console.log(`${this.email} buys ${item.name}`);
          },
        };
        return user;
      }
      
      const user1 = createUser('a@email.com', '1999-09-01');
      const user2 = createUser('b@email.com', '1998-07-02');
      const user3 = createUser('c@email.com', '1990-11-24');
  4. 객체 만들기 2: Constructor function

    • Constructor Function 함수 안에서 this를 활용해 객체를 생성하고 리턴하는 특별한 함수

      • this 키워드는 자바스크립트의 다양한 상황에서 다르게 쓰이며, constructor function 내에서는 생성할 객체를 가리킨다.

      • new 키워드를 앞에 붙여서 함수를 호출해야 정상적으로 작동한다.

      • 리턴문을 작성하지 않아도 생성한 객체를 자동으로 리턴한다.

      • 함수의 첫 문자를 대문자로 지정하도록 많은 스타일가이드에서 권장한다.

        function User(email, birthdate) {
          this.email = email;
          this.birthdate = birthdate;
          this.buy = function (item) {
            console.log(`${this.email} buys ${item.name}`); 
          };
        }
        
        const user1 =  new User('a@email.com', '1999-09-01');
        const user2 = new User('b@email.com', '1998-07-02');
        const user3 = new User('c@email.com', '1990-11-24');
  5. 객체 만들기 3: Class(ES2015)

    • Class 객체를 생성하기 위한 템플릿

      • this 키워드는 Class 내에서 생성할 객체를 가리킨다.

      • 보통 프로퍼티는 constructor() 안쪽에, 메소드나 getter는 constructor() 바깥쪽에 작성한다.

      • Declaration, unnamed expression, named expression 세 가지 방법으로 Class 객체를 만들 수 있다.

      • Hoisting을 허용하지 않는다.

        // Class 선언식(declaration)
        class User1 {
          constructor(email, birthdate) {
            this.email = email;
            this.birthdate = birthdate;
          }
          buy(item) {
            console.log(`${this.email} buys ${item.name}`); 
          }
        }
        
        const user1 =  new User('a@email.com', '1999-09-01');
        // Class 표현식(expression)
        // unnamed
        const User = class {
          constructor(email, birthdate) {
            this.email = email;
            this.birthdate = birthdate;
          }
          buy(item) {
            console.log(`${this.email} buys ${item.name}`);
          }
        }
        
        const user1 =  new User('a@email.com', '1999-09-01');
        // Class 표현식(expression)
        // named
        const User = class UserClassName {
          constructor(email, birthdate) {
            this.email = email;
            this.birthdate = birthdate;
          }
          buy(item) {
            console.log(`${this.email} buys ${item.name}`);
          }
        }
        
        const user1 = new User('a@email.com', '1999-09-01');
        
        User.name // "UserClassName"
      • Classes - JavaScript | MDN 참조


객체 지향 프로그래밍의 4개의 기둥

  1. 추상화(abstraction): 어떤 구체적인 것을 원하는 방향으로 간략화해서 나타내는 것

  2. 캡슐화(Encapsulation): 객체의 특정 프로퍼티에 직접 접근하지 못하도록 막는 것

    • 특정 프로퍼티에 대한 접근을 getter, setter 메소드들을 통해서만 가능하도록 할 수 있다

      class User {
        constructor(email, birthdate) {
          this.email = email;
          this.birthdate = birthdate;
        }
      
        buy(item) {
          console.log(`${this.email} buys ${item.name}`); 
        }
        
        get email() {
          return this._email;
        }
      
        set email(address) {
          if (addrress.includes('@')) {
            this._email = address;
          } else {
            throw new Error('invalid email address');
          }
        }
      }
    • get() getter 메소드는 obj.propName을 사용해 프로퍼티를 읽으려고 할 때 실행된다.

    • set() setter 메소드는 obj.propName = value을 사용해 프로퍼티에 값을 할당하려 할 때 실행된다.

    • 프로퍼티 네임과 동일한 이름을 getter, setter의 이름으로 사용했기 때문에, 프로퍼티에 접근하려면 obj._propName처럼 프로퍼티 네임 앞에 언더바(_)를 붙여서 접근할 수 있다.

    • getter, setter만 설정할 경우 obj._propName의 값은 여전히 접근 가능하고, 수정도 가능하기 때문에 완벽한 캡슐화라고 볼 수 없다.

  3. 캡슐화 더 알아보기

    • 완벽한 캡슐화

      • Java에는 private이라는 키워드가 있어서 문법 차원에서 캡슐화를 지원하지만, JavaScript에는 캡슐화를 자체적으로 지원하는 문법이 아직 없다.

      • 하지만 JavaScript에서도 클로저(Closure)라고 하는 개념을 응용해서 완벽한 캡슐화를 할 수 있다

      • closure 어떤 함수와 그 함수가 참조할 수 있는 값들로 이루어진 환경을 하나로 묶어서, 함수가 정의된 당시에 참조할 수 있었던 변수들을 계속 참조할 수 있는 상태의 함수

        function createUser(email, birthdate) {
          let _email = email;
        
          const user = {
            birthdate,
        
            get email() {
              return _email;
            },
        
            set email(address) {
              if (address.includes('@')) {
                _email = address;
              } else {
                throw new Error('invalid email address');
              }
            },
          };
        
          return user;
        }
      • function 안, user 객체 바깥에 _email 변수를 만들고, _email 변수의 값을 읽고 쓸 수 있는 email이라는 getter/setter 메소드를 만들면 완벽한 캡슐화가 가능하다.

      • 메소드에도 closure 개념을 응용할 수 있다

        function createUser(email, birthdate) {
          const _email = email;
          let _point = 0;
        	  
          // 사용자가 물건을 살 때마다 1포인트씩 적립해 주는 함수
          function increasePoint() {
            _point += 1;
          }
        
          const user = {
            birthdate,
        
            get email() {
              return _email;
            },
        
            get point() {
              return _point;
            },
        
            buy(item) {
              console.log(`${this.email} buys ${item.name}`);
              increasePoint();
            },
          };
        
          return user;
        }
      • function 안, user 객체 바깥에 _point 변수와 incresePoint() 함수를 만들고, _point 변수의 값을 읽을 수 있는 point라는 getter 메소드를 만들면 완벽한 캡슐화가 가능하다.

  4. 상속(inheritance): 자식 객체가 부모 객체의 프로퍼티와 메소드를 물려받는 것

    • extends 클래스를 다른 클래스의 자식으로 만들기 위해 사용되는 키워드

      class User {
        constructor(email, birthdate) {
          this.email = email;
          this.birthdate = birthdate;
        }
        buy(item) {
          console.log(`${this.email} buys ${item.name}`); 
        }
      }
      
      class PremiumUser extends User {
        constructor(email, birthdate, level) {
          super(email, birthdate);
          this.level = level;
        }
      
        streamMusicForFree() {
          console.log(`Free music streaming for ${this.email}`);
        }
      }
    • 자식 클래스의 constructor() 안쪽에 super() 키워드를 호출해야 자식 클래스에서 this 키워드에 접근할 수 있다. 호출하기 전에는 ReferenceError가 발생한다.

  5. 다형성(Polymorphism): 프로그램 언어 각 요소들이 다양한 데이터 타입에 속하는 것이 허가되는 성질

    const user1 = new User('chris123@google.com', '19920321');
    const user2 = new User('rache@google.com', '19880516');
    const user3 = new User('brian@google.com', '20051125');
    
    const pUser1 = new PremiumUser('tommy@google.com', '19901207', 3);
    const pUser2 = new PremiumUser('helloMike@google.com', '19900915', 2);
    const pUser3 = new PremiumUser('alicekim@google.com', '20010722', 5);
    
    const users = [user1, pUser1, user2, pUser2, user3, pUser3];
    
    users.forEach((user) => {
      user.buy(item);
    });
    • 이렇게 하나의 변수 user가 다양한 종류의 객체 User, PremiumUser를 가리킬 수 있는 것도 다형성이라고 한다.
    • 자식 클래스에서 부모 클래스의 메소드를 오버라이딩하고 그 후에 이 다형성을 활용하는 경우가 많다.
  6. 부모 클래스의 메소드가 필요하다면?

    • 자식 클래스에서 부모 클래스의 메소드를 오버라이딩해서 부모 클래스의 메소드를 불러올 수 없을 때, super.method() 키워드를 사용해서 부모 클래스의 메소드를 자식 클래스 내에서 불러올 수 있다.
  7. instanceof 연산자

    • object instanceof constructor constructor의 프로토타입 프로퍼티가 객체의 프로토타입 체인에 나타나는지 여부를 검사하는 연산자
    • 객체의 프로토타입 체인을 검사하므로 자식 클래스로 만든 객체를 왼쪽에, 부모 클래스를 오른쪽에 넣고 instaceof 연산자를 적용해도 true를 리턴한다.
  8. static 프로퍼티와 static 메소드

    • static 클래스로 만든 객체가 아니라 클래스 자체로 접근할 수 있는 프로퍼티나 메소드

      class Math {
        static PI = 3.14;
      
        static getCircleArea(radius) {
          return Math.PI * radius * radius;
        }
      }
      
      // static 프로퍼티 값 변경
      Math.PI = 3.141592;
      // static 메소드 추가
      Math.getRectangleArea = function (width, height) {
        return width * height;
      }
      
      console.log(Math.PI);
      console.log(Math.getRectangleArea(4, 5));
      
      // Date 내장 객체의 static 메소드 now()
      console.log(Date.now());
    • static property나 method도 getter, setter 메소드로 캡슐화할 수 있다.

  9. 클래스는 파일 하나당 하나씩 넣어주는 게 좋아요

    • 보통 하나의 클래스를 모듈화하여 export하는 방식을 많이 사용한다



Feedback

  • JavaScript의 프로토타입(Prototype)에 대한 보충 학습이 필요하다.
  • Java의 클래스 개념이 많이 들어가 있는 것 같다. Java를 공부할 때 공통점과 차이점을 유념하자.
  • 다음으로 '재귀 함수' 코드잇 콘텐츠를 수강할 예정이다.



Reference

profile
job's done

0개의 댓글