[Javascript]자바스크립트의 함수, 오브젝트, 프로토타입
오늘은 자바스크립트에서 '함수'에 대해 짚고 넘어가겠다.
우선, 프로그래밍을 공부해본 사람이라면 함수에 대해 너무나 잘 알고 있을것이다. 그렇지만 한번만 더 짚고 넘어가보자!
함수(메서드)
function add(n, m) {
return n + m;
}
var x = add(1,2); //3을 반환
x = add(1.23, 3.45) // 4.68을 반환
x = add("hello", "world") //"helloworld"를 반환
일단, 자바와 달리 자바스크립트의 함수는 function 키워드로 정의한다. 리턴 타입, 파라미터의 타입등은 명시해주지 않아도 되며, 각각의 타입들은 컴파일시 알아서 타입이 인지된다!(자바랑 관련이 없는데 왜 자바스크립트인지는 아직까지 모르겠다)
위를 보면 세가지의 예시들이 있다. 신기하게도 리턴 타입이 명시되지 않은 함수(메서드)에서 각각은 다른 리턴 타입을 가진다.
오브젝트
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
this.getName = function() {
return this.make + ' ' + this.model + ' ' + this.year;
}
}
var c = new Car("Hyundai", "Sonata", 2021);
alert(c.getName()); // "Hyundai Sonata 2021"
자바스크립트에서의 오브젝트 생성은 자바와 비슷하다. new 연산자를 통해 새 오브젝트를 만들고, 함수의 인자를 원하는 값으로 넘겨주면 된다. this를 한번 더 설명하고 넘어가겠다. 나 조차도 이 this때문에 자료구조 수업에서 애를 먹었던 기억이 난다,,, 간단 명료하게 말해 여기서의 this는 "자기자신" 이다. 즉, 언제든 우리가 Car에 대한 인스턴스를 만들고, 파라미터들을 넘겨주면, 그 파라미터는 "자기자신" 즉, 그 오브젝트의 것이 된다. 위를 예로 들어 var c는 new 연산자를 통해 오브젝트가 생성되었고, 인자로 "hyndai", "sonata", 2021을 넘겨주었다. 그러면 맨 처음 함수 선언 당시 썼던 this는 "c"를 가리킨다. 아래와 같이 말이다.
c.make
c.model
c.year
또한, 위에서의 getName 이라는 함수는 Car이라는 함수 안에 선언된 또다른 함수이다(중첩함수). 함수 내부에서 또 다른 함수를 선언할때, 내부 함수는 외부 함수의 변수에 접근 할 수 있다. 이는 클로저(Closure)라 불리는 개념으로, 함수가 정의된 스코프를 기억하여 나중에 쓸 수 있도록 하는 개념이다.
프로토타입
"자바스크립트는 여타 프로그래밍 언어들과 다르게 '클래스' 라는 개념이 없는 프로그래밍 언어이다. 대신, 프로토타입은 객체 간에 상속을 담당하는데, 이를 '프로토타입 체인'이라고 한다. 자바에서 상속을 배운 사람이라면 프로토타입 체인이 비슷한 개념임을 이해할 수 있을 것이다. 예를 들어, VSCode에서 .js 확장자를 가진 자바스크립트 파일을 만들고, 위의 예시에서 보여준 Car를 입력하고 '.'을 누른다면 자동완성으로 'prototype'이라는 속성을 볼 수 있다. 이것은 자바스크립트가 모든 객체에 자동으로 `Object.prototype`이라는 내장 객체의 프로토타입을 상속해주기 때문이다. 말로는 설명이 너무 복잡하므로 예시를 보면서 이해해보자.
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
this.getName = function() {
return this.make + ' ' + this.model + ' ' + this.year;
};
}
Car.prototype.country = "Korea"
var c = new Car("Hyundai", "Sonata", 2021);
console.log(c.getName() + c.country);
여기서 Car 함수는 위의 예시와 동일하다. 그렇지만 아래 다른점이 있다. Car.prototype.country = 'Korea'를 추가해주었다. "prototype"을 일종의 "유전자" 라고 이해하면 이해하기가 쉽다. 위의 함수에서 Car함수는 '부모' 이고, var c = new Car()에서의 c는 '자식' 이다. 그 말은 즉, Car.prototype.country = 'Korea'는 부모함수에 'country'라는 유전자를 추가해주고, 그 유전자의 속성은 'Korea'로 설정해줘. 라는 말이랑 같다. 이제 위의 코드에서 c.country를 하면 console.log문에 Korea가 출력되고, 최종 출력은 아래와 같다.

그런데 여기서 이러한 의문이 들었다. 근데 나는 c라는 오브젝트에 Korea라는 속성을 할당한적이 없고, 부모함수 내에서도 Korea라는 속성을 할당한적이 없는데 어떻게 컴퓨터는 이 사실을 알고 Korea라는 속성을 할당했을까?
컴퓨터는 내부적으로 이러한 일련의 과정을 거쳐 속성을 찾아낸다.
[인스턴스에 country라는 속성이 있는지 확인하고 있으면 반환한다(위의 Car의 경우엔 c인스턴스엔 country라는 속성이 정의되어있지 않으므로 c 인스턴스의 부모(== Car 함수)로 올라가서 찾는다 -> 우리는 Car.prototype.country = "Korea"라고 지정해주었으므로 지정한 속성을 찾을때까지 부모를 타고 계속 올라간다) -이걸 프로토타입 체이닝 이라고 부른다!
그러면 도대체 왜 '프로토타입'을 사용할까? 그냥 부모함수에 this.country = "Korea" 라고 해주면 되는것 아닌가?
프로토타입을 사용하는 이유는 객체 간의 코드를 공유하고 메모리를 효율적으로 사용하기 위함이다. 특히, 여러 객체가 같은 메서드나 속성을 공유할 경우, 프로토타입을 사용하면 중복된 코드를 피하고 코드의 재사용성을 높일 수 있다.
프로토타입을 사용하지 않고 메서드를 직접 생성자 함수 내에 정의하면, 각 객체가 생성될 때마다 해당 메서드가 새로 생성되기 때문에 메모리 사용이 비효율적일 수 있다. 반면 프로토타입을 사용하면 모든 객체가 동일한 프로토타입을 공유하므로 해당 메서드는 한 번만 생성되고 모든 객체가 이를 공유한다.
아래는 두 가지 방식으로 메서드를 추가한 경우를 비교한 예제이다:
1. **프로토타입 사용:**
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
Car.prototype.getName = function() {
return this.make + ' ' + this.model + ' ' + this.year;
};
2. **객체 내에서 메서드 직접 정의:**
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
this.getName = function() {
return this.make + ' ' + this.model + ' ' + this.year;
};
}
위의 두 방식을 사용하여 객체를 생성하면, 프로토타입을 사용한 경우에는 모든 객체가 같은 `getName` 메서드를 참조한다. 그러나 객체 내에서 메서드를 직접 정의한 경우에는 각 객체가 독립적으로 `getName` 메서드를 가지며, 이는 메모리의 낭비로 이어질 수 있다.