자바스크립트 - Object, Constructor, Prototype, Class

여름노래불러줘·2020년 6월 28일
1

객체 (Object)

어떤 사물에 대한 데이터를 순서 없이 그룹화 한 것.

빈 객체 생성하기

객체 리터럴로 생성하는 방법

let car = {};

console.log(car); // {}

Object 생성자로 만드는 방법

let car = new Object();

console.log(car); // {}

객체에 데이터 추가하기

객체 리터럴을 통해 선언과 동시에 데이터 추가

let car = {
    color: 'red'
};

console.log(car.color); // red

객체 외부에 선언

let car = {};
car.color = 'red';
console.log(car); // red

선언과 동시에 데이터를 할당하는게 더 직관적이고 좋아보이지만,
'어떤 방법이 더 좋다' 할 수 없다.
그냥 객체와 데이터를 할당하는데 두 가지 방법이 있을 뿐

다수의 객체를 만들고 싶을 때

let casper = {
  	color: 'green'
};
let avante = {
	color: 'red'
};
let sonata = {
  	color: 'yellow'
};
let grandeur = {
  	color: 'black'
};

이런 방법도 있지만 모든 객체가 공통으로 가지고 있는 color 속성 때문에 코드에 중복이 많이 생겼으며 코드 양도 많아지는 문제가 있다.
이를 해결하기 위해서 '생성자'를 사용할 수 있다.

생성자 (Constructor)

자바같은 언어에서는 클래스라는 개념이 있어, 부모 클래스를 자식에게 상속시켜 이런 문제를 해결할 수 있다.

그러나 자바스크립트에는 클래스가 없기 때문에 함수가 이 역할을 대신한다.

생성자 함수 생성

빈 함수 Car 을 만들었다.

function Car() {
}

생성자로 사용하는 함수는 보통 함수 이름의 첫 글자는 대문자로 작성한다. 반드시 그럴 필요는 없지만, 함수 이름의 첫 글자가 대문자라면 함수 내용을 자세히 살펴보지 않아도 "이 함수는 생성자 함수구나" 라는 것을 알 수 있게 하는 일종의 관행이다.

생성자 함수에 대해 알아보기

console.log(Car()) // undefined

Car 함수에서 아무 것도 반환하지 않고 있기 때문에 이 함수는 undefined 를 반환한다.

이번에는 다음과 같이 작성해보고 변수 avante 에 어떤 값이 들어 있는지 확인해보자.

// new 키워드를 사용해 Fruit 함수를 복사해 avante 변수에 저장

let avante = new Car();
console.log(avante) // Car { }

Car 함수에선 아무것도 반환하지 않고 있지만, new 키워드를 사용해 생성한 avante 변수에는 빈 객체가 담겨있다. 이는 new 키워드를 사용해 복사한 함수는 객체를 반환하는 것을 의미한다.

생성자 함수 제대로 사용해보기

이를 이용하면 객체를 쉽고 빠르게 만들어낼 수 있다.
Car 함수의 내용을 다음과 같이 바꾸고 new 키워드를 사용해 새로운 변수에 할당해보자.

// 생성자 함수 Car는 파라미터 type, color 를 입력받는다. 
function Car(type, color) {
  	this.type = type;
	this.color = color;
}

let avante = new Car('sedan', 'red'); 
let casper = new Car('suv', 'green'); 

console.log(avante, casper);

// 변수 출력
Car { type: 'sedan', color: 'red' } 
Car { type: 'sedan', color: 'green' }

계속 이렇게 함수를 복사할 때 생기는 문제

이제 Car 함수에는 차량을 만드는 회사 이름인 brand 속성과
차량 제조사의 국가인 from 속성이 추가된다.

// 생성자 함수 
function Car(type, color) {
  	this.type = type;
	this.color = color;
  	this.brand = 'hyundai';
  	this.from = 'Republic of Korea'
}

// 객체 생성
let avante = new Car('sedan', 'red');
let casper = new Car('suv', 'green');
let sonata = new Car('sedan', 'white');
let grandeur = new Car('sedan', 'black');

// 객체 내옹 출력
console.log(avante, casper, sonata, grandeur);

Car { 
	type: 'sedan',
    color: 'red',
    brand: 'hyundai', // 중복되는 속성
    from: 'Republic of Korea' // 중복되는 속성
}

Car {
  	type: 'suv',
    color: 'green',
    brand: 'hyundai', // 중복되는 속성
    from: 'Republic of Korea' // 중복되는 속성
}

Car {
  	type: 'sedan',
    color: 'white',
    brand: 'hyundai', // 중복되는 속성
    from: 'Republic of Korea' // 중복되는 속성
}

Car {
  	type: 'sedan',
    color: 'black',
    brand: 'hyundai', // 중복되는 속성
    from: 'Republic of Korea' // 중복되는 속성
}

color 는 모델별로 차이가 있을 수 있지만,
brandfrom 은 모두 동일한 값을 가지고 있으며,
type 또한 sedan 혹은 suv 둘 중 하나이다.

수 많은 객체를 만들게 되면 컴퓨터 메모리 안에는 복사해낸 모든 객체가 중복되는 값을 가지기 때문에 비효율적이다.

프로토타입 (Prototype)

모든 객체가 공통으로 가지고 있는 속성이나 메소드에는 프로토타입을 사용하여 낭비되는 메모리를 줄일 수 있다. 프로토타입은 부모 객체에 선언한다.

Car 함수에서 중복되는 속성을 Prototype 형태로 바꾸기

앞서 만들어낸 객체들은 Car 함수를 복사한다.
Car 함수 내부에서 type, brad, from 속성을 제거하고,
외부에 프로토타입으로 선언한다.

이제 각 객체를 출력해보자.


function Car(type, color) {
  	this.color = color;
}

Car.prototype.type = type => type === 'sedan' ? 'sedan' : 'suv';
Car.prototype.brand = 'hyundai';
Car.prototype.from = 'Republic of Korea';

let avante = new Car('sedan', 'red');
let casper = new Car('suv', 'green');
let sonata = new Car('sedan', 'white');
let grandeur = new Car('sedan', 'black');

console.log(avante, casper, sonata, granduer)

// 출력 결과
Car { color: 'red' } 
Car { color: 'green' } 
Car { color: 'white' } 
Car { color: 'black' }

각 객체들은 color 를 제외한 어떤 내용도 담고 있지 않다. 그러나 아래와 같이 호출을 해보면 undefined 가 아닌 정상적인 값이 출력되는 것을 확인할 수 있다. 해당속성을 각 객체가 아닌 부모 Car 에서 참조하기 때문이다.

console.log(avante.brand, casper.brand, sonata.type(), grandeur.from)

// hyundai, hyundai, sedan, Republic of Korea

특정 객체에 변경이 필요한 경우

제네시스는 현대 브랜드 로고 대신 제네시스라는 로고를 달고 판매되는 플래그십 브랜드이다.

genesis 라는 객체를 하나 더 만드려고 한다.
brand 값이 hyundai 여도 나쁘진 않지만 genesis 면 더 좋을 거 같다.

그러나 Car 함수의 Prototype 에 이미 정의된 brand 속성이 hyundai 라는 값으로 초기화되고 있다.

이럴땐 해당 객체에 같은 이름의 속성과 값을 추가해 부모가 아닌 객체에서 직접 brand 값을 참조하도록 할 수 있다.

let genesis = new Car('sedan', 'blue')
genesis.brand = 'genesis'

console.log(genesis)
// Car { color: 'white', brand: 'genesis' }

자바스크립트 런타임이 메소드를 실행할 때 거치는 과정

프로토타입은 자바에서 사용하는 클래스, 상속과 같은 개념으로
메소드나 속성값을 사용할 때 부모 객체를 참조해 자식 스코프에서 실행할 수 있게 해준다.

클래스 (Class)

클래스는 자바스크립트에서 Prototype 과 같이 객체를 복사하는 또 다른 방법이다.

자바의 클래스에 익숙한 사람들을 위해 자바스크립트에 만들어진 문법적 설탕(Syntatic sugar)일뿐 내부가 프로토타입과 완전히 동일하게 작동하는 것으로 알고 있었는데, 이번에 다시 한 번 정리를 해보니 꼭 그렇지만은 않은 것 같다.

무엇이 다른지에 대해 궁금하다면 이 곳에 잘 정리되어 있으니 궁금하면 읽어보는 것도 좋을 것 같다.

클래스 생성

클래스는 다음과 같이 선언할 수 있다.
생성한 클래스의 타입을 출력해보면 클래스는 함수라는 것을 알 수 있다.

클래스는 호이스팅을 지원하지 않는다.

class Car {}

console.log(typeof Car); //  function
console.log(Car) // Car { }

클래스의 초기상태 정의

클래스를 통해 객체의 초기상태를 정의할 때는 constructor 함수 내부에 선언한다.
클래스가 호출되면 constructor 함수가 가장 먼저 실행되어 객체를 초기화 한다.

클래스 내부에서 메소드를 정의하려면 예약어 function은 제외하고 메소드의 이름과 내용만 작성한다.

다음은 각 생성자와 클래스 이용해 Car를 생성한 예제이다.

function Car(type, color) {
  	this.type = type;
	this.color = color;
}


// 함수 Car를 클래스로 변경
class Car {
	constructor(type, color) {
      	this.type = type;
     	this.color = color;
    }
}

메소드 선언

메소드는constructor 외부에 선언할 수 있으며, prototype 과 동일하게 작동 한다.

class Car {
	constructor(type, color) {
    	this.type = type;
     	this.color = color;
    }

  	start() {
    	console.log('Engine Start!');
    }
  
 	stop() {
    	console.log('Engine Stop...');
    }
}

클래스를 통해 객체 복사하기 및 메소드 사용법

생성자를 이용하는 방법과 같다.

let avante = new Car("sedan", "red");
console.log(avante) // Car { type: 'sedan', color: 'red' }
console.log(avante.start()) // Engine Start!

오버라이딩

앞서 기술했던 특정 객체 내용을 변경하는 것과 동일한 방법이다.

avante.start = function() {
  console.log("Avante Engine Start")
}
console.log(avante.start()) // Avante Engine Start

상속

상속을 사용하면 부모 클래스에서 정의된 메소드나 속성들을 자식 클래스에서 그대로 이어받을 수 있다.
부모 클래스에 선언한 내용은 자식 클래스에서 변경이 필요하지 않다면 재선언할 필요가 없다.

상속 예제


class HEVCar extends Car {
	motorStart() {
        console.log("Electric motor is enabled")
    }
}
let avanteHybrid = new HEVCar('sedan', 'white');
console.log(avanteHybrid.start()); // Engine Start

super

자식 클래스에서 부코 클래스 객체를 사용해야 할 때 사용할 수 있는 키워드다.
앞서 클래스가 호출되면 가장 먼저 constructor 가 실행되어 객체가 초기화된다 했는데, 이 때 super 키워드 사용시 constructor 는 HEVCar 가 아닌 부모 Car의 내용을 참조한다.

class HEVCar extends Car {
	constructor(type, color, trim) {
		super(type, color)
		this.trim = trim;
	}
}

super 를 이용해 자식 클래스 수신 파라미터 확장

let avanteHybrid = new HEVCar("sedan", "white", "insperation")
console.log(avanteHybrid) // HEVCar { type: 'sedan', color: 'white', trim: 'insperation' }

2개의 댓글

comment-user-thumbnail
2021년 3월 24일

3번 프로토타입 설명하실 때 자바스크립트가 메소드를 실행한다기 보다 엔진(런타임)이 실행시키는 것이라고 표기하는 것이 올바를 것 같습니다.

생성자함수는(class, new) 다른 함수들과 구분되야 할 필요가 있기에 파스칼 표기법을 쓰시는 것이 좋을 것 같습니다.

혼자 정리 목적인 것이라면 아무 상관 없겠지만 강의 개념으로 쓰시는 것이라면
검색을 통해 들어오는 초심자분들이 봤을 때 혼란을 일으킬 만한 애매한 표기들은 되도록 배제해주시는 것이 좋을 것 같네요.

좋은 글 감사합니다.

1개의 답글