-
5. JavaScript 객체와 클래스JavaScript 2023. 3. 29. 23:41
I. 객체의 기본 사용법들
1. 객체 생성과 프로퍼티 접근
const food1 = { foodName: '햄버거', foodPrice: 5000, foodVegan: false }; console.log(food1); // {name: '햄버거', price: 5000, vegan: false} console.log( food1.foodName, // 💡 마침표 프로퍼티 접근 연산자 food1['foodPrice'] // 💡 대괄호 프로퍼티 접근 연산자 );
✨식별자 명명 규칙에 벗어나는 키 이름 사용시
변수명 등으로 사용할 수 없는 이름의 키인 경우,
대괄호 프로퍼티 접근 연산자로만 가능
const obj = { 1: '하나', // 숫자도 객체의 키로는 사용 가능 'ab-cd': 'ABCD', // 문자 포함 시 키도 따옴표로 감싸야 함 's p a c e': 'Space' } // 대괄호 프로퍼티 접근 연산자로만 가능 console.log( obj[1], obj['ab-cd'], obj['s p a c e'] ); // ⚠️ 오류 발생 // console.log( // obj.1, // obj.ab-cd, // obj.s p a c e // );
✨표현식으로 키값 정의하기
- 대괄호 [ ] 사용
const objKey = { numberOne: 1, numberTwo: 2 }; const arrKey = [1, 2, 3]; const obj = { [objKey]: '객체를 키값으로', [arrKey]: '배열을 키값으로' } console.log( obj[objKey], obj[arrKey] // 객체를 키값으로 배열을 키값으로 );
단, 실제로 해당 객체나 배열의 내용이나 참조값이 키가 되는 것이 아니다.
객체와 배열이 문자열로 치환되어 키가 되는 것이다.
2. 프로퍼티 삭제 - delete 연산자
const person1 = { hisName: '홍길동', hisAge: 24, hisSchool: '한국대', hisMajor: '컴퓨터공학' }; console.log(person1);
delete person1.hisAge; console.log(person1);
위와 같이 delete 연산자를 사용하여,
해당 객체의 프로퍼티를 삭제할 수 있다.
이렇게 삭제하면 프로퍼티명 뿐만이 아니라 그에 맞는 값도 삭제가 된다.
✨키의 동적 사용
const product1 = { productName: '노트북', productColor: 'gray', productPrice: 800000 } function addModifyProperty (obj, key, value) { // obj.key = value; // ⚠️ 의도와 다른 작업 수행 obj[key] = value; }
addModifyProperty (product1, 'productInch', 16); console.log(product1);
위의 코드는,
addModifyProperty 라는 함수를 정의해두고,
이 함수를 이용하여 프로퍼티와 값을 추가해준다.
이런식으로 키의 동적 사용이 가능하다.
3. ES6 추가 문법
- 객체 선언 시 프로퍼티 키와 대입할 상수/변수명이 동일할 시 단축 표현
const numberOne = 1, numberTwo = 2; const obj1 = { numberOne: numberOne, numberTwo: numberTwo } console.log(obj1); const obj2 = { numberOne, numberTwo } console.log(obj2);
- 메서드(method) : 객체에 축약표현으로 정의된 함수 프로퍼티
// 일반 함수 프로퍼티 정의 const person = { personName: '홍길동', salutate: function (formal) { return formal ? `안녕하십니까, ${this.personName}입니다.` : `안녕하세요, ${this.personName}이에요.`; } } console.log(person.salutate(true));
// ⭐️ 메서드 정의 const person = { personName: '홍길동', salutate (formal) { return formal ? `안녕하십니까, ${this.personName}입니다.` : `안녕하세요, ${this.personName}이에요.`; } } console.log(person.salutate(true));
II. 생성자 함수
1. 생성자 함수의 필요성
// 홍치킨의 체인점을 나타내는 객체들 const chainOne = { marketName: '판교', marketNo: 3, introduce () { return `안녕하세요, ${this.marketNo}호 ${this.marketName}점입니다!`; } }; const chainTwo = { marketName: '강남', marketNo: 17, introduce () { return `안녕하세요, ${this.marketNo}호 ${this.marketName}점입니다!`; } }; const chainThree = { marketName: '제주', marketNo: 24, introduce () { return `안녕하세요, ${this.marketNo}호 ${this.marketName}점입니다!`; } };
위와 같이, 같은 형식의 객체들을 다수 만들어야 할 때,
아래의 예시처럼 생성자 함수로 객체를 만들면 된다.
// 생성자 함수 function HongChicken (marketName, marketNo) { this.marketName = marketName; this.marketNo = marketNo; this.introduce = function () { return `안녕하세요, ${this.marketNo}호 ${this.marketName}점입니다!`; } } // 인스턴스 생성 const chainOne = new HongChicken('판교', 3); const chainTwo = new HongChicken('강남', 17); const chainThree = new HongChicken('제주', 24);
- 생성자 함수명은 일반적으로 대문자로 시작 (파스칼 케이스)
- 생성자 함수로 만들어진 객체를 인스턴스(instance)라 부름
- this.~ 로 생성될 인스턴스의 프로퍼티들 정의
- 생성자 함수는 new 연산자와 함께 사용
- 암묵적으로 this 반환
- 생성자 함수에서는 메서드 정의 불가
✨또 호출 시, new 를 붙이지 않으면 undefined를 반환한다.
function HongChicken (marketName, marketNo) { this.marketName = marketName; this.marketNo = marketNo; this.introduce = function () { return `안녕하세요, ${this.marketNo}호 ${this.marketName}점입니다!`; } } console.log(HongChicken('홍대', 30)); // undefined console.log(new HongChicken('홍대', 30));
2. 생성자 함수로 만들어진 객체
- 프로토타입(prototype) - 자바 스크립트 객체지향의 중심
function HongChicken (marketName, marketNo) { this.marketName = marketName; this.marketNo = marketNo; this.introduce = function () { return `안녕하세요, ${this.marketNo}호 ${this.marketName}점입니다!`; } } const chainOne = new HongChicken('판교', 3); console.log(chainOne);
// 본사에서 새 업무를 추가 // 프로토타입: 본사에서 배포하는 메뉴얼이라고 이해 HongChicken.prototype.introEng = function () { return `Welcome to Hong Chicken at ${this.name}!`; }; console.log(chainOne.introEng()); console.log(new HongChicken('강남', 17).introEng());
두번째 코드를 보면, HongChicken 함수에 프로토타입으로 introEng 기능을 추가했다.
무슨 말인가 하면,
HongChicken 의 introduce 프로퍼티와, introEng 는
프로토타입으로 연결되어 있으므로 유기적으로 그 기능을 사용할 수 있다.
✨타 방식으로 만든 객체와의 차이
- 객체 자체의 로그도 상세가 다르다는 것에 유의해야 함 (앞에 생성자 함수명이 붙음)
- instanceof : 객체가 특정 생성자 함수에 의해 만들어졌는지 여부 반환
- 프로토타입의 constructor의 체인이 해당 생성자 함수 포함하는지 여부
III. 클래스
class HongChicken { constructor (marketName, marketNo) { this.marketName = marketName; this.marketNo = marketNo; } introduce () { // 💡 메서드 return `안녕하세요, ${this.marketNo}호 ${this.marketName}점입니다!`; } } const chain1 = new HongChicken('판교', 3); const chain2 = new HongChicken('강남', 17); const chain3 = new HongChicken('제주', 24);
Syntactic Sugar : 문법을 보다 읽기 쉽게 만드는 것
자바와 같은 타 언어에 익숙한 사람들을 위해 생성자 함수, 프로토타입 기반인 자바스크립트 문법으로,
타 언어의 클래스와 비슷한 문법으로 포장한 것을 말한다.
⚠ 하지만, 클래스와 생성자 함수의 동작이 동일하지는 않는다.
① 클래스 - new 없이 사용하면 오류
② 생성자 함수 - 오류가 아닌 undefined 반환
2. constructor 메서드
class Person { constructor (personName, personAge, personMarried = false) { this.personName = personName; this.personAge = personAge; this.personMarried = personMarried; } } const person1 = new Person('박영희', 30, true); const person2 = new Person('오동수', 18); console.log(person1, person2);
- 인스턴스 생성시 인자를 받아 프로퍼티를 초기화함
- 클래스에 하나만 있을 수 있음 (초과 시 오류)
- 다른 메서드 이름을 쓸 수 없음
- 기본값 사용 가능
- 필요없을 시(인자가 없을 때 등등...) 생략 가능
- 값을 반환하지 말 것! (생성자 함수와 마찬가지로 암묵적으로 this 반환
3. 클래스의 메서드
class Dog { bark () { return '멍멍'; } } const badugi = new Dog(); console.log(badugi, badugi.bark()); // Dog {} '멍멍'
4. 필드 - field
// 필드값이 지정되어 있으므로 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(); console.log(slime1, slime2); slime1.attack(slime2); console.log(slime1, slime2);
- constructor 밖에서, this.~ 없이 인스턴스의 프로퍼티 정의
5. 정적(static) 필드와 메서드
class HongChicken { // 정적 변수와 메서드 static brand = '홍치킨'; static contact () { return `${this.brand}입니다. 무엇을 도와드릴까요?`; } constructor (name, no) { this.marketName = marketName; this.marketNo = marketNo; } introduce () { return `안녕하세요, ${this.marketNo}호 ${this.marketName}점입니다!`; } } console.log(HongChicken); console.log(HongChicken.contact());
- 인스턴스의 수와 관계없이 메모리 한 곳만 차지
- 인스턴스 없이 클래스 차원에서 호출
- 정적 메서드에서는 정적 필드만 사용 가능
마지막으로,
클래스는 함수이자, 일급 객체 이므로,
typeof 시 function으로 구분되며
다른 곳에 할당도 가능하다.
IV. 접근자 프로퍼티와 은닉
1. 접근자 프로퍼티
- getter, setter 함수라고도 부름
- 스스로는 값을 갖지 않음 - 다른 프로퍼티의 값을 읽거나 저장할 때 사용
- get, set을 앞에 붙임
- 클래스에서도 사용 가능
const person1 = { age: 17, get koreanAge () { return this.age + 1; }, set koreanAge (krAge) { this.age = krAge - 1; } } console.log(person1, person1.koreanAge); // {age: 17} 18 person1.koreanAge = 20; console.log(person1, person1.koreanAge); // {age: 19} 20
getter
- 반드시 값을 반환해야 함
- 특정 프로퍼티(들)를 원하는 방식으로 가공하여 내보낼 때 사용
setter
- setter는 하나의 인자를 받음
- 특정 프로퍼티에 값이 저장되는 방식을 조작하거나 제한하는데 사용
⚠️필드 이름과 setter의 이름이 같을 때 오류 발생!!
해결책
- setter와는 다른 필드명을 사용
- constructor의 marketNo는 setter를 가리키게 하고,
setter와 getter의 this.marketNo => this._marketNo와 같이 이름을 바꿔줌
2. 캡슐화
- 객체지향의 주요 요소 중 하나 - 객체 내부의 값을 감추는 것
- 인스턴스의 프로퍼티 값을 함부로 열람하거나 수정하지 못하도록 함
- 자바스크립트의 필드는 기본적으로 public (은닉x)
✨private 필드를 통한 은닉
class Employee { #name = ''; #age = 0; constructor (name, age) { this.#name = name; this.#age = age; } } const emp1 = new Employee('홍준우', 26);
위와 같이 필드명 앞에 #을 붙여서 사용한다.
이 private 필드들은 생성자뿐만 아니라 클래스 바로 안쪽에 정의해야 하고,
그로 인해 클래스 내에서는 private 필드에 접근이 가능하다.
private 필드들은 생성자(constructor), 접근자 프로퍼티, 기타 함수에서 접근이 가능하다.
V. 상속
서로 다른 클래스나 생성자 함수가 같은 속성들을 공유할 때,
이들의 관계를 정의하여 코드의 중복을 줄이고 효율을 높인다.
1. 클래스의 상속 문법
class Bird { wings = 2; } class Eagle extends Bird { claws = 2; } class Penguin extends Bird { swim () { console.log('수영중...'); } } class EmperorPenguin extends Penguin { size = 'XXXL'; } const birdy = new Bird(); const eaglee = new Eagle(); const pengu = new Penguin(); const pengdol = new EmperorPenguin(); console.log(birdy, eaglee, pengu, pengdol); for (const i of [ [ '1.', birdy instanceof Bird ], // birdy는 Bird의 인스턴스인가? true [ '2.', eaglee instanceof Bird ], // eaglee는 Bird의 인스턴스인가? true [ '3.', eaglee instanceof Eagle ], // eaglee는 Eagle의 인스턴스인가? true [ '4.', pengdol instanceof Penguin ], // pengdol는 Penguin의 인스턴스인가? true [ '5.', pengdol instanceof Bird ], // pengdol는 Bird의 인스턴스인가? true [ '6.', birdy instanceof Eagle ] // birdy는 Eagle의 인스턴스인가? false ]) { console.log(i[0], i[1]); } pengu.swim(); // 수영중... pengdol.swim(); // 수영중... eaglee.swim(); // 오류
- 클래스에서는 extends로 상속관계 정의
- 자식 클래스에서 또 다른 클래스가 상속받을 수 있음
- 자식 클래스는 부모 클래스의 속성을 기본적으로 가져옴
- 자식 클래스의 인스턴스는 부모 클래스의 인스턴스로 인식됨
✨[[Prototype]] 으로 상속관계 살펴볼것 - Object
2. 오버라이딩 (overriding)
자식 클래스에서 부모로부터 물려받은 속성이나 기능을 덮어씀
class Bird { wings = 2; canFly = true; travel () { console.log('비행중...') } } class Eagle extends Bird { claws = 2; } class Penguin extends Bird { canFly = false; travel () { console.log('수영중...') } } const eaglee = new Eagle(); const pengu = new Penguin(); console.log(eaglee); eaglee.travel(); console.log(pengu); pengu.travel();
위 코드에서 Eagle과 Penguin 클래스는 둘 다 Bird 클래스를 상속받았다.
하지만 각각 부모의 필드를 재정의하였고,
그 결과 부모의 필드를 덮어썼다.
3. super
부모 클래스의 constructor 또는 메서드 호출
① 부모 클래스
class HongChicken { marketNo = 0; menu = { '후라이드': 10000, '양념치킨': 12000 }; constructor (marketName, marketNo) { this.marketName = marketName; if (marketNo) this.marketNo = marketNo; } introduce () { return `안녕하세요, ${this.marketNo}호 ${this.marketName}점입니다!`; } order (marketName) { return `${this.menu[marketName]}원입니다.` } }
② 자식 클래스
class JunWooChicken extends HongChicken { #word = ''; constructor (marketName, marketNo, word) { super(marketName, marketNo); this.#word = word; } introWithConcept () { return super.introduce() + ' ' + this.#word; } order (marketName) { return super.order(marketName) + ' ' + this.#word; } } const pikaChain = new JunWooChicken('안양', 50, '감사합니당~'); console.log(pikaChain.introWithConcept()); console.log(pikaChain.order('후라이드'));
- super는 다른 클래스에서 상속받은 클래스에서만 사용 가능
- 자식 클래스의 constructor 내에서는 부모 클래스의 constructor를 가리킴
- 자식 클래스의 메서드 내에서는 부모 클래스를 가리킴
- 부모 클래스의 constructor나 메서드에 추가적인 동작을 넣기 위해 사용
VI. 객체의 스프레드와 디스트럭쳐링
1. 스프레드 (spread)
- 기본 문법
const class1 = { typeNumber: 1, typeString: 'A', typeBoolean: true }; const class2 = { ...class1 }; // 아래의 참조복사 코드와 다름! // const class2 = class1; console.log(class2);
- 특정 객체의 프로퍼티를 포함하는 다른 객체 생성에 유용
const class1 = { typeNumber: 1, typeString: 'A', typeBoolean: true }; const class2 = { typeObject: { x: 10, y: 100 }, typeArray: [1, 2, 3] }; const class3 = { ...class1, z: 0 } const class4 = { ...class2, ...class3, ...class2.typeObject }
- 중복되는 프로퍼티는 뒤의 것이 덮어씀
✨ 복사의 깊이
① 해당 객체 바로 안쪽의 원시값은 복제하지만 참조값은 같은 값을 가리킴
② 원시값만 있는 객체만 얕은 복사 (참조값은 깊은 복사)
2. 디스트럭쳐링 (destructuring)
- 기본 문법
// 기존 코드 const obj1 = { x: 1, y: 2, z: 3 }; const x = obj1.x; const y = obj1.y; const z = obj1.z; console.log(x, y, z); // 디스트럭쳐링으로 간략화 const obj1 = { x: 1, y: 2, z: 3 }; const {x, y, z} = obj1; console.log(x, y, z);
✨ 일부만 가져오는 것도 가능
'JavaScript' 카테고리의 다른 글
6-2. JavaScript 빌트인 전역 프로퍼티와 함수 (0) 2023.03.30 6-1. JavaScript 전역 객체와 표준 빌트인 객체 (0) 2023.03.30 4. JavaScript 함수의 모든 것 (0) 2023.03.27 3. JavaScript 제어문(if/else문, for문, switch/case문, while문) (0) 2023.03.22 2. JavaScript 자료형과 연산자 (0) 2023.03.22