생성자란 인스턴스를 생성하기 위해 new
연산자와 함께 사용되는 함수
생성자를 통해 생성된 객체(인스턴스)는 같은 프로퍼티와 메서드를 공유할 수 있음
생성자는 기본적으로 함수이므로 함수가 필요함
생성자 함수는 관습적으로 첫글자에 대문자를 사용
function Func(){}
let inst = new func()
생성자를 사용해 인스턴스를 생성하기
// 생성자 함수
function Factory(name) {
this.name = name;
this.sayName = function() {
console.log(`제 이름은 ${this.name}입니다.`)
}
}
// 인스턴스 생성
const robot = new Factory("도라에몽")
robot.sayName()
// 제 이름은 도라에몽입니다.
❗ 원래 함수에서의 this
는 함수를 호출한 객체를 참조하지만, new
연산자를 사용하면 인스턴스를 참조한다
생성자를 사용해 인스턴스를 생산해낼 수 있지만, 메서드를 포함한 인스턴스를 생성하면 메서드가 인스턴스 수만큼 계속해서 생성된다
function Factory(name) {
this.name = name;
this.sayName = function() {
console.log(`제 이름은 ${this.name}입니다.`)
}
}
const robot = new Factory("도라에몽")
const robot1 = new Factory("짱구")
/*
.
.
99개의 robot 인스턴스 생성
.
.
*/
const robot100 = new Factory("100번째 로봇")
robot1.sayName === robot100.sayName // false
위의 예제에서 함수를 비교해 false가 나온것처럼 두 함수는 서로 다른 메모리 공간을 가르키고 있고, 100개의 인스턴스가 생성되면 그에 따라 100개의 함수가 생성된 것이다.
이런 자원의 낭비의 문제점을 보완하기 위해 프로토타입
을 사용한다
프로토타입 : 모든 객체에서 해당 속성과 메서드를 사용할 수 있게 만들 수 있음
Factory에 프로토타입으로 sayName이라는 메서드를 추가
function Factory(name) {
this.name = name;
}
// sayName이라는 메서드를 추가
Factory.prototype.sayName = function() {
console.log(`제 이름은 ${this.name}입니다.`)
}
const robot = new Factory("도라에몽")
const robot1 = new Factory("짱구")
/*
.
.
99개의 robot 인스턴스 생성
.
.
*/
const robot100 = new Factory("100번째 로봇")
robot1.sayName === robot100.sayName // true
// 서로 같은 메모리 공간을 가르키고 있음
프로토타입을 사용해 모든 인스턴스가 하나의 메서드가 공유하도록 만들어 자원을 더 효율적으로 사용할 수 있게 해준다
prototype
, __proto__
함수가 생성될 때 함수는 prototype
을 가지게 된다.
인스턴스의 __proto__
는 함수의 prototype
과 서로 같은 데이터 공간을 가르키게 된다.
예시사진
아래 예제는 생성자 함수에서 상속을 다룬 예제이다
Object.create
지정된 프로토타입 객체 및 속성(property)을 갖는 새 객체를 만듬
function Parent() {
this.name = 'Choi';
}
Parent.prototype.rename = function (name) {
this.name = name;
}
Parent.prototype.sayName = function () {
console.log(this.name);
}
const human = new Parent()
human.rename("BK")
human.sayName() // "BK"
// 자식 역할을 할 생성자 함수 생성
function Child() {
Parent.call(this);
// this로 사용할 객체가 Parent의 this
// 새로 생성된 인스턴스의 this는 Parent를 가르킴
// Parent의 this를 참조하게 되는 것
}
// 대입연산자로 Parent의 프로토타입을 Child의 프로토타입에 할당
Child.prototype = Object.create(Parent.prototype);
const person = new Child()
call()
메서드와 prototype
할당으로 Child 객체는 Parent 객체의 모든것을 상속받게 된다
객체를 생성하기 위한 템플릿.
class 표현식과 class 선언문 두가지 방법을 제공
ES6 버전부터 추가되었으며 class 문법을 상속을 더 쉽게 구현할 수 있다.
class Basic{
constructor(){
}
// 여러 메서드를 정의할 수 있다
// 정의한 메서드는 Basic.prototype에 저장
메서드() {
}
class
생성자 함수를 class로 간단하게 표현할 수 있다
// 생성자 함수와 프로토타입
function Test(name){
this.name = name;
}
Test.prototype.sayName = function(){
console.log(`이름은 ${this.name}입니다.`)
}
const human = new Test('CHOI');
///////////////////////////////////////////
// class 문법
class test {
constructor(name){
this.name = name;
}
SayName() {
console.log(`이름은 ${this.name}입니다.`)
}
}
const person = new test('CHOI');
위 예시의 생성자함수와 class는 완전히 동일한 역할을 한다
extends
extends
키워드는 클래스의 상속을 위해 사용된다.
상속은 이미 존재하는 클래스(부모)의 모든것을 파생 클래스(자식)에게 모든 속성과 메서드를 사용할 수 있게 해주는 개념
super
super
키워드를 통해 상속
super
키워드를 통해 상위 클래스의 생성자를 호출하거나, 메서드를 호출 할 수 있다.
생성자(constructor
)에서는 super
키워드 하나만 사용되거나, this
키워드가 사용되기 전에 호출되어야 한다. (런타임 오류:예외 발생)
class Human {
constructor(name) {
this.name = name
}
sayName() {
console.log(`안녕하세요 ${this.name} 입니다.`)
}
}
class Me extends Human {
constructor(name, age){
super(name);
this.age = age
}
sayName(){
super.sayName();
console.log(`${this.age}살 입니다.`)
}
}
const me = new Me('bk', '23')
me.sayName()
// 안녕하세요 bk 입니다.
// 23살 입니다.
getter와 setter를 사용하면, 객체의 프로퍼티를 보호하고 유효성 검사도 수행할 수 있다.
get
구문은 객체의 속성 접근 시 호출할 함수를 바인딩한다
setter : 속성 값을 설정하는 메서드, set 키워드를 사용해 정의
set
구문은 객체의 속성에 할당을 시도할 때 호출할 함수를 바인딩한다
class에서 get과 set 키워드 없이 객체의 프로퍼티에 접근할 수 있으나, 직접 접근하는 것은 객체의 속성을 보호하지 못한다.
다른 코드에서 객체의 속성을 변경할 수 있으므로, 객체의 상태를 일관성 있게 유지하기 어렵다.
그래서 getter와 setter를 정의해두고, 객체의 프로퍼티에 간접적으로 접근해야 한다.
또한 getter와 setter를 사용하면, 객체의 프로퍼티를 보호하고 유효성 검사도 수행할 수 있다.
class Person {
constructor(firstName, lastName) {
this._firstName = firstName;
this.lastName = lastName;
}
get firstName() {
return this._firstName;
}
set firstName(value) {
if (typeof value !== 'string') {
throw new TypeError('firstName은 문자여야 합니다');
}
this._firstName = value;
}
}
let Human = new Person("BK", "CHOI")
Human.firstName // getter로 간접적 접근
Human.firstName = 1 // setter로 간접적 접근
ES6에서는 클래스 내부에서 비공개 프로퍼티를 만들 수 있도록 #
기호를 사용하는 프라이빗 필드(private field) 기능이 도입되었다.
class Circle {
#radius = 0; // 프라이빗 필드
constructor(radius) {
this.#radius = radius;
}
getArea() {
return Math.PI * this.#radius * this.#radius;
}
}
let test = new Circle(30)
test.#radius = 100 // node.js 환경에서는 예외를 반환
getter, setter와 비공개 프로퍼티는 기본적으로 같은 목적을 가지고 있다.
getter와 setter는 클래스 외부에서 접근 가능한 프로퍼티를 캡슐화하기 위해 사용되며 클래스의 프로퍼티에 직접적으로 접근하는 게 아니라 getter와 setter를 통해 접근하고 간접적으로 접근해 조작하기 때문에 클래스의 상태를 보호하고 유지할 수 있다.
반면, 비공개 프로퍼티는 클래스 내부에서만 접근 가능한 프로퍼티를 의미하는데, 클래스 외부에서는 비공개 프로퍼티에 직접 접근할 수가 없으므로 클래스 외부에서 프로퍼티의 값을 읽거나 변경할 수 없음, 즉 클래스의 상태를 완전히 보호할 수 있지만 클래스 내부에서만 사용할 수 있기 때문에 클래스 외부에서 필요한 값을 제공하기 위해 getter와 setter를 사용해야 할 수 있음
static
Class 문법에서 static
키워드를 사용하여 정적 메소드(static method)와 정적 속성(static property)을 정의할 수 있다.
이는 인스턴스를 생성하지 않고도 사용할 수 있는 속성과 메서드이다.
상위 클래스에서 정의된 메서드를 파생 클래스에서 재정의하여 사용하는 것을 의미한다.
하위 클래스에서 상위 클래스에서 정의된 메소드를 사용할 수 없거나, 새로운 동작을 구현해야 할 때 오버라이드한다.
파생 클래스에서 상위 클래스의 메서드를 오버라이드 하는 예제
class Say {
sayHello(){
console.log("부모 클래스 메서드")
}
}
class AnotherSay extends Say {
sayHello(){
console.log("오버라이드")
}
}
new AnotherSay().sayHello() // "오버라이드"