배꼽파지 않도록 잘 개발해요

[코드잇] 자바스크립트 객체 지향 기본기 본문

코드잇 Codeit/Front-End

[코드잇] 자바스크립트 객체 지향 기본기

꼽파 2023. 12. 31. 09:13


  • 1. 객체와 클래스

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

  • 1. 객체와 클래스

    객체 지향과 절차 지향

    • 객체 지향 프로그래밍 : 프로퍼티와 메소드로 이루어진 각 객체들의 상호작용을 중심으로 코드를 작성하는 것
    절차 지향 프로그래밍 : 변수와 함수를 가지고 작업의 순서에 맞게 코드를 작성하는 것


    객체만들기

    Object-Literal

    · 객체를 나타내는 문자열

    · 중괄호 안에 프로티와 메소드를 나열

    • 프로퍼티 : 객체의 상태를 나타내는 변수들 / email, birthdate
    • 메소드 : 객체의 행동을 나타내는 함수들 / buy(item)

    this : 현재 객체 자체를 의미 (user)
    this.email : user의 email 프로퍼티

    const user = {
        email: 'gildong@google.com',
        birthday: '1992-03-03',
        buy(item) {
            console.log(`${this.email} buys ${item.name}`);
        },
    }
    
    const item = {
    	name: '스웨터',
    	price: 30000,
    };
    
    console.log(user.email);  // gildong@google.com
    console.log(user['email']);  // gildong@google.com
    console.log(user.birthday);  // 1992-03-03
    user.buy(item);  // gildong@google.com buys 스웨터

    Factory function

    객체를 생성하는 함수

     

    비슷한 내용의 객체를 이름만 다르게 하여 생성하는 경우

    const user1 = {
        email: 'gildong@google.com',
        birthday: '1982-03-03',
        buy(item) {
            console.log(`${this.email} buys ${item.name}`);
        },
    }
    
    const user2 = {
        email: 'dooly@google.com',
        birthday: '1002-03-03',
        buy(item) {
            console.log(`${this.email} buys ${item.name}`);
        },
    }

    이런 식으로 계속 많은 수의 객체를 생성하면 코드의 양이 기하급수적으로 늘어남.

     

    함수 안에 객체를 만들고, 해당 객체를 리턴함.

    function createUser(email, birthdate) {
    	const user = {
    		email: email,
    		birthdate: birthdate,
    		buy(item) {
    			console.log(`${this.email} buys ${item.name}`);
    		},
    	};
    	return user;
    }
    
    const item = {
    	name: '스웨터',
    	price: 30000,
    };
    
    const user1 = createUser('dooly@google.com', '1002-03-03'); 
    const user2 = createUser('gildong@google.com', '1982-03-03');  
    
    console.log(user1.email);  // dooly@google.com
    console.log(user2.email);  // gildong@google.com
    
    user1.buy(item);  // dooly@google.com buys 스웨터
    user2.buy(item);  // gildong@google.com buys 스웨터

    이 코드는 팩토리 함수인 'createUser'을 사용하여 user 객체를 생성함.

     

    프로퍼티 네임과 파라미터가 같은 경우 값 대입하는 부분 생략 가능함.

    function createUser(email, birthdate) {
    	const user = {
    		email,
    		birthdate,
    		buy(item) {
    			console.log(`${this.email} buys ${item.name}`);
    		},
    	};
    	return user;
    }

     


    Constructor function

    객체를 생성하는 용도로 사용하는 Constructor function을 정의하고, 그 안에서 this 키워드를 사용하여 생성될 객체의 프로퍼티와 메소드를 설정하는 방법

    this가 매번 생성되는 해당 객체를 가리킴
    생성되는 객체의 프로퍼티와 매소드가 매번 달라지는 것임.

    • constructor function은 호출시에는 앞에 new를 붙여야 됨. 
    • constructor function은 함수의 이름대문자로 시작함. (관습)

    function User(email, birthdate) {
    	this.email = email;
    	this.birthdate = birthdate;
    	this.buy = function (item) {
    		console.log(`${this.email} buys ${item.name}`);
    	};
    }
    
    const item = {
    	name: '스웨터',
    	price: 30000,
    };
    
    
    const user1 = new User('dooly@google.com', '1002-03-03');
    const user2 = new User('gildong@google.com', '1982-03-03');
    
    console.log(user1.email);  // dooly@google.com
    console.log(user2.email);  // gildong@google.com 
    
    console.log(user1.birthdate);  // 1002-03-03
    console.log(user2.birthdate);  // 1982-03-03
    
    user1.buy(item);  // dooly@google.com buys 스웨터
    user2.buy(item);  // dooly@google.com buys 스웨터

    Class

    ES6(2015)에서는 class라는 키워드가 추가됨.

    프로퍼티는 constructor 안에, 메소드는 바깥에 분리해서 씀.

    class User {
    	constructor(email, birthdate) {
    		this.email = email;
    		this.birthdate = birthdate;
    	}
    
    	buy(item) {
    		console.log(`${this.email} buys ${item.name}`);
    	}
    }
    
    const item = {
    	name: '스웨터',
    	price: 30000,
    };
    
    
    const user1 = new User('dooly@abc.com', '1002-02-02');
    
    console.log(user1.email);  // dooly@abc.com
    console.log(user1.birthdate);  // 1002-02-02
    
    user1.buy(item);  // dooly@abc.com buys 스웨터
    
    const user2 = new User('gildong@abc.com', '1982-02-02');
    
    console.log(user2.email);  // gildong@abc.com
    console.log(user2.birthdate);  // 1982-02-02
    
    user2.buy(item);  // gildong@abc.com buys 스웨터

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

    추상화

    · 어떤 구체적인 존재를 원하는 방향으로 간략화해서 나타내는 것
    · 클래스 설계도 추상화 과정에 해당됨.
    · 클래스와 프로퍼티, 메소드 이름을 직관적으로 알기 쉽게 지어야 함.
    · 부족하면 추가적으로 주석을 달거나 별도의 설명 문서를 작성함.


    캡슐화

    · 특정 프로퍼티에 대한 접근을 미리 정해진 메소드들을 통해서만 가능하도록 하는 것

    · 숨기고자 하는 프로퍼티 이름에 언더바(_)를 씀.

    · 사용자가 객체 내부의 프로퍼티 값을 이상하게 설정하는 것을 방지, 안정성 높임

     

    setter 메소드로 이메일 유효성 검사 예시

    class User {
    	constructor(email, birthdate) {
    		this.email = email;
    		this.birthdate = birthdate;
    	}
    
    	buy(item) {
    		console.log(`${this.email} buys ${item.name}`);
    	}
    	
    	// setter 메소드로 유효성 검사
    	set email(address) {
    		if (address.includes('@')) {
        		this._email = address;
    		} else {
    			throw new Error('invalid email address');
        	}
        }
    }
    
    const item = {
    	name: '스웨터',
    	price: 30000,
    };
    
    
    const user1 = new User('dooly@abc.com', '1002-02-02');
    user1.email = 'dooly';  // 이메일 형식이 아님.
    console.log(user1._email);

     

    getter 메소드와 setter 메소드를 작성

    class User {
    	constructor(email, birthdate) {
    		this.email = email;
    		this.birthdate = birthdate;
    	}
    
    	buy(item) {
    		console.log(`${this.email} buys ${item.name}`);
    	}
    
        // getter 메소드
        get email() {
            return this._email;
        }
    	
    	// setter 메소드로 유효성 검사
    	set email(address) {
    		if (address.includes('@')) {
        		this._email = address;
    		} else {
    			throw new Error('invalid email address');
        	}
        }
    }
    
    const item = {
    	name: '스웨터',
    	price: 30000,
    };
    
    
    const user1 = new User('dooly@abc.com', '1002-02-02');
    console.log(user1.email);  // dooly@abc.com

     

     

    getter 메소드

    · email의 속성값을 가져오기 위해 사용됨.
    · 실제 값은 '_email'이라는 프라이빗 속성에 저장되어 있으나, 외부에서는 마치 객체의 직접적인 속성인 것처럼 접근 가능함.

    console.log(user1.email);

    email 프로퍼티 값이 직접 읽혀지는게 아니라 email의 getter 메소드가 실행됨.

    setter 메소드

    · email 속성값을 설정하기 위해 사용함.
    · 유효한 이메일 주소만 저장하도록 보장함.

     

    하지만 다음과 같이 여전히 변수에 직접 접근 가능함.

    console.log(user1._email);
    user1._email = 'chris robert';

     

    Java - Private 키워드
    JavaScript - 캡슐화를 자체적으로 지원하는 문법이 없음.

     

    클로저

    · 자바스크립트에서 어떤 함수와 그 함수가 참조할 수 있는 값들로 이루어진 환경을 하나로 묶은 것

    → 이를 통해 캡슐화를 할 수 있음.

    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;
    }
    
    const user1 = createUser('chris123@google.com', '19920321');
    console.log(user1.email);


    user 객체가 생성될 때 get과 set 메서드가 _email 변수에 대한 클로저를 형성하게 됨.

    user 객체가 반환되면서 createUser 함수의 실행은 종료됨.
    하지만 get과 set메서드는 여전히 _email에 접근할 수 있음.

     

     

    클로저로 잘 캡슐화되었는지 확인하는 방법

    console.log( Object.getOwnPropertyNames(user1) );

    캡슐화된 변수 _email이 user1 객체에 존재하지 않는 것을 확인할 수 있음.


    상속

    · 상속을 적용하면 두 클래스 간의 공통된 부분은 부모클래스에 적기
    · 자식클래스는 부모 클래스와 겹치는 부분을 제외하고 적으면 됨.
    → 코드의 재사용성이 좋아짐.
    · 자식클래스로 객체를 만드려고 할 때는 반드시 그 생성자 함수 안에서 super을 호출해서 부모 클래스의 생성자 함수를 먼저 호출해줘야 함.

    // 부모 클래스
    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}`);
    	}
    }
    
    const item = {
    	name: '스웨터',
    	price: 30000,
    }
    
    const pUser1 = new PremiumUser('dooly@google.com', '1002-02-02', 'gold');
    
    console.log(pUser1.email);  // dooly@google.com
    console.log(pUser1.birthdate);  // 1002-02-02
    console.log(pUser1.level);  // gold
    pUser1.buy(item);  // dooly@google.com buys 스웨터
    pUser1.streamMusicForFree();  // Free music Streaming for dooly@google.com


    자식 클래스에 적을 것

    • extends 클래스A : 클래스A를 상속받는다
    • constructor 안에 super

    다형성

    ·  하나의 변수가 다양한 종류의 객체를 가리킬 수 있는 것

    ·  오버라이딩(overriding) : 자식 클래스에서 부모 클래스와 동일한 이름의 메소드를 정의하고, 그 내용을 다르게 채우는 것

    ·  부모 클래스와 다른 프로퍼티나 메소드 사용 가능 : super(프로퍼티), super.메소드

    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, point) {
        super(email, birthdate);  // 부모 클래스의 프로퍼티
        this.level = level;  // 자식클래스에만 추가한 프로퍼티
        this.point = point;
      }
    
      buy(item) {
        super.buy(item);  // 부모 클래스의 메소드
        this.point += item.price * 0.05;  // 자식 클래스에만 추가한 메소드
      }
    
      streamMusicForFree() {
        console.log(`Free music streaming for ${this.email}`);
      }
    }
    const item = {
      name: '스웨터',
      price: 300000,
    };
    
    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);
    });

    instance of 연산자

    ·  객체가 특정 클래스의 인스턴스인지 확인할 수 있음.

    ·  현재 변수가 가리키는 객체가 정확히 어느 클래스로 만든 객체인지 확인하고 싶은 경우

    ·  가능하면 남용하지 말고, 다형성을 활용한 코드를 작성하자.

     

    현재 user 객체가 부모 클래스에서 만든 거, pUser 객체는 자식 클래스에서 만든 것임.

    users.forEach((user) => {
      console.log(user instanceof PremiumUser);
    });

     

    users.forEach((user) => {
      console.log(user instanceof User);
    });

    자식 클래스로 만든 객체는 부모 클래스로 만든 객체로도 인정됨.


    static 프로퍼티와 static 메소드

    · 클래스 자체에 속하는 프로퍼티나 메소드로, 클래스의 인스턴스를 생성하지 않고도 바로 접근할 수 있음.

    class Math {
    	static PI = 3.14;
    	
    	static getCircleArea(radius) {
    		return Math.PI * radius * radius;
    	}
    }
    
    console.log(Math.PI);
    console.log(Math.getCircleArea(5));

    728x90