[Javascript] 객체와 클래스

SOL·2023년 8월 1일
0

Javascript

목록 보기
5/13
post-thumbnail

객체 생성과 프로퍼티 접근

{}기호를 이용하여 객체를 생성할 수 있습니다.

const food = {
  name: '피자',
  price: 5000,
};

console.log(food);
console.log(
  food1.name, // 마침표 프로퍼티 접근 연산자
  food1['price'] // 대괄호 프로퍼티 접근 연산자
);

비어있는 객체를 생성 후 프로퍼티를 추가/수정할 수 있습니다.

// 빈 객체 생성
const food2 = {};
console.log(food2);

// 프로터피 추가
food2.name = '샐러드';
food2.price = '6000';
console.log(food2);

// 프로터피 수정
food2['price'] = '6500';
console.log(food2);

표현식(어떤 값을 반환하는 코드)으로 키값을 정의할 수 있습니다. 대괄호를 사용해야 합니다.

let idx = 0;
const  obj = {
  ['key-' + ++idx]: `value-${idx}`,
  ['key-' + ++idx]: `value-${idx}`,
  ['key-' + ++idx]: `value-${idx}`,
  [idx ** idx]: 'POWER'
}

console.log(obj);

프로퍼티를 삭제할 때, delete 연산자를 사용합니다.

const person1 = {
  name: '홍길동',
  age: 20,
};

delete person1.age;
console.log(person1);

ES6 추가 문법

객체 선언 시 프로퍼티의 키와 value에 들어갈 상수명 또는 변수명이 동일할 시 단축 표현이 가능합니다.

const x = 1, y = 2;

const obj1 = { 
  x: x,
  y: y
}
console.log(obj1);

const obj2 = { x, y }
console.log(obj2);

메서드는 객체의 축약 표현으로 정의된 함수 프로퍼티를 말합니다. 따라서 이렇게 정의된 함수만 메서드라고 부릅니다.

// 일반 함수 프로퍼티 정의
const person = {
  name: '홍길동',
  state: function (formal) {
    return formal
    ? `정답입니다.`
    : `틀렸습니다.`;
  }
}
console.log(person.state(true));


// 메서드로 정의
const person = {
  name: '홍길동',
  
  state (formal) {
    return formal
    ? `정답입니다.`
  	: `틀렸습니다.`;
  }
}
console.log(person.salutate(true));



생성자 함수

같은 형식의 여러가지 객체를 만들 때, 효율적으로 객체를 생성하기 위해 생성자 함수를 이용합니다.

생성자 함수명은 대문자로 시작합니다. 생성자 함수는 new 연산자와 함께 사용되며, 생성자 함수로 만들어진 객체를 인스턴스라고 부릅니다.

생성자 함수는 객체가 아닌 함수이기 때문에 메서드 정의가 불가능합니다.

// 생성자 함수 
function People (name, no) {
  this.name = name;
  this.no = no;
  this.introduce = function () {
    return `안녕하세요, ${this.no}${this.name}입니다!`;
  }
}

// 인스턴스 생성
const people1 = new People('영수', 1);
const people2 = new People('광수', 2);
const people3 = new People('영철', 3);

console.log(people1, people1.introduce());
console.log(people2, people2.introduce());
console.log(people3, people3.introduce());

function으로 선언된 함수는 기본적으로 생성자 함수의 기능을 갖습니다. new 키워드와 같이 사용하면 그 함수는 생성자 함수가 됩니다.

function doNothing() {};

console.log(new doNothing()); // doNothing {}

객체를 반환하는 함수랑 생성자 함수의 차이

먼저, 생성자 함수로 만들어진 인스턴스들은 프로토타입을 통해서 유기적으로 계속 연결되어있습니다.

생성자 함수에 함수를 추가해보겠습니다.

People.prototype.intro2 = function () {
  return `감사합니다.`;
};

console.log(people1.intro2()); // 함수가 추가되기전에 만들어진 인스턴스지만, 새로운 함수 호출이 가능합니다.

아래 코드는 위의 People 생성자 함수처럼 똑같은 형태의 객체를 반환하는 함수입니다.

function getPeopleObj (name, no) {
  return {
    name, no,
    introduce () {
      return `안녕하세요, ${this.no}${this.name}입니다!`;
    }
  }
}

const people4 = getPeopleObj('영수', 1);

사용성이 달라보이지 않는데 왜 굳이 생성자 함수를 만들어서 사용할까요?

생성자 함수로 만든 인스턴스 people1과 그냥 객체를 반환하는 함수로 만든 people4의 로그를 찍어보면 다른점을 찾을 수 있습니다.

인스턴스는 프로토타입의 constructor 체인이 해당 생성자 함수를 포함하고있습니다.
또, instanceof 키워드를 사용하여 특정 생성자 함수에 의해 만들어졌는지의 여부 반환도 다릅니다.

console.log(people1, people1 instanceof People); // true
console.log(people4, people4 instanceof People); // false



클래스

프로토타입 기반인 자바스크립트의 문법을 타 언어의 클래스와 비슷한 문법으로 포장하여 객체 지향 프로그래밍을 지향하기 위한 문법적 설탕입니다. 클래스를 사용하면 객체를 생성하고 관리하는 방법이 더 직관적이고 간결해집니다.

클래스를 사용하여 인스턴스 생성

클래스를 정의하기 위해서 class 키워드를 사용합니다.

class People {
  constructor (name, no) {
    this.name = name;
    this.no = no;
  }
  introduce () { // 메서드
    return `안녕하세요, ${this.no}${this.name}입니다!`;
  }
}

// 인스턴스 생성
const people1 = new People('영수', 1);
const people2 = new People('광수', 2);
const people3 = new People('영철', 3);

// 클래스는 함수
console.log(typeof people1); // function

constructor 메서드

클래스를 이용해서 새로운 객체를 만들 때 호출되는 특이한 메서드입니다. 이 메서드는 객체를 초기화시켜줍니다. 따라서 메서드 안에 객체의 초기 속성을 설정할 수 있습니다.

class Person {
  constructor (name, age, married = false) {
    this.name = name;
    this.age = age;
    this.married = married;
  }
}

const person1 = new Person('영수', 30);
const person2 = new Person('광수', 31);
console.log(person1, person2);
  • 클래스에 하나만 있을 수 있습니다.
  • 메서드명은 반드시 constructor 이어야 합니다.
  • 인스턴스 초기화가 필요없으면 생략이 가능합니다.
  • 메서드 내부에서 어떠한 값을 return 하면 안됩니다. 생성자 함수처럼 암묵적으로 this를 반환해줍니다.

클래스의 메서드

클래스 내부에 생성된 함수로, 인스턴스 생성 후 메서드를 호출합니다.

class Dog {
  bark () {
    return '멍멍';
  }
}
const dog1 = new Dog();
console.log(dog1.bark()); 

필드

constructor 밖에서 this 없이 인스턴스의 프로퍼티를 정의합니다.

// 필드값이 지정되어 있으므로 constructor 메서드 필요없음
class Slime {
  hp = 50;
  op = 4;
  attack (enemy) {
    enemy.hp -= this.op;
    this.hp += this.op/4;
  }
}

const slime1 = new Slime();
const slime2 = new Slime();

slime1.attack(slime2);

정적 멤버 (static 변수와 메서드)

정적 멤버는 인스턴스 멤버와 달리 클래스 자체에 속해 있습니다. 즉 인스턴스가 아닌 클래스 레벨에서 존재하기 때문에, 클래스가 정의될 때 한 번만 메모리에 할당됩니다. 이 정적 멤버들은 클래스의 모든 인스턴스에서 공유되기 때문에 인스턴스가 생성되어도 별도의 메모리 공간을 차지하지 않습니다.

그래서 인스턴스 생성 없이 클래스 차원에서 메서드 호출이 가능하며, 인스턴스를 통해서는 접근할 수 없습니다.

class Company {

  // 정적 변수와 메서드
  static brand = '애플';
  static contact () {
    return `${this.brand}입니다. 무엇을 도와드릴까요?`; // 정적 메서드에서는 정적 필드만 사용가능합니다.
  }

  constructor (name, price) {
    this.name = name;
    this.price = price;
  }
  introduce () {
    return `새로나온 ${this.name}의 가격은 ${this.price}입니다!`;
  }
}

console.log(Company.contact()); //인스턴스 생성 없이 클래스 차원에서 호출 가능

static 변수와 메서드는 클래스의 인스턴스가 아닌 클래스 자체에 속해 있으며, 인스턴스 간에 공유되어야 하는 데이터나 기능을 관리하는 데 유용합니다. 이를 통해 공통된 기능 제공, 데이터 공유, 메모리 절약, 클래스 수준의 설정 관리 등을 할 수 있습니다.

static 메서드는 유틸리티 함수나 클래스와 관련된 전역적인 동작을 정의할 때 특히 유용합니다.



생성자 함수와 클래스의 차이

문법적 차이

class

  • ES6(ECMAScript 2015)에서 도입된 문법입니다.
  • class 키워드를 사용해 클래스를 정의합니다.
  • 클래스 내부에서 constructor 메서드를 사용해 객체의 초기값을 설정합니다.
  • 클래스를 사용할 때는 new 키워드를 사용해 객체를 생성합니다.
  • 클래스 문법은 좀 더 직관적이고, 객체 지향 프로그래밍을 처음 배우는 사람들에게 친숙해.
  • constructor, getters, setters, 정적 메서드 등 여러 가지 기능이 내장되어 있어.
  • 클래스를 선언할 때 호이스팅(hoisting)이 되지 않아, 선언하기 전에 사용할 수 없어.

생성자 함수

  • ES6 이전부터 사용되던 객체 생성 방식이야.
  • 일반 함수처럼 정의하고, 함수 이름의 첫 글자를 대문자로 쓰는 것이 관례야.
  • 함수 내부에서 this를 사용해 객체의 속성을 정의하고, new 키워드로 객체를 생성해.
  • 생성자 함수는 자바스크립트의 원래 동작 방식에 기반해, 더 유연하지만 조금 더 복잡해.
  • 함수 자체가 호이스팅되기 때문에, 선언하기 전에 사용할 수 있어.

프로토타입 기반 상속

class

  • 클래스에서는 메서드를 constructor 외부에 정의하고, 자동으로 프로토타입에 추가됩니다.
  • 상속받는 클래스는 extends 키워드를 사용하고, 부모 클래스의 생성자를 호출할 때 super()를 사용합니다.
class Employee extends Person {
    constructor(name, age, jobTitle) {
        super(name, age);
        this.jobTitle = jobTitle;
    }
}

생성자 함수

  • 생성자 함수에서 메서드를 프로토타입에 수동으로 추가해야 합니다.
  • 상속을 구현하려면 Object.create()나 call, apply 같은 방법을 사용해야 하고, 직접 프로토타입 체인을 설정해야 합니다.
function Employee(name, age, jobTitle) {
    Person.call(this, name, age);
    this.jobTitle = jobTitle;
}

Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;

엄격 모드

class

  • 클래스 내부에서 정의된 모든 코드는 자동으로 "엄격 모드(strict mode)"로 실행됩니다. 이는 오류를 예방하는 데 도움이 됩니다.

생성자 함수

  • 생성자 함수는 명시적으로 "엄격 모드"를 선언해야 합니다. 그렇지 않으면 일반적인 함수처럼 동작합니다.

정적 메서드와 속성

class

  • static 키워드를 사용해 정적 메서드나 속성을 쉽게 정의할 수 있습니다. 이는 클래스 자체에 속하는 메서드나 속성입니다. 따라서 인스턴스를 생성하지 않아도 사용가능합니다.
class MathUtil {
    static add(a, b) {
        return a + b;
    }
}

console.log(MathUtil.add(2, 3)); // 5

생성자 함수

  • 생성자 함수에서는 정적 메서드를 직접 함수에 추가해야 합니다.
function MathUtil() {}

MathUtil.add = function(a, b) {
    return a + b;
}

console.log(MathUtil.add(2, 3)); // 5

클래스와 생성자 함수는 모두 객체를 생성하는 방법이지만, 클래스는 객체 지향 프로그래밍 패턴을 더 직관적으로 지원하고, 다양한 기능이 내장되어 있습니다.. 반면, 생성자 함수는 자바스크립트의 오래된 방식으로, 더 유연하지만 코드가 복잡해질 수 있습니다.

클래스가 더 현대적이고, 대부분의 새로운 코드에서는 클래스를 사용하는 것이 추천됩니다.



접근자 프로퍼티

접근자 프로퍼티는 getter, setter 함수라고도 부르며 함수명 앞에 getset을 붙입니다. 스스로는 값을 갖지 않고 다른 프로퍼티의 값을 읽거나 저장할 때 사용합니다.

생김새는 함수처럼 지정되었지만 프로퍼티처럼 사용합니다.

const person1 = {
  age: 17, // 데이터 프로퍼티
    
  // 접근자 프로퍼티
  get koreanAge () {
    return this.age + 1;
  },
  
  // 접근자 프로퍼티
  set koreanAge (krAge) {
    this.age = krAge - 1;
  }
}

console.log(person1,  person1.koreanAge); // person1.koreanAge() 이렇게 사용하지 않음.

클래스에서도 사용 가능합니다.

class Company {
  constructor (name, price) {
    this.name = name;
    this.price = price;
  }
  
  get newItems(){
  	return `${새로나온 신상 ${this.name}의 가격은 ${this.price}입니다!}`;
  }

  set newItemsPrice(newPrice){
	if( typeof newPrice !== 'number') return;
	if(newPrice <= 0) return;
	this.price = newPrice;
  }
	
}


은닉

자바스크립트의 필드는 기본적으로 public으로 은닉되지 않습니다.

데이터 은닉: private 필드와 메서드를 사용하면 클래스 외부에서 객체의 내부 상태를 직접 수정하지 못하도록 보호할 수 있습니다. 이를 통해 객체의 무결성을 유지할 수 있습니다.

캡슐화: 객체 지향 프로그래밍의 중요한 개념 중 하나인 캡슐화를 구현하는 데 도움이 됩니다. 내부 구현을 숨기고, 외부에 노출할 필요가 없는 부분을 은닉할 수 있습니다.

유지보수: 클래스 내부의 동작을 자유롭게 변경할 수 있어, 유지보수가 용이해집니다. 외부에서 접근하지 못하므로, 클래스의 내부 구조를 바꿔도 외부 코드에 영향을 미치지 않습니다.

private

private한 필드명과 메서드명 앞에는 #기호를 붙여 선언합니다. 이 필드와 메서드는 클래스 외부에서 접근할 수 없으며, 오직 클래스 내부에서만 접근하고 호출 가능합니다.

class Person {
    // private 필드
    #name;
    #age;

    constructor(name, age) {
        this.#name = name;
        this.#age = age;
    }

    // public 메서드
    getDetails() {
        return `Name: ${this.#name}, Age: ${this.#age}`;
    }

    // private 메서드
    #calculateBirthYear() {
        const currentYear = new Date().getFullYear();
        return currentYear - this.#age;
    }

    getBirthYear() {
        return this.#calculateBirthYear();
    }
}

const person = new Person('Alice', 30);

console.log(person.getDetails()); // "Name: Alice, Age: 30"

// 외부에서 private 필드에 접근하려고 하면 오류 발생
console.log(person.#name); // SyntaxError: Private field '#name' must be declared in an enclosing class

console.log(person.getBirthYear()); // 올바르게 계산된 출생 연도 반환


상속

상속(Inheritance)은 한 클래스가 다른 클래스의 속성과 메서드를 물려받아 사용하는 개념입니다. 이를 통해 코드의 재사용성을 높이고, 객체 지향 프로그래밍(OOP)에서 중요한 개념인 다형성을 구현할 수 있습니다.

상속을 사용하면, 부모 클래스의 기능을 자식 클래스가 물려받아 사용할 수 있습니다. 자식 클래스는 부모 클래스의 모든 속성과 메서드를 그대로 사용할 수 있으며, 필요에 따라 새로운 속성이나 메서드를 추가하거나, 부모 클래스의 메서드를 재정의(오버라이딩)할 수도 있습니다.

클래스 상속 구현

extends키워드를 사용합니다.

class Animal {
    constructor(name) {
        this.name = name;
    }

    speak() {
        console.log(`${this.name} makes a sound.`);
    }
}

class Dog extends Animal {
    constructor(name, breed) {
        super(name); // 부모 클래스의 생성자를 호출
        this.breed = breed;
    }

    // 부모 클래스의 메서드를 재정의 (오버라이딩)
    speak() {
        console.log(`${this.name} barks.`);
    }
}

const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.speak(); // "Buddy barks."

super

super는 부모 클래스의 생성자(constructor)나 메서드를 호출할 때 사용됩니다.
자식 클래스에서 부모 클래스의 생성자를 호출할 때는 super()를 사용해야 합니다. super()를 호출하지 않으면 this 키워드를 사용할 수 없기 때문에, 자식 클래스에서 부모 클래스를 상속받은 속성을 초기화하려면 반드시 super()를 호출해야 합니다.

메서드 오버라이딩 (Method Overriding)

자식 클래스는 부모 클래스의 메서드를 재정의(오버라이딩)할 수 있습니다. 자식 클래스에서 동일한 이름의 메서드를 정의하면, 부모 클래스의 메서드를 덮어쓰게 됩니다.
만약 자식 클래스에서 부모 클래스의 메서드를 호출하고 싶다면, super.메서드명()을 사용하면 됩니다.



객체의 스프레드와 디스트럭쳐링

객체 스프레드

...기호를 이용해서 객체를 펼칠 수 있습니다. 특정 객체의 프로퍼티를 포함하는 새로운 객체 생성에 유용하게 사용됩니다.중복되는 프로퍼티는 뒤의 것이 덮어씌어집니다.

const class1 = {
  a: 1, b: 'A', c: true
};
const class2 = {
  d: { x: 10, y: 100 }, e: [1, 2, 3]
};
const class3 = {
  ...class1, z: 0
}
const class4 = {
  ...class2, ...class3, ...class2.d
}

주의할 점은, 얕은 복사를 하므로 원본 객체를 수정할 시 복제한 객체의 참조값도 같이 변합니다. 원시값만 있는 객체만 값에 의한 복사를 합니다.

const class1 = {
  x: 1,
  y: { a: 2 },
  z: [3, 4]
};

const class2 = { ...class1 };
class1.x++;
class1.y.a++;
class1.z[0]++;

console.log(class1); // {x:2, y: {a:3}, z:[4,4] }
console.log(class2); // {x:1, y: {a:3}, z:[4,4] }

디스트럭쳐링

객체안의 필요한 프로퍼티를 짧은 코드로 변수(또는 상수)에 할당시킬 수 있습니다.

const obj1 = {
  x: 1, y: 2, z: 3
};

const {x, y, z} = obj1;

console.log(x, y, z);

profile
개발 개념 정리

0개의 댓글

관련 채용 정보