JavaScript는 프로그램의 소스코드를 한 줄마다 기계어로 번역하는 방식을 취하고 있으며, 이는 한 줄 한줄 번역하므로 테스트에 용이한 구조를 가지고 있지만, 처리 속도가 컴파일 언어 보다 느리다는 단점이 있습니다. (아래 그림 참조)
프로토타입 패턴의 특징은 객체를 복제를 통해 생성한다는 점이 있습니다.
클래스 패턴 언어(Java)와 비교해보며 알아봅시다. 예를 들어 게임 캐릭터를 하나 생성한다고 생각하면, 처음 캐릭터가 생성될 때 너무 맨몸이면 유저가 싫어할 것 같으니 기본적인 무기과 갑옷을 가진 상태로 생성하고자 합니다.
// Player.java
class Weapon {}
class Armor {}
class BasicSward extends Weapon {}
class BasicArmor extends Armor {}
class Player {
public Weapon weapon;
public Armor armor;
public Player() {
this.weapon = new BasicSward(); // 기본 무기
this.armor = new BasicArmor(); // 기본 갑옷
}
}
위와 같이 Java(자바)의 경우에는 Player 객체가 자신이 생성될 때 BasicSward 객체와 BasicArmor 객체까지 함께 생성해야 합니다. 이 경우 그냥 Player 객체만 생성하는 상황보다 객체의 생성 비용이 높다고 할 수 있습니다. 그리고 다른 기본 아이템을 추가할수록 Player의 객체의 생성 비용 계속해서 높아집니다.
💡그럼 이 비용을 줄일 수 있는 좋은 방법이 없을까?
만약 캐릭터가 처음 생성될 때 가지고 있는 아이템이 항상 같다는 전제 조건이 있다면 생성 비용이 높은 Player객체를 딱 한번만 생성하고 그 다음부터는 생성된 객체를 복사해서 사용해도 될 것 같다는 생각을 할 수 있습니다.
이런 관점으로 접근하는 것이 바로 프로토타입 패턴이라고 할 수 있습니다. 프로토타입, 즉 원본 객체가 존재하고 그 객체를 복제해서 새로운 객체를 생성하는 방법인 것입니다. (프로토타입의 사전적인 정의는 "본래의 형태", "시제품" 등이고 여기서는 "본래의 형태"의 의미로 사용된 듯합니다.)
자바스크립트도 역시 복제를 통해 새로운 객체를 생성합니다. 자바스크립트가 어떻게 활용되는지 알아봅시다.
Code
function User () {}
const evan = new User();
console.log(11, evan);
console.log(22, typeof evan);
console창 결과물
11, User { __proto__: Object }
22, "object"
User함수에 new라는 생성자를 붙여서 객체를 인스턴스화 했습니다. 그런데 evan의 타입을 찍어보면 함수가 아니고 객체이다. evan은 무엇으로부터 복제된 것일까? 사실 evan은 User함수의 prototype 객체를 복제한 것입니다. 자바스크립트의 모든 객체는 prototype 객체를 가지고 있고 그 prototype을 복제하면서 객체를 생성합니다.
code
function User () {}
console.log(11, User.prototype);
console.log(22, typeof User.prototype);
console창 결과
11, { constructor: f User(), __proto__: Object }
22, "object"
처음 User에서 함수를 선언할 때 함수의 프로토 타입 객체도 함께 생성됩니다. 그리고 이 프로토타입 객체는 함수를 사용해서 새로운 객체를 생성할 때 원본 객체 역할을 해줄 객체를 의미합니다.
즉, new User()라는 문법을 사용하여 새로운 객체를 만들게 되면 User 함수 자체가 아니라 User 함수가 생성될 때 함께 생성된 User 함수의 프로토타입 객체를 복제해서 새로운 객체를 만든다는 것입니다.
정적 타입의 언어는 변수를 선언할 때 변수에 할당할 수 있는 값의 종류, 즉 데이터 타입을 사전에 선언해야한다 이를 명시적 타입 선언(explicit type declaration)이라 한다
동적 타입 언어는 변수에 어떤 데이터 타입의 값이라도 자유롭게 할당할 수 있다.
🤔 유연성 (flexibility)은 높지만 신뢰성(reliability) 는 떨어게되는 것이다
1) 변수를 억제하는 것이 좋다
2) 재할당을 하지 않는 것이 좋다
3) 상수를 쓰는것이 좋다
4) 전역 변수를 최대한 사용하지 않는다.
👉 동적 타입의 언어의 특성을 고려하여 4가지 정도 고려하여 코드를 작성한다면 가독성이 올라갈 것 입니다.
여기서 말하는 일급객체가 뭘까요?
특정 언어의 일급 객체란, 컴퓨터 프로그래밍 언어에서 일반적으로 다른 객체들에 적용 가능한 연산을 모두 지원하는 객체를 가리킨다. 즉, JavaScript의 함수는 보통의 다른 언어와 다르게 변수에 담을 수도 있고 다른 함수의 인자로도 사용이 가능하다.
다음과 같은 조건을 만족할 때 일급 객체라고 말할 수 있다.
// 변수에 함수 할당
var bar = fucntion() { return 'javscript'; };
console.log(bar()); // javascript
var test = function(func) {
func(); // 파라미터로 받은 함수 호출
}
// test() 함수에 다른 함수를 파라미터로 넣어 호출
test(function() {
console.log('javascript');
});
// 함수를 리턴하는 test() 함수
function test() {
return function() {
console.log('javscript');
}
}
var bar = test();
bar();
JavaScript에서는 이런 특성 때문에 숫자와 문자처럼 함수를 변수에 저장거나, 파라미터로 전달하고, 함수의 리턴값으로도 사용할 수가 있는 것이다.
클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 클로저를 이해하려면 자바스크립트가 어떻게 변수의 유효범위를 지정하는지(Lexical scoping)를 먼저 이해해야 한다.
다음 코드를 보자.
function init() {
var name = "Mozilla"; // name은 init에 의해 생성된 지역 변수이다.
function displayName() { // displayName() 은 내부 함수이며, 클로저다.
alert(name); // 부모 함수에서 선언된 변수를 사용한다.
}
displayName();
}
init();
init()은 지역 변수 name과 함수 displayName()을 생성한다. displayName()은 init() 안에 정의된 내부 함수이며 init() 함수 본문에서만 사용할 수 있다. 여기서 주의할 점은 displayName() 내부엔 자신만의 지역 변수가 없다는 점이다. 그런데 함수 내부에서 외부 함수의 변수에 접근할 수 있기 때문에 displayName() 역시 부모 함수 init()에서 선언된 변수 name에 접근할 수 있다. 만약 displayName()가 자신만의 name변수를 가지고 있었다면, name대신 this.name을 사용했을 것이다.
위 코드를 실행하면 displayName() 함수 내의 alert()문이 부모 함수에서 정의한 변수 name의 값을 성공적으로 출력한다. 이 예시를 통해 함수가 중첩된 상황에서 파서가 어떻게 변수를 처리하는지 알 수 있다. 이는 어휘적 범위 지정(lexical scoping)의 한 예이다. 여기서 "lexical"이란, 어휘적 범위 지정(lexical scoping) 과정에서 변수가 어디에서 사용 가능한지 알기 위해 그 변수가 소스코드 내 어디에서 선언되었는지 고려한다는 것을 의미한다. 단어 "lexical"은 이런 사실을 나타낸다. 중첩된 함수는 외부 범위(scope)에서 선언한 변수에도 접근할 수 있다.
그럼 다음 예제를 봅시다.
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
//myFunc변수에 displayName을 리턴함
//유효범위의 어휘적 환경을 유지
myFunc();
//리턴된 displayName 함수를 실행(name 변수에 접근)
이 코드는 바로 전의 예제와 완전히 동일한 결과가 실행된다. 하지만 흥미로운 차이는 displayName()함수가 실행되기 전에 외부함수인 makeFunc()로부터 리턴되어 myFunc 변수에 저장된다는 것이다.
한 눈에 봐서는 이 코드가 여전히 작동하는 것이 직관적으로 보이지 않을 수 있다. 몇몇 프로그래밍 언어에서, 함수 안의 지역 변수들은 그 함수가 처리되는 동안에만 존재한다. makeFunc() 실행이 끝나면(displayName함수가 리턴되고 나면) name 변수에 더 이상 접근할 수 없게 될 것으로 예상하는 것이 일반적이다.
하지만 위의 예시와 자바스크립트의 경우는 다르다. 그 이유는 자바스크립트는 함수를 리턴하고, 리턴하는 함수가 클로저를 형성하기 때문이다. 클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다. 첫 번째 예시의 경우, myFunc은 makeFunc이 실행 될 때 생성된 displayName 함수의 인스턴스에 대한 참조다. displayName의 인스턴스는 변수 name 이 있는 어휘적 환경에 대한 참조를 유지한다. 이런 이유로 myFunc가 호출될 때 변수 name은 사용할 수 있는 상태로 남게 되고 "Mozilla" 가 alert 에 전달된다.
다음은 조금 더 흥미로운 예제인 makeAdder 함수이다:
function makeAdder(x) {
var y = 1;
return function(z) {
y = 100;
return x + y + z;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
//클로저에 x와 y의 환경이 저장됨
console.log(add5(2)); // 107 (x:5 + y:100 + z:2)
console.log(add10(2)); // 112 (x:10 + y:100 + z:2)
//함수 실행 시 클로저에 저장된 x, y값에 접근하여 값을 계산
이 예제에서 단일 인자 x를 받아서 새 함수를 반환하는 함수 makeAdder(x)를 정의했다. 반환되는 함수는 단일인자 z를 받아서 x와 y와 z의 합을 반환한다.
본질적으로 makeAdder는 함수를 만들어내는 공장이다. 이는 makeAdder함수가 특정한 값을 인자로 가질 수 있는 함수들을 리턴한다는 것을 의미한다. 위의 예제에서 add5, add10 두 개의 새로운 함수들을 만들기 위해 makeAdder함수 공장을 사용했다. 하나는 매개변수 x에 5를 더하고 다른 하나는 매개변수 x에 10을 더한다.
add5와 add10은 둘 다 클로저이다. 이들은 같은 함수 본문 정의를 공유하지만 서로 다른 맥락(어휘)적 환경을 저장한다. 함수 실행 시 add5의 맥락적 환경에서 클로저 내부의 x는 5 이지만 add10의 맥락적 환경에서 x는 10이다. 또한 리턴되는 함수에서 초기값이 1로 할당된 y에 접근하여 y값을 100으로 변경한 것을 볼 수 있다. (물론 x값도 동일하게 변경 가능하다.) 이는 클로저가 리턴된 후에도 외부함수의 변수들에 접근 가능하다는 것을 보여주는 포인트이며 클로저에 단순히 값 형태로 전달되는것이 아니라는 것을 의미한다.
👍 장점1. 데이터를 보존할 수 있다.
👍 장점2. 정보의 접근 제한 (캡슐화)
👍 장점3. 모듈화에 유리하다.
❓예시코드
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
<p>Some paragraph text</p>
<h1>some heading 1 text</h1>
<h2>some heading 2 text</h2>
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>
body {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.2em;
}