[JavaScript] 생성자 함수(Constructor function)와 프로토타입(Prototype)

Dodam·2023년 9월 20일
0
post-thumbnail

생성자 함수(Constructor function)

생성자는 객체를 생성하기 위한 함수이다. 생성자는 다른 함수들과 달리 대문자로 시작한다.

function Student(grade, name) {
	this.grade = grade
  	this.name = name
  	this.introduce = function() {
    	console.log('학년: ' + this.grade + ' 이름: ' + this.name)
	}
}

자바스크립트에서 생성자는 new 연산자를 통해 호출할 수 있다. new 연산자를 사용하지 않으면 생성자를 반환할 수 없기 때문에 undefined가 출력된다.

var gildong = new Student(3, '홍길동')
var cheolsoo = Student(1, '김철수')

console.log(gildong)
console.log(cheolsoo)
Student { grade: 3, name: '홍길동', introduce: [Function (anonymous)] }
Undefined

new 연산자의 원리

new 연산자를 사용하면 내부적으로 다음과 같이 동작한다.

  • 빈 객체를 생성한다.
  • 새로 생성된 빈 객체는 생성자의 프로토타입을 상속받는다.
  • this를 새로 생성된 객체에 바인드 시킨다.
  • 생성자에 명시적으로 다른 객체를 리턴하지 않는 경우, this로 바인드된 객체가 반환된다.
    (일반적으로 생성자는 값을 리턴하지 않는다.)

프로토타입(Prototype)

자바스크립트의 모든 객체는 자신의 부모 역할을 담당하는 객체와 연결되어 있다. 그리고 이는 마치 객체 지향의 상속 개념처럼, 부모 객체의 프로퍼티 또는 메소드를 상속받아 사용할 수 있게 한다. 이러한 부모 객체를 Prototype(프로토타입) 객체 또는 줄여서 Prototype(프로토타입)이라고 한다.

자바스크립트에서는 객체를 생성하면 그 객체 안에 prototype이라는 객체가 자동으로 생성되고, 그 프로토타입 안에 생성자를 가리키는 constructor라는 객체가 자동으로 생성되어 서로 참조할 수 있게 된다.

위의 코드와 동일하게 동작하지만, this.introduce 대신 Student.prototype.introduce로 선언해주었다.
this로 선언할 경우, 생성자를 호출할 때마다 프로퍼티와 메소드도 하나씩 만들어진다.
하지만 프로토타입을 선언할 경우, 프로퍼티나 메소드가 한 번만 만들어져 해당 메모리를 공유할 수 있고 불필요한 낭비를 줄일 수 있다.

function Student(grade, name) {
	this.grade = grade
  	this.name = name
}

Student.prototype.introduce = function() {
	console.log('학년: ' + this.grade + ' 이름: ' + this.name)
}
var gildong = new Student(3, '홍길동')
var cheolsoo = new Student(1, '김철수')

gildong.introduce()
cheolsoo.introduce()
학년: 3 이름: 홍길동
학년: 1 이름: 김철수

생성자와 프로토타입은 서로 참조한다. 따라서 다음과 같은 경우도 만족할 수 있다.
__proto__는 실제 객체를 만들 때, 생성자의 prototype을 참조하고 있는 프로퍼티이다.

function Student(grade, name) {
	this.grade = grade
  	this.name = name
}

var gildong = new Student(3, '홍길동')

console.log(Student.prototype.constructor === Student)
console.log(Student.prototype === gildong.__proto__)
console.log(gildong.__proto__.constructor === Student)
true
true
true

프로토타입 체인(Prototype chain)

자바스크립트는 특정 객체의 프로퍼티나 메소드에 접근하려고 할 때, 해당 객체에 접근하려는 프로퍼티 또는 메소드가 없다면 [[Prototype]]이 가리키는 링크를 따라 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티나 메소드를 차례대로 검색한다. 이것을 프로토타입 체인이라고 한다.

var student = {
	name: 'Lee',
  	score: 90
}

// Object.prototype.hasOwnProperty()
console.log(student.hasOwnProperty('name'));  // true

student 객체는 hasOwnProperty 메소드를 가지고 있지 않으므로 에러가 발생해야 하나 정상적으로 결과가 출력되었다. 이는 student 객체의 [[Prototype]]이 가리키는 링크를 따라가서 student 객체의 부모 역할을 하는 프로토타입 객체(Object.prototype)의 메소드 hasOwnProperty를 호출했기 때문에 가능한 것이다.

constructor 프로퍼티

프로토타입 객체는 constructor 프로퍼티를 갖는다. 이 constructor 프로퍼티는 객체의 입장에서 자신을 생성한 객체를 가리킨다.

예를 들어 Person() 생성자 함수에 의해 생성된 객체를 foo라고 할 때,
이 foo 객체를 생성한 객체는 Person() 생성자 함수이다.
이 때 foo 객체 입장에서 자신을 생성한 객체는 Person() 생성자 함수이며, foo 객체의 프로토타입 객체는 Person.prototype이다.
따라서 프로토타입 객체 Person.prototype의 constructor 프로퍼티는 Person() 생성자 함수를 가리킨다.

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

var foo = new Person('Lee');

// Person() 생성자 함수에 의해 생성된 객체를 생성한 객체는 Person() 생성자 함수이다.
console.log(Person.prototype.constructor === Person);

// foo 객체를 생성한 객체는 Person() 생성자 함수이다.
console.log(foo.constructor === Person);

// Person() 생성자 함수를 생성한 객체는 Function() 생성자 함수이다.
console.log(Person.constructor === Function);

프로토타입 객체의 확장

프로토타입 객체도 객체이므로 일반 객체와 같이 프로퍼티를 추가/삭제할 수 있다.

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

var foo = new Person('Lee');

Person.prototype.sayHello = function() {
	console.log('Hi! my name is ' + this.name);
};

foo.sayHello();

생성자 함수 Person은 프로토타입 객체 Person.prototype와 prototype 프로퍼티에 의해 바인딩되어 있다. 위의 예에서 Person.prototype 객체에 메소드 sayHello를 추가하면 sayHello 메소드가 프로토타입 체인에 반영된다. 따라서 생성자 함수 Person에 의해 생성된 모든 객체는 프로토타입 체인에 의해 부모객체인 Person.prototype의 메소드를 사용할 수 있게 된다.

원시 타입(Primitive data type)의 확장

자바스크립트에서 원시 타입(숫자, 문자열, boolean, null, undefined)을 제외한 모든 것은 객체이다.

다음 예제를 살펴보면 원시 타입인 문자열이 객체와 유사하게 동작하는 것을 알 수 있다.
(단, 원시 타입 문자열과 String() 생성자 함수로 생성한 문자열 객체의 타입은 분명 다르다.)

var str = 'test';
console.log(typeof str);					// string
console.log(str.constructor === String);	// true
console.dir(str);							// test

var strObj = new String('test');
console.log(typeof strObj);						// object
consolel.log(strObj.constructor === String);	// true
console.dir(strObj);
// {0: "t", 1: "e", 2: "s", 3: "t", length: 4, __proto__: String, [[PrimitiveValue]]: "test" }

console.log(str.toUpperCase());		// TEST
console.log(strObj.toUpperCase());	// TEST

원시 타입은 객체가 아니므로 프로퍼티나 메소드를 직접 추가할 수 없다.

var str = 'test';

str.myMethod = function() {
	console.log('str.myMethod');
};

str.myMethod();	// Uncaught TypeError: str.myMethod is not a function

하지만 String 객체의 프로토타입 객체 String.prototype에 메소드를 추가하면
원시 타입, 객체 모두 메소드를 사용할 수 있다.

var str = 'test';

String.prototype.myMethod = function() {
	return 'myMethod';
};

console.log(str.myMethod());		// myMethod
console.log('string'.myMethod());	// myMethod
console.dir(String.prototype);

즉, 모든 객체는 프로토타입 체인에 의해 Object.prototype 객체의 메소드를 사용할 수 있으며,
Object.prototype 객체는 프로토타입 체인의 종점으로 모든 객체가 사용할 수 있는 메소드를 갖는다.

profile
⏰ Good things take time

0개의 댓글