Constructor Functions?

  • 함수 생성자는 여러 프로그래밍 언어에서 클래스와 동의어이다.
  • 어떤 경우 사람들은 Reference Types, Class, 데이터 타입을 정의 하도록 해주고,
  • 그 속성과 행동을 통해 여러 객체가 생성될 수 있도록 해주는 생성자라고 이해하면 된다.

function Person(name, position) { 
	this.name = name, 
	this.position = position 
}

보통 함수는 괄호화 함께 호출하고 생성자 함수는 new 연산자를 사용한다.

const hannah = new Person('Hannah Yoo', 'programmer')
const jisoo = new Person('Jisoo Han', 'student')

여기서 우리는 Person 이라는 객체를 두 개 생성하고 있다.
new 키워드와 함께 생성자 함수가 호출 될 때, this는 생성되고 있는 바로 그 객체를 참조한다.

단점

prototype 오염

생성자 함수는 모든 인스턴스가 같은 프로토타입을 공유하는데, 프로토 타입이 변경되면, 모든 인스턴스에 영향을 미칩니다.

this 바인딩 문제

생성자 함수 내부에서 this가 의도한 객체를 가리키지 않는 경우 발생

상속의 복잡성

클래스 기반 상속에 비해서 생성자 함수를 통해 상속을 구현하는 것이 더 복잡한 문제입니다.

Methods

메소드는 생성자 함수 안에서 속성에 함수를 할당하면서 정의된다.

function Person(name) { 
	this.name = name; 
	this.hi = function () {
		console.log(`Hi, My name is ${this.name}`)
	} 
} 
const hannah = new Person('Hannah Yoo'); 
hannah.hi(); // Hi, My name is Hannah Yoo

여기서 hi 속성은 할당된 함수이다.
Person 객체가 호출 될 때, this 키워드는 새로 생성된 Person 객체에 응답 할 것이다.

이런 방식으로 메소드가 정의 된다 해도 이런 접근법은 약간 귀찮다.
Person 인스턴스가 생성될 때마다 그 객체의 hi 프로퍼티에 새로운 함수가 정의되고, 할당된다.

만일 우리가 5개의 Person 객체를 생성한다 치면, 각자 모두가 같은 일을 하는 자신만의 hi 메소드를 가지게 된다.
더 효율적인 방법은 hi 속성을 한 번만 정의하는 것이다.

그리고 각 Person 객체는 같은 함수를 참조하면 된다.
이를 위해서는 함수의 prototype을 써볼 수 있다.

function Person(name) {
    this.name = name;
}

Person.prototype.hi = function () {
    console.log('hi my name is' + this.name)
}
const hannah = new Person('Hannah Yoo')
const jisoo = new Person('Jisoo Han')

hannah.hi();
jisoo.hi();

여러 번 Person.prototype에 새로운 메서드를 추가하는 것보다 그냥 Person.prototype 객체를 재 정의하는 게 낫다.
앞서 prototype이 거의 빈 객체라고 언급했는데, 정확히 말하자면, prototypeconstructor라는 속성을 가지고 있고,
이는 생성자 함수를 가리킨다.

우리가 완전히 새로운 객체로 prototype을 덮어 씌운다면, 이 constructor속성도 reset 해야 한다.

왜 Prototype에 써야 하는 걸까?

prototype 위의 그 어떤 것이든 모든 해당 인스턴스 객체 사이에 공유되기 때문에, 우리는 prototype에 선언된 메소드랄지 생성자 객체 위에 저장된 속성 만을 보곤 한다.

메소드는 공유된 행동 양식으로 각 객체는 자신만의 유일한 메소드가 없다.
하지만, 각 객체는 자신만의 속성을 가져야 할 때 도 있는 법이다.

prototype이 아니라 객체 자체에 정의된 속성은 own properties라고 불린다.

ES6 classes

ES6 덕분에 우리는 위 생성자 함수를 class 문법으로 쓸 수 있게 되었습니다.

class Person { 
	constructor(name) { 
		this.name = name; 
	} 
	hi() { 
		console.log('..') 
	} 
}

여기서 hi()Person.prototype에 저장된다.

Property Lookups

만일 우리가 객체와 생성자의 prototype에 같은 이름의 속성을 선언하면, 어떤 일이 벌어질까?

function Person(name) { 
	this.name = name; 
	this.walk = function () { 
		console.log('moon walking') 
	} 
} 
Person.prototype.walk = function () { 
	console.log('normal walking') 
}; 

const mj = new Person('Michael Jackson'); 
mj.walk();

이 예제에서는, walk이라는 메소드가 Person 인스턴스와 Person.prototype 에 각각 선언되었다.
콘솔에는 뭐라고 찍힐까?

JavaScript는 우선 객체 자신의 own property 부터 찾으려 한다.
만일 own property가 존재한다면, 해당 속성이 사용된다.

그렇지 않으면, 해당 객체를 만든 함수의 prototype을 찾아보려 할 것이다.
따라서 위 예제에서는 mj 객체 자체에서 walk 가 발견되고, 콘솔에는 moon walking이 찍힐 것이다.
만일 Person 객체가 아래처럼 생겼다면, 객체 자체에 walk 메서드가 발견되지 않으니,
JS는 walk 메서드가 발견되는 Person.prototype을 찾게 되고, normal walking이 콘솔에 찍힐 것이다.

function Person(name) { 
	this.name = name; 
}

상속

만일 클래스 기반의 언어를 공부한 사람이라면, 상속이 어떻게 작동하는지 궁금할 것이다.
Animal 이라는 생성자가 있다고 해보자.

function Animal() {} 
Animal.prototype.eat = function () { 
	console.log('eating') 
};

이번에는 Cat 생성자가 있다고 해보자

function Cat() { 
	Cat.prototype.meow = function () { 
		console.log('meowing'); 
	} 
};

Cat은 동물의 한 중료이니, Animal로부터 확장된 Cat을 갖고자 한다.
상속을 하기 위한 한 가지 방법은 아래와 같다.

function Animal() {} 
Animal.prototype.eat = function () { 
	console.log('eating') 
} 

function Cat(){} 
Cat.prototype = new Animal(); 
Cat.prototype.constructor = Cat; 

Cat.prototype.meow = function () { 
	console.log('meowing'); 
}

예전에는 클래스 기반의 언어를 사용한적이 있다면, 분명 이 대목에서 뭔가 할 것이다.
굉장히 투박해 보이지 않은가?
ES6의 클래스가 이 과정을 깔끔하게 만들어주었다.

class Animal { 
	eat() { 
		console.log('eating') 
	} 
}

class Cat extend Animal () { 
	meow() { 
	console.log('meowing') 
	} 
}

#JavaScript