필자는 최전방 개발자로서 자바스크립트를 사용하는 사람들을 많이 만난다. 이들 대부분은 주로 jQuery와 몇 가지 플러그인을 사용해 멋진 라이트박스 효과나 이미지 슬라이더를 추가하는 정도의 작업을 하지만, 그중 일부는 키보드 위로 손을 올려 놓기만 해도 코드가 저절로 써진다는 ‘제다이’급 개발자들이 있다.
이 두 가지 기술 수준의 중간 어딘가에 있는 사람은 때로 어떻게 해야 할지 모르는 상황에 부닥친다. 전문적인 지식을 찾는 길은 많다. 지금 구덩이에 빠져 도움이 필요하다면 모든 자바스크립트 전문가가 알아야 하는 다음과 같은 몇 가지 개념을 살펴보라.
자바스크립트 비밀 1: 클로저(Closures)
자바스크립트의 클로저는 함수에 부모 함수의 범위에 대한 접근성을 제공한다. 개발자에게는 극히 혼란스러운 개념이다. 필자는 자바스크립트를 배울 당시 의도하지 않은 클로저로 인해 코드를 디버깅하는 데 몇 시간을 소비한 적이 있다. 이런 실수를 통해 배운 이후에는 클로저가 상당히 멋지다고 생각했고 다시 몇 시간 동안 클로저를 사용하여 문제를 해결하려 헛되이 노력하기도 했다.
결국, 배운 것은 클로저는 부모 범위에 대한 접근성을 가진 안쪽 함수(아래 고차 함수 참조)를 반환할 때 유용하다는 점이다. 이로써 변수를 보관하기 위한 일종의 비공개, 또는 보호된 환경이 만들어진다. 아래 예에서 count는 threeTimer가 호출될 때 접근 및 증분되며, 점이나 괄호 기호를 통해 직접 접근할 수는 없다.
// **Closures**
function makeThreeTimer(){
var count = 0;
return function(){
if(count < 3){
console.log('doing work');
count++;
}
else {
throw new Error('No more work');
}
}
}
var threeTimer = makeThreeTimer();
threeTimer(); // logs 'doing work' (count gets incremented)
threeTimer(); // logs 'doing work' (count gets incremented)
threeTimer(); // logs 'doing work' (count gets incremented)
threeTimer(); // throws an error
threeTimer.count; // returns undefined
자바스크립트 비밀 2: 고차 함수
기능적 프로그래밍 언어에서 함수는 1급 구성원이다. 다른 모든 값과 마찬가지로 전달할 수 있으며 이로써 흥미로운 가능성이 열린다. 고차 함수는 다른 함수를 소비 또는 생산하는 함수를 말한다. call 또는 bind와 같은 function 프로토타입의 메소드는 모두 고차 함수다. 커링(currying), 메모이제이션(memoization)과 같은 기법을 고차 함수로 표현할 수 있다. 자바스크립트에서 고차 함수를 사용하면 관점 지향 프로그래밍(Aspect-oriented programming)도 가능하다.
자바스크립트 비밀 3: 함수 호출
함수를 호출하는 가장 일반적인 방법(괄호 연산자를 사용하는 방법)을 이해한 다음에는 call과 apply를 사용하는 방법을 배울 차례다. 괄호 연산자 대비 call/apply를 사용할 때의 장점은 함수가 실행되는 컨텍스트를 지정할 수 있다는 점이다(this의 값). 이러한 형태는 고차 함수, 특히 이러한 고차 함수가 나중에 실행되는 함수를 소비할 때 볼 수 있다. Function 프로토타입에서 bing 메소드의 내부는 call/apply의 훌륭한 예다.
// Possible implementation of bind using apply
function bind(func, context){
return function(){
func.apply(context, Array.prototype.slice.apply(arguments));
}
}
자바스크립트 비밀 4: this는 무엇인가?
this 키워드는 많은 자바스크립트 개발자에게 큰 장애물로, 어떤 사람들은 아예 이 키워드를 외면한다. 필자가 지금까지 접한 이 키워드에 대한 최고의 설명은 예후다 캐츠가 함수 호출에 대한 블로그에 쓴 글이다. call/apply 또는 bind를 사용하지 않을 경우 this 값은 다음과 같은 경우를 제외하고 항상 전역 객체를 참조한다.
1. 문제의 함수가 new 연산자를 사용하여 호출된 경우, 현재 만들어지고 있는 새로운 객체를 가리킨다.
2. 문제의 함수가 객체의 멤버인 경우, 이 객체를 가리킨다.
단, 호출되는 함수가 클릭 핸들러 또는 setTimeout 등에서 비동기적으로 호출될 때는 2번 규칙을 무시해야 한다. 다음을 참고하라.
Person.getName(); // 'this' points to Person
setTimeout(Person.getName, 1000); // 'this' points to the global object
JavaScript secret No. 5: Protecting the global scope
자바스크립트 비밀 5: 전역 범위 보호
자바스크립트의 결함 중 하나는 페이지의 모든 스크립트가 공유되는 전역 컨텍스트에서 실행된다는 점이다. 이 결함이 악용될 경우 웹 사이트는 교차 사이트 스크립팅 공격에 취약하게 된다. 공유 전역 컨텍스트는 다른 문제도 일으킨다. 예를 들어 많은 스크립트가 한 페이지에서 실행되는데, 모든 스크립트를 개발자가 결정하는 것은 아니다(광고 스크립트 등이 대표적이다). 이 스크립트들은 전역 공간에서 실행되며 모두 같은 전역 변수에 접근할 수 있다. 이러한 스크립트 두 개가 같은 전역 변수를 사용하게 되면 서로 간섭하기 시작하고, 그러면 코드는 망가지게 된다.
전역 범위의 사용을 최소화하는 것은 간섭을 막고 긴 디버깅 시간을 줄이는 데 도움이 되는 방어적 기법이다. 변수를 전역적으로 저장하는 것을 완전히 없앨 수는 없지만 네임스페이싱과 같은 기법을 사용하면 최소화할 수 있다.
// **Namespacing**
// The global footprint is a single object containing
// all other references needed.
var MyApp = {};
MyApp.id = 42;
MyApp.utils = {
validate: function(){
//do work
}
};
또는 모듈 패턴도 있다.
// **Module pattern**
// This relies on immediately invoked anonymous functions and the
// closure they create to maintain access to variables within the
// function.
var MyApp = (function(){
//declare a local variable
var appId = 42;
function getAppId(){
//referring to a variable in this function's parent scope
return appId;
}
return {
getAppId: getAppId
};
}());
appId; // undefined
MyApp.appId; //undefined
MyApp.getAppId(); // returns 42. (Brought to you by closures)
JavaScript secret No. 6: Inheritance
자바스크립트 비밀 6: 상속
자바스크립트에서 상속은 여러 가지 이유로 긴 혼란의 역사를 갖고 있다. 대부분 개발자(필자가 만난 많은 자바스크립트 개발자들 포함)들은 클래스 모델을 충실하게 이해하지만, 프로토타입 모델에 대해서는 매우 혼란스러워한다. 프로토타입 상속을 사용하는 언어 목록을 살펴보고 나면 아마 이러한 현상을 이해할 수 있을 것이다.
자바스크립트를 제외한 다른 주류 언어에는 프로토타입 상속이 없다. 게다가 자바스크립트에서는 클래스 모델을 에뮬레이션하는 것이 가능하다. 그 결과 상속에 대한 접근 방식이 매우 다양하며, 이들 중 상당수가 독선적, 모순적이다. 의사 클래스 방식(pseudo-classical approach)은 자바스크립트의 함정 중 하나이므로 필자는 절대 사용하지 말 것을 제안한다. 대부분 개발자에게 친숙하게 보이겠지만 사실 근사치일 뿐이므로 금방 깨진다. editor@itworld.co.kr
이 두 가지 기술 수준의 중간 어딘가에 있는 사람은 때로 어떻게 해야 할지 모르는 상황에 부닥친다. 전문적인 지식을 찾는 길은 많다. 지금 구덩이에 빠져 도움이 필요하다면 모든 자바스크립트 전문가가 알아야 하는 다음과 같은 몇 가지 개념을 살펴보라.
자바스크립트 비밀 1: 클로저(Closures)
자바스크립트의 클로저는 함수에 부모 함수의 범위에 대한 접근성을 제공한다. 개발자에게는 극히 혼란스러운 개념이다. 필자는 자바스크립트를 배울 당시 의도하지 않은 클로저로 인해 코드를 디버깅하는 데 몇 시간을 소비한 적이 있다. 이런 실수를 통해 배운 이후에는 클로저가 상당히 멋지다고 생각했고 다시 몇 시간 동안 클로저를 사용하여 문제를 해결하려 헛되이 노력하기도 했다.
결국, 배운 것은 클로저는 부모 범위에 대한 접근성을 가진 안쪽 함수(아래 고차 함수 참조)를 반환할 때 유용하다는 점이다. 이로써 변수를 보관하기 위한 일종의 비공개, 또는 보호된 환경이 만들어진다. 아래 예에서 count는 threeTimer가 호출될 때 접근 및 증분되며, 점이나 괄호 기호를 통해 직접 접근할 수는 없다.
// **Closures**
function makeThreeTimer(){
var count = 0;
return function(){
if(count < 3){
console.log('doing work');
count++;
}
else {
throw new Error('No more work');
}
}
}
var threeTimer = makeThreeTimer();
threeTimer(); // logs 'doing work' (count gets incremented)
threeTimer(); // logs 'doing work' (count gets incremented)
threeTimer(); // logs 'doing work' (count gets incremented)
threeTimer(); // throws an error
threeTimer.count; // returns undefined
자바스크립트 비밀 2: 고차 함수
기능적 프로그래밍 언어에서 함수는 1급 구성원이다. 다른 모든 값과 마찬가지로 전달할 수 있으며 이로써 흥미로운 가능성이 열린다. 고차 함수는 다른 함수를 소비 또는 생산하는 함수를 말한다. call 또는 bind와 같은 function 프로토타입의 메소드는 모두 고차 함수다. 커링(currying), 메모이제이션(memoization)과 같은 기법을 고차 함수로 표현할 수 있다. 자바스크립트에서 고차 함수를 사용하면 관점 지향 프로그래밍(Aspect-oriented programming)도 가능하다.
자바스크립트 비밀 3: 함수 호출
함수를 호출하는 가장 일반적인 방법(괄호 연산자를 사용하는 방법)을 이해한 다음에는 call과 apply를 사용하는 방법을 배울 차례다. 괄호 연산자 대비 call/apply를 사용할 때의 장점은 함수가 실행되는 컨텍스트를 지정할 수 있다는 점이다(this의 값). 이러한 형태는 고차 함수, 특히 이러한 고차 함수가 나중에 실행되는 함수를 소비할 때 볼 수 있다. Function 프로토타입에서 bing 메소드의 내부는 call/apply의 훌륭한 예다.
// Possible implementation of bind using apply
function bind(func, context){
return function(){
func.apply(context, Array.prototype.slice.apply(arguments));
}
}
자바스크립트 비밀 4: this는 무엇인가?
this 키워드는 많은 자바스크립트 개발자에게 큰 장애물로, 어떤 사람들은 아예 이 키워드를 외면한다. 필자가 지금까지 접한 이 키워드에 대한 최고의 설명은 예후다 캐츠가 함수 호출에 대한 블로그에 쓴 글이다. call/apply 또는 bind를 사용하지 않을 경우 this 값은 다음과 같은 경우를 제외하고 항상 전역 객체를 참조한다.
1. 문제의 함수가 new 연산자를 사용하여 호출된 경우, 현재 만들어지고 있는 새로운 객체를 가리킨다.
2. 문제의 함수가 객체의 멤버인 경우, 이 객체를 가리킨다.
단, 호출되는 함수가 클릭 핸들러 또는 setTimeout 등에서 비동기적으로 호출될 때는 2번 규칙을 무시해야 한다. 다음을 참고하라.
Person.getName(); // 'this' points to Person
setTimeout(Person.getName, 1000); // 'this' points to the global object
JavaScript secret No. 5: Protecting the global scope
자바스크립트 비밀 5: 전역 범위 보호
자바스크립트의 결함 중 하나는 페이지의 모든 스크립트가 공유되는 전역 컨텍스트에서 실행된다는 점이다. 이 결함이 악용될 경우 웹 사이트는 교차 사이트 스크립팅 공격에 취약하게 된다. 공유 전역 컨텍스트는 다른 문제도 일으킨다. 예를 들어 많은 스크립트가 한 페이지에서 실행되는데, 모든 스크립트를 개발자가 결정하는 것은 아니다(광고 스크립트 등이 대표적이다). 이 스크립트들은 전역 공간에서 실행되며 모두 같은 전역 변수에 접근할 수 있다. 이러한 스크립트 두 개가 같은 전역 변수를 사용하게 되면 서로 간섭하기 시작하고, 그러면 코드는 망가지게 된다.
전역 범위의 사용을 최소화하는 것은 간섭을 막고 긴 디버깅 시간을 줄이는 데 도움이 되는 방어적 기법이다. 변수를 전역적으로 저장하는 것을 완전히 없앨 수는 없지만 네임스페이싱과 같은 기법을 사용하면 최소화할 수 있다.
// **Namespacing**
// The global footprint is a single object containing
// all other references needed.
var MyApp = {};
MyApp.id = 42;
MyApp.utils = {
validate: function(){
//do work
}
};
또는 모듈 패턴도 있다.
// **Module pattern**
// This relies on immediately invoked anonymous functions and the
// closure they create to maintain access to variables within the
// function.
var MyApp = (function(){
//declare a local variable
var appId = 42;
function getAppId(){
//referring to a variable in this function's parent scope
return appId;
}
return {
getAppId: getAppId
};
}());
appId; // undefined
MyApp.appId; //undefined
MyApp.getAppId(); // returns 42. (Brought to you by closures)
JavaScript secret No. 6: Inheritance
자바스크립트 비밀 6: 상속
자바스크립트에서 상속은 여러 가지 이유로 긴 혼란의 역사를 갖고 있다. 대부분 개발자(필자가 만난 많은 자바스크립트 개발자들 포함)들은 클래스 모델을 충실하게 이해하지만, 프로토타입 모델에 대해서는 매우 혼란스러워한다. 프로토타입 상속을 사용하는 언어 목록을 살펴보고 나면 아마 이러한 현상을 이해할 수 있을 것이다.
자바스크립트를 제외한 다른 주류 언어에는 프로토타입 상속이 없다. 게다가 자바스크립트에서는 클래스 모델을 에뮬레이션하는 것이 가능하다. 그 결과 상속에 대한 접근 방식이 매우 다양하며, 이들 중 상당수가 독선적, 모순적이다. 의사 클래스 방식(pseudo-classical approach)은 자바스크립트의 함정 중 하나이므로 필자는 절대 사용하지 말 것을 제안한다. 대부분 개발자에게 친숙하게 보이겠지만 사실 근사치일 뿐이므로 금방 깨진다. editor@itworld.co.kr