블로그 내 검색

2012. 1. 18.

클로저(Closure) 사용에는 주의가 필요합니다

# 글을 쓴지 좀 됐는데 방문자가 아직 많네요 ~ 좋습니다 :)
# 곧 글을 대폭 수정할 예정입니다; 오래전에 쓴 글이라 잘못된게 많네요

전 포스팅에 이어 이번 포스팅에서는 클로저의 주의점에 대한 짧은 내용입니다.
클로저의 동작을 이해하기 위해서는 선행 지식이 조금 필요합니다.

자바스크립트의 유효 범위는 함수로 구분된다고 전 포스팅에서 말씀드렸습니다.

이것을 염두에 두고 새로운 사실을 알아봅시다.
자바스크립트에서 함수가 만들어질 때에는 일반적인 코드 구조나 블럭외에, 위에서 유효 범위를 탐색하기 위한 중요한 내부적 속성이 만들어 집니다.

이것을 스코프 체인(Scope Chain)이라고 합니다

이것은 ECMA-262(PDF 파일)에서 정의된 속성인 [[Scope]] 라는 속성으로 참조가 가능합니다. 이 [[Scope]] 속성은 프로그램 문법으로는 일반적으로 접근할 수 없는 속성이기도 합니다. (크롬 인스펙터 - 개발자 도구 - 등의 툴을 사용하면 볼 수 있습니다.)

이 속성은 일종의 스택(Stack)같은 컬렉션 형태로서, 변수 객체 (Variable Object)라는 키와 값의 집합 객체를 각 컬렉션 요소로 가집니다.

잠깐 정리하고 갑시다.

변수 객체 (Variable Object)
어떤 코드가 실행될 때 찾을 변수를 키와 값으로 가진 객체. 프로그램에서는 접근할 수 없습니다.

스코프 체인 (Scope Chain)
함수가 생성될 때 같이 생성되는 변수 객체의 컬렉션.
요소로 변수 객체를 가집니다.
변수를 찾을 때 이 컬렉션을 스택처럼 사용하게 됩니다.

[[Scope]] 
함수가 만들어질 때 생성되는 스코프 체인(Scope Chain)  참조하는 함수의 속성.
역시 프로그램에서 접근 불가능입니다.

스코프 체인
[0]변수객체
[1]변수객체
[2]변수객체

[스코프 체인의 구조는 대략 이렇습니다]

그럼 위 스코프 체인과 변수 객체를 이해하기 위해 아래와 같은 평범한 코드를 예로 들어 봅시다.

var v = "global_context";
function showVar(i) {
    return i+v;
}

별 의미없는 함수죠.
이것의 스코프 체인은 다음과 같은 구조일 것입니다.

showVar 함수의 Scope Chain 구조
[0] 글로벌 오브젝트 (변수 객체)
thisglobal(브라우저일 경우 window)
global(브라우저일 경우 window)구현 object
document구현 object
showVarshowVar 함수
기타 글로벌 객체들의 이름과 값들
...
etc
...

좀더 살펴봅시다.
자바스크립트에서 함수가 실행될 경우에 자바스크립트 엔진은 먼저 실행 문맥 (execution context) 을 생성합니다.
함수와 실행 문맥은 1:1이 아니며, 함수가 실행될 때마다 하나씩 생성됩니다.

실행 문맥은 실행기의 스택에 차례로 쌓이게 되고 실행되고 나면 파괴되기에 무한정 생길 염려는 없습니다. (간혹 과도한 재귀 호출이나 무리한 루프문으로 자바스크립트가 정지하면서 Stack Overflow 오류를 내는 경우가 있는데 이것은 이 실행 문맥이 무리하게 실행 스택에 쌓여서 발생하는 것입니다.)

실행 문맥은 함수를 실행시키기 위해 아주 중요한 사전 작업을 진행하는데 그중 하나는 그 함수가 가진 스코프 체인을 자신의 스코프 체인으로 복사하는 일입니다.
실행 문맥또한 스코프 체인을 통해 유효범위를 계산하기 때문이죠.

그 뒤 실행 문맥은 활성 객체 (Activation Object) 라는 변수 객체를 하나 만들어서, 자신의 스코프 체인의 맨 앞에 삽입합니다.
이 활성 객체는 함수 단위의 유효범위에서 정의된 지역 변수들과, 인자, this, arguments 등의 특수한 변수까지 포함한 변수 객체입니다.

다시 용어 두개가 등장했네요.
정리하고 넘어가죠.

실행 문맥 (Execution Conext)
코드가 실행될 때마다 생성되는, 실행 환경을 정의하고 실제 실행하는 내부 객체.
실행 후 파괴되어 없어집니다.

활성 객체 (Activation Object)
함수가 실행될 때 실행 문맥에 의해 생성되며, 함수 자체에 대한 변수 객체가 됩니다.
때때로 호출 객체 (Call Object) 라고도 불립니다.
인자, 지역변수, this, arguments등이 포함되어 있고 스코프 체인의 맨 앞에 옵니다.

실행 문맥은 코드를 실행하면서 변수나 프로퍼티를 만날 경우 실행중인 영역이 가진, 스코프 체인을 순서대로 넘기면서 해당 변수와 프로퍼티를 찾아 값을 참조하면서 코드를 실행시킵니다.

showVar("This is ") 이라고 실행한다면 showVar 실행 문맥의 스코프 체인은 다음과 같을 것입니다

showVar 실행 문맥의 Scope Chain 구조
[0] 활성 객체 (변수 객체)
thisglobal(브라우저일 경우 window)
arguments[ "This is" ]
i"This is"
[1] 글로벌 오브젝트 (변수 객체)
thisglobal(브라우저일 경우 window)
global(브라우저일 경우 window)구현 object
document구현 object
showVarshowVar 함수
기타 글로벌 객체들의 이름과 값들
...
etc
...

함수 showVar가 실행되고, return i + v; 라는 문장을 만났습니다. 자바스크립트는 i라는 변수를 찾기 위해 제일 먼저 실행 문맥의 스코프 체인 맨 앞의 변수객체, 즉 위 구조를 참고한다면 활성 객체를 뒤집니다.

활성 객체에서 변수 i를 찾을 수 있습니다. 사용합니다.
그 다음 v를 찾습니다. 그런데, 활성 객체에는 그런데 v가 없습니다. 그렇다면 그 다음의 변수 객체인 글로벌 오브젝트를 뒤지게 됩니다.
글로벌에는 v가 있습니다. 그럼 그 변수를 참조하고, 성공적으로 값을 리턴하게 될 것입니다.

이제 대충 함수 실행 시, 변수 객체와 실행 문맥, 그리고 유효 범위를 검색하는 방법에 대해 이해하셨을 것 같습니다. 프로토타입에 대해 좀 아시는 분이라면 이 방식이 프로토타입 체인 검색과 상당히 유사하다는 것도 아시게 될 듯 합니다.
(이 블로그의 프로토타입 포스팅을 보고 싶으면 이곳으로)

만일 함수가 여러개 중첩되거나, eval, try-catch, with 등의 동적 스코프 작성 구문이나 함수를 사용한다거나 하면 (eval, try-catch, with 등의 문법이 스코프 체인에 어떠한 영향을 주는지는 여기서 설명하기엔 너무 길어집니다. 기회가 있다면 포스팅해 보겠습니다), 스코프 체인의 크기는 늘어나게 되며, 변수를 찾을 때 뒤지는 스코프 체인의 변수 객체도 그에 맞춰 늘어납니다.

그에 따라 스코프 체인을 검색하는 시간은 늘어나고, 당연히 스크립트의 효율은 떨어집니다.
사용자는 굳어버린 화면을 바라보며 복잡한 표정을 짓겠죠;

이제 클로저가 왜 안좋은 영향을 주는지 알아볼 때군요.
클로저의 경우 이 스코프 체인의 구성이 특이하게 진행됩니다.

function hello() {
    var f = "hello, ";
    function world() {
        alert(f + "world");
    }
    return world;
};
var say = hello();

이러한 클로저를 사용하는 함수가 있다고 가정합니다.

hello를 실행할 경우의 스코프 체인은 약간 복잡합니다.
실행시에 역시 hello를 실행하기 위한 실행 문맥이 생성됩니다. 그리고 미리 생성된 hello의 스코프 체인에 있는 글로벌 변수객체가 먼저 삽입된 뒤, 생성된 활성화 객체가 차례로 스코프 체인에 삽입될 것입니다.

그리고 이제 실행 결과로 얻어진 클로저 say의 스코프 체인도 생성됩니다.
say 클로저의 스코프 체인에는 글로벌 객체가 먼저 삽입된 뒤, 그 다음 실행 문맥이 생성한 hello의 활성화 객체가 변수 객체로 삽입됩니다.

함수 say의 스코프 체인의 구조는 다음과 같을 것입니다

say의 Scope Chain 구조 (Closure 생성 시)
[0] hello 함수가 실행될 때 생성된 활성화 객체
thisglobal(브라우저일 경우 window)
arguments[ ]
f"hello"
worldworld 함수
[1] 글로벌 오브젝트
thisglobal(브라우저일 경우 window)
global(브라우저일 경우 window)구현 object
document구현 object
hellohello 함수
sayundefined
alert내장 함수
기타 글로벌 객체들의 이름과 값들
...
etc
...

여기서 문제 하나가 나옵니다.
활성화 객체는 보통 함수가 실행될 때 실행 문맥에 의해 만들어지고, 활성화 객체가 파괴될 때 같이 없어지는 인스턴트 객체입니다.

그런데 클로저에 쓰일 경우 클로저의 스코프 체인에 활성화 객체가 속하게 되면서 실행 문맥이 끝나도 활성화 객체는 남아있습니다. 클로저가 다수 생성되고, 그것을 해제해주지 않는다면 다수의 활성화 객체가 생성된채로 남고 이것은 문제가 됩니다.

이것이 클로저를 남용할 때 오는 첫번째 문제입니다.

이제 클로저 say를 실행해 봅시다.
클로저 say가 실행되면 역시 실행 문맥이 만들어지고 say의 스코프 체인을 실행 문맥의 스코프 체인에 복사한 뒤, say를 위한 새로운 활성화 객체가 생성되고 스코프 체인의 앞에 삽입됩니다. 다음과 같습니다.

say 실행 문맥의 Scope Chain 구조 (Closure 실행 시)
[0] say 함수가 실행될 때 생성된 활성화 객체
thisglobal(브라우저일 경우 window)
arguments[ ]
[1] hello 함수가 실행될 때 생성된 활성화 객체
thisglobal(브라우저일 경우 window)
arguments[ ]
f"hello"
worldworld 함수
[2] 글로벌 오브젝트
thisglobal(브라우저일 경우 window)
global(브라우저일 경우 window)구현 object
document구현 object
hellohello 함수
sayundefined
alert내장 함수
기타 글로벌 객체들의 이름과 값들
...
etc
...

다른 일반적 함수와 다르게 스코프 체인이 하나 더 늘었을 뿐 아니라 자주 접근해야 할 글로벌 오브젝트 변수 객체가 제일 마지막입니다.

결과적으로 alert을 찾기 위해서 스코프 체인을 두개 다 뒤진 뒤에야, alert을 찾고 실행할 겁니다. 당연히 성능은 저하됩니다. 주의해야 할 점이죠.

요약해 봅시다.

  • 클로저는 클로저를 생성한 함수의 활성화 객체를 그대로 가지고 있게 되어 의도치 않은 메모리 낭비가 발생할 수 있다.
  • 클로저 실행 시, 불필요한 스코프 탐색을 하게 되어 성능이 나빠진다.

좋은 점이 있다면 나쁜 점도 있기 마련입니다.
이런 점들이 있다고 해서 클로저를 아예 안쓰거나 버리는 것 또한 바보짓입니다.

절대 남용하지 말고 변수 스코프를 잘 고려한 변수 위치 선정등으로 스코프 체인 탐색을 줄여가면서 클로저를 활용하는 기법을 익힌다면 좀더 자바스크립트 코딩이 즐거워질 지 모릅니다(?)

포스팅 내용을 좀 더 자세히 알아보고 싶다면 다음 책을 추천드립니다.
더글라스 크락포드의 자바스크립트 핵심 가이드자바스크립트 성능 최적화

기회가 되면 실제 스코프 체인의 구조를 크롬 인스펙터(크롬 개발자 도구)를 사용하여 탐색하면서 여러 경우의 스코프 체인 구조를 확인해보는 포스팅을 해보겠습니다.

마지막으로 문제 하나 던져 보면서 포스팅을 마칩니다.
이 문제의 실행 결과를 정확히 예측했다면 스코프 체인을 완전히 이해했다고 봐도 될 것 같네요.

// 결과로 찍히는 경고창의 문자열은 뭘까요?
var word = "global";
function showVar() {
    return word;
}
function receiveFunc(showVar) {
    var word = "activate";
    alert(showVar());
}
receiveFunc(showVar);

답 보기

2012. 1. 17.

자바스크립트의 클로저 (JavaScript's Closure)

# 글을 쓴지 좀 됐는데 방문자가 아직 많네요 ~ 좋습니다 :)
# 곧 글을 대폭 수정할 예정입니다; 오래전에 쓴 글이라 잘못된게 많네요

자바스크립트를 조금 공부하다보면 바로 맞딱뜨리는 개념중 하나가 클로저 (Closure)라는 개념입니다.
클로저는 하늘에서 뚝 떨어진 용어도 아니고 프로그래밍 언어에 있는 용어중 하나로서, 언어마다 조금씩 다른 구현과 특성을 가지고 있는 재미있는 개념이죠.

글로 쓰게 되니 더욱 어렵습니다.
위 문장들은 결과적으로는 다 같은 뜻입니다.

그럼 예제 코드를 보죠.
클로저의 예를 들때 많이 예시가 되는 시퀀스 함수입니다.

var sequencer = function() {
    var s = 0;
    return function() {
        return ++s; 
    }
};

/*
seq 함수는 이런 구조를 가지고 반환될 것입니다
function seq() {
    return ++s;
}
*/
var seq = sequencer();

seq(); // 1
seq(); // 2
seq(); // 3

보통의 언어라면 말도 안되는 코드입니다.
이미 sequencer 내부의 s 변수는 다른 언어로 치면 유효범위를 벗어난 쓸 수 없는 변수입니다. 자바같은 가비지 컬렉터를 가진 언어에서는 바로 가비지 컬렉팅의 대상이 되겠지요.

그러나 자바스크립트에서는 다릅니다.

내부 함수는 자신이 선언된 환경에 대해 연결을 갖습니다.
그래서 위 예제에서 반환된 함수는 자신이 선언될 때의 환경인 sequencer 의 유효범위에 대한 연결을 갖게 되며, 호출하게 되면 그 연결을 통해 s 변수에 "직접" 접근합니다.

여기서!!!
변수를 절대 복사하는게 아님을 유의하세요.
변수가 있는 객체( - Variable Object 라고 부릅니다)를 참조하는 것입니다.

만일 s 변수를 복사했다면 반환되는 값은 항상 1이어야할 것입니다.
복사된 변수는 0일테고, 그것에 대해 1이 증가한 값을 반환할 테니까요.

잘 이해가 안되시면 클로저 설명시 자주 언급되는 함수 그 두번째인 클릭시 자신의 순서를 반환하는 핸들러를 붙이는 예제를 봅시다.

// 모든 LI 태그를 가져와서,
// 클릭시 자신의 순번을 표시하는 예제(?)
var items = document.getElementsByTagName("li");
for(var i=0; i < items.length; i++) {
    items[i].onclick = function(event) {
        alert("My Sequence is " + (i+1)); // 자신의 순번 출력
    }
}

여기서 프로그래머가 기대한 값은 <LI> 태그를 클릭할 때 자신이 문서에 사용된 순서를 출력하는 것일 겁니다. 그러나 여기서 출력하는 값은 LI의 제일 제일 마지막 순번의 값에 1을 더한 값만이 출력됩니다.

만일 문서의 LI 태그가 4개라면 5만을 출력할 것입니다.

값이 복사된 것이 아닌 변수에 직접 접근하고 있기 때문입니다.
for문이 종료되는 시점의 i값은 4이며, 핸들러들은 그것에 직접 접근하여 1을 더한 값만을 출력하게 되는 것이죠.

위의 함수가 제대로 동작하도록 고쳐봅시다.

var items = document.getElementsByTagName("li");
for(var i=0; i < items.length; i++) {    
    (function() {   // 새로운 스코프 선언
        var idx = i; // 클로저가 접근할 변수 선언
        items[i].onclick = function(event) {
            alert("My Sequence is " + (idx+1)); 
        }
    })();
}

이렇게 고쳐 쓰면 원하는대로 동작합니다.
이벤트 함수인 클로저가 참조하는 대상이 i가 아닌 새로운 스코프의 idx가 되었기 때문이죠.
(어렵게 말한다면, 실행 컨텍스트의 스코프 체인 탐색 방법에 따라 참조 순위에서 변수 i는 뒤로 밀립니다)

익명 함수를 쓰는게 좀 어색해 보일 수 있겠지만 자바스크립트에서는 저것이 명시적으로 블럭 스코프를 선언하는 방법입니다.

클로저가 막강한 문법이긴 하나, 주의할 점이 있습니다.
함수가 메서드로 호출될 때, 외부 함수의 this키워드와 특수한 변수인 arguments에는 정상적인 접근이 되지 않습니다.
정확히는 접근이 되나 저 둘에 대해서는 메서드를 소유한 객체의 this 에 연결되지 못합니다.

아래 코드를 봅시다.

window.name = "window";
var object = {
    name: "object",
    getName: function() {
        function findName() {
            return "my name is " + this.name;
        }
        return findName();
    }
}
object.getName(); // my name is window.

위의 코드는 의도와는 다른 동작을 보입니다.
my name is object 가 아닌 my name is window 가 출력됩니다.

메서드 내부의 함수가 실행될 때 this가 메서드를 소유한 객체에 연결된 것이 아닌, 글로벌 객체에 연결되었기 때문입니다. 난감한 결과입니다.

더글라스 크락포드는 이러한 동작을 보고 아래처럼 한마디 했죠.

이건 명백히 설계상의 실수라고 할 수 있습니다.
 - 더글라스 크락포드

내부 함수가 외부 함수를 돕지 못하다니...
하지만 걱정하지 않아도 됩니다. 보통 이런 경우 관습적으로 사용하는 용법이 있습니다.

// 위 코드에서 getName 부분입니다
getName: function() {
    var that = this; // this를 따로 변수에 할당해둬 내부 함수가 접근 가능하도록 한다.
    function findName() {
        return "my name is " + that.name;
    }
    return findName();
}

이렇게 this를 따로 변수에 할당해 두는 방법입니다.
이렇게 하면 자신의 이름이 잘 출력됩니다.

여기까지 읽고 머리가 좋으시거나, 자바스크립트의 스코프에 잘 이해하고 계신 분이라면 한가지를 알 수 있을거라 생각합니다.

그렇습니다.
자바스크립트의 모든 함수는 클로저입니다.

클로저를 잘 익히고 사용할 줄 알아야 자바스크립트를 좀더 유연하게 다룰 수 있습니다.
언어적 특성을 잘 이해하고 다룰 수 있으면 자바스크립트 코딩이 더욱 재미있어질 거라고 생각합니다.

...
...
...

이대로 끝내긴 좀 아쉬우니 클로저에 대해 한마디 덧붙입니다.
클로저는 이러한 유연성과 활용의 유연성 등 많은 강점을 가지고 있지만, 그것에 가려진 안좋은 점도 상당합니다.

클로저의 안좋은 점에 대해서는 다음 포스팅에 하도록 해보겠습니다.

2012. 1. 13.

자작 자바스크립트 문제 해설

기존에 포스팅한 자바스크립트 퀴즈에 대한 해설입니다.
퀴즈 1
var name = "javarouka";
var introduce = function() {
    var intro = "My name is ";
    if(!name) {
        var name = "unknown"
    }
    return intro += name;
}
document.write(introduce());
변수 선언과 할당에 대해 묻는 문제입니다.
자바스크립트에서는 변수가 선언되는 시점은 영역이 생성될 때이지만, 할당은 런타임시에 일어납니다.

문제에서는 전역스코프에 name 이 선언되어 있지만 함수 안에서 name이 다시 정의되어 있으므로 자바스크립트의 스코프 체인 탐색 방법에 따라 전역의 name을 가립니다. (variable shadow)

여기서 다르게 설명하자면,
function 스코프 안에서 var로 선언된 모든 변수는 정의만 위로 끌어 올려져(호이스팅) 아직 값을 할당받지않은 undefined 상태. (댓글 달아주신 익명님 감사합니다.)
위 설명에 따라, 아직 name에 할당하기 전이므로 값은 undefined가 되며 false로 평가되고, "unknown"이 할당됩니다. 그리고 문자열을 반환하게 되므로

답은 "My name is unknown" 가 됩니다.

이런 선언이 해석시 먼저 일어나는 것을 끌어올려지는 것과 비슷하다고 하여 Hoisting이라고 부릅니다.

퀴즈 2
document.write(myName() + " / ");
var myName = "javarouka";
function myName() {
    return "rouxrouka";
}
delete myName;
document.write(myName);
함수 선언문이 특별하게 동작하는 것과 delete 연산자의 기능에 대해 묻는 문제입니다.
함수 선언문은 변수와 달리 생성시 Hoisting됨과 동시에 선언까지 완료됩니다.
myName 함수는 이미 생성시 선언과 할당이 완료되어 있고 첫번째 줄은 성공적으로 수행됩니다.

두번째 줄에 가서 글로벌에 선언된 myName 변수를 덮어쓰며, 다시 delete로 그 변수를 삭제하려 시도하지만 var로 선언된 변수는 언어 규약상 삭제불가 속성이 붙게 됩니다.

따라서 마지막 줄도 성공적으로 출력합니다.

그래서 답은 "rouxrouka / javarouka"

퀴즈 3
(function(doc) {
    document.write(new doc() === doc());
    document.write(new doc() === document);
    document.write(doc() === document);
})
(function arg() {
    return document;
})
자바스크립트의 생성자 함수의 동작 과정에 대해 이해하고 있는지 문제입니다.
일견 복잡해 보이는데, 풀어쓰면 간단합니다.

함수가 생성자로 쓰일 때 객체가 만들어지는 과정은,

1. 새로운 빈 객체를 생성합니다.
2. 빈 객체의 프로토타입 링크에 생성자 함수의 프로토타입 객체를 연결합니다.
3. 생성자 함수의 this 에 위에서 만든 객체를 바인딩 한 뒤 함수를 실행합니다.
4. 1번에서 만든 객체를 반환하지만, 만일 생성자 함수에서 객체를 반환하면 그것을 반환합니다.

이것으로 문제를 해석하면 간단합니다. 모든 조건문이 결과적으로 모두 같은 객체인 document를 비교하고 있습니다.

따라서 답은 "truetruetrue"

퀴즈 4
function setter(aryUnits) {
    for(var i=0; i < aryUnits.length; i++) {
        var id = i + 1;
        aryUnits[i] = new Object();
        aryUnits[i].getId = function() {
            return id;
        }
    }
}
var ary = new Array(5);
setter(ary);
document.write(ary[3].getId());
자바스크립트의 객체가 참조 타입이라는 것과 클로저의 스코프에 대해 묻는 문제입니다
빈 5만큼의 길이를 갖는 배열을 선언한 뒤 함수에 인자로 주고 있습니다.

참조 타입으로 전달되므로 함수에서 배열에 어떠한 조작을하면 리턴값이 없어도 인자의 배열은 내용물이 변합니다.

함수 안에서는 getId라는 함수를 가진 객체를 할당하고 있지만, 클로저의 스코프상 모든 배열객체들은 같은 메모리상의 id를 참조할 겁니다.

for 문이 종료되며 id 변수는 5가 할당되고, 배열의 모든 객체의 getId는 5를 반환합니다.
답은 따라서 가 됩니다.

퀴즈 5
function args() {
    return (typeof arguments) && (arguments instanceof Array);
}
document.write(args());
실행 객체의 특수한 변수중 하나인 arguments에 대해 묻는 문제입니다
arguments는 유사 배열로서 length를 가지고 있지만 배열이 아닙니다.

따라서 답은 false

퀴즈 6
var c;
document.write(typeof typeof c);
자바스크립트의 해석순서와 typeof의 반환값에 대해 묻는 문제입니다.
typeof는 왼쪽에서 순서대로 연산을 처리하여 선언만 된 변수 c의 타입을 반환하는데 이 경우 문자열 "undefined"가 되며,
이것을 다시 typeof하면 "string"입니다.

퀴즈 7
var privacy = {
    secret: "I did not study last night.",
    getSecret : function() {
        return this.secret;
    }
};
var what = privacy.getSecret;
document.write(what());
키워드 this에 대해 묻는 문제입니다.
what 변수에 함수를 할당하여, 전역 컨텍스트에서 함수를 실행했으므로 해당 함수내의 this는 글로벌 객체가 되고,
글로벌 객체에는 secret이라는 프로퍼티가 없으므로 undefined 가 됩니다

퀴즈 8
function func1( ) { return func1; }
if(new func1() === func1()) {
    document.write("same!");
}
else {
    document.write("different!");
}
문제만 괜히 꼬아 낸 퀴즈3의 복습입니다.
func 함수의 반환값과 생성자로 사용하여 반환된 객체는 같습니다.

답은 "same!"

퀴즈 9
(function(){
    Object.prototype.scope = "prototype";
    (function(f) {
        var scope = "local";
        alert(f());
    })(function checkScope() { return scope; });
})();
익명 함수가 다수 쓰여있어 괜히 복잡해 보입니다. 함수의 스코프 체인에 대해 묻는 문제입니다.
이것은 자칫 잘못하면 일반적인 클로저의 스코프 사용같이 보이지만 전혀 아닙니다.

클로저의 정의를 다시 생각해 봅시다.
생성 당시의 스코프에 대한 연결을 갖는 블록입니다. checkScope는 함수 밖에서 생성되어 있습니다.
그리고 checkScope는 프로토타입 연결에 "prototype" 이 지정되어 있습니다.
checkScope의 스코프 체인에는 scope 변수따윈 없습니다. 프로토타입 연결에 존재하죠.

그럼 따라가 봅시다. 첫 익명함수 블록은 건너 뜁시다. 의미 없습니다.
그다음 줄에서 Object의 프로토타입에 scope라는 프로퍼티를 선언하고 "prototype" 이라는 변수를 할당했습니다.
그 다음 다시 익명함수 블록인데 인자로 scope라는 변수를 반환하는 함수를 인자로 주고 있습니다.

익명함수 안에서는 인자로 넘겨진 함수를 실행하는데, 함수 scopeCheck의 스코프 체인상에는 익명함수의 영역은 포함되지 않으며, 더구나 실행 컨텍스트 또한 익명함수의 실행컨텍스트가 아닌 글로벌 컨텍스트이므로 함수 scopeCheck에서는 변수 scope의 존재조차 모릅니다. 따라서 기본 영역에서 변수 scope를 찾을 수 없으므로 프로토타입 연결을 따라 찾은 prototype 문자열을 반환합니다.
여기서 만일 Object.prototype.scope = "prototype"; 문장이 없다면 실행 오류가 나게 됩니다.
undefined 변수를 사용하려 하기 때문이죠.

답은 "prototype"

퀴즈 10
var strZero = "0";
var numZero = 0;
var notNumer = NaN;
var strEmpty = '';
var tab = '\t';
var undef;
var nll = null;
if(strZero == true) {
    document.write("1");
}
if(notNumer == false) {
    document.write("2");
}
if(undef == nll) {
    document.write("3");
}
if(tab == 0) {
    document.write("4");
}
if(tab == true) {
    document.write("5");
}
if(strEmpty == numZero) {
    document.write("6");
}
if(false == 'false') {
    document.write("7");
}
if(strEmpty == numZero) {
    document.write("8");
}
자바스크립트의 동등 연산자 "==" 에 대해 묻는 문제입니다.
0, "0", "", "\t", false, undefined, null, NaN 은 불린 평가에서 false로 평가됩니다.
하지만 특이하게도 0과 NaN이 동등하지 않으며, null과 false가 동등하지 않지만 그렇지만 "" 과 false는 동등하는 둥....
어리둥절한 모습도 많습니다.

이러한 골치아픈 문제의 해결책은 일치 연산자 "===" 를 사용하는 것이죠.
방문자분이 자세한 설명을 덧붙여 주셨네요. 감사합니다.
/*
== 비교연산
1. 타입이 같으면 그냥 비교, 둘다 숫자,문자열,불리언...
2. null 과 undefined 는 그 둘끼리만 true.
3. 숫자와 문자열,불리언 섞이면 숫자로 변환한 후 비교
4. NaN값과 비교는 무조건 false. NaN == NaN 도 false.
5. 객체와 문자열,숫자 비교는 객체의 valueOf, toString 시도 후 비교
 */
//표현식 평가 ex) x=' '; if(x){ 이것은 참 }
//null, undefined, '', 0, false, NaN 값만 false로 변환되고 나머지는 모두 true.

//비교연산은 자동 형변환이 일어난다.
if(4 == '4'){ // 문자열,불리언은 숫자로 캐스팅 4 == 4 true
    console.log('1'); 
}
if(true == '0'){ // 문자열,불리언은 숫자로 캐스팅 1 == 0 false
    console.log('2'); 
}
if(true == '1'){ // 문자열,불리언은 숫자로 캐스팅 1 == 1 true
    console.log('3'); 
}
if(true == 'true'){ // 문자열,불리언은 숫자로 캐스팅 1 == 'true' false
    console.log('4'); 
}

//빈문자열, 공백문자 \t \v \n \r \f ' ' 는 숫자 0으로 캐스팅
if('\t' == 'false'){ // '\t' == 'false' false (같은타입 변환없슴)
    console.log('5'); 
}
if(' ' == true){ // 0 == 1 false
    console.log('6'); 
}
if('' == 0){ // 0 == 0 true 
    console.log('7'); 
}
if('1' > true){ // 문자열,불리언은 숫자로 캐스팅 1 > 1 false
    console.log('8'); 
}
if('NaN' == NaN) { // NaN 은 무조건 비교불가 false리턴 isNaN()으로 비교 false
    console.log('9'); 
}
if(false == undefined) { // null, undefined 는 무조건 그 둘끼리 비교만 참 false
    console.log('10'); 
}
if(null == false){ // null, undefined 는 무조건 그 둘끼리 비교만 참 false 
    console.log('11'); 
}
if(null == undefined){ // null, undefined 는 무조건 그 둘끼리 비교만 참 true
    console.log('12'); 
}

//표현식 평가
//null, undefined, '', 0, false, NaN 값만 false로 변환되고 나머지는 모두 true.
if(' ' && '0' && '\t' && 'false') {
    console.log('모두 참'); 
}
if(new Number(0) && new Boolean(false)) { // 객체는 null이 아니면 무조건 참
    console.log('15'); 
}
if(a == null){ // a == 반드시 null or undefined
    console.log('16');
}
if(!a) { // a == 반드시 '', 0, false, null, undefined, NaN
    console.log('17'); 
}
if(a) { // a == 반드시 '', 0, false, null, undefined, NaN 아닌값.
    console.log('18'); 
}
문제로 돌아가서, 하나하나 따라가다 보면 답은 "3468" 입니다.


사실 여기 예로 든 문제들은 실 업무에서는 거의 부딪힐 일이 없는 케이스가 대부분입니다. 하지만 간혹 일어나는 애매한 스크립트의 오류를 잡으려면 기본 지식은 있는 편이 좋지 않을까요?

2012. 1. 12.

Dart Language 맛보기

웹 클라이언트 사이드의 독보적인 언어는 역시나 자바스크립트일 것이다.
최근 웹이 부각되면서 더욱 각광받는 언어이기도 하다.
구글에서도 다수의 서비스를 자바스크립트로 멋지게 구현해냈었고 지금도 구현중(일 것이다)이다.

그런데 뜬금없이 구글은 자바스크립트가 지금 해 내고 있는 일을 할 수 있는 웹 클라이언트 언어를 한창 개발중이라고 발표했다. (사실 발표한지는 좀 됐다.)

그 이름은

Dart

아직 한창 개발중이며 다트 그룹스에서는 수많은 개발자들이 개발에 참여하여 피드백을 보내고 있다.

다트 공식 페이지는 이곳.(http://www.dartlang.org)

개발 방향은
  • 보다 유연한 웹 구조적 프로그래밍 언어
  • 친근한 문법으로 쉽게 익힐 수 있는 언어
  • 빠른 실행과 높은 성능을 내는 언어
  • 다양한 디바이스(모바일, 태블릿, 노트북, 심지어는 서버)까지 아우르는 언어
  • 모든 현대적 브라우저에서 빠른 실행을 제공
이라고 한다.
이것만 보면 정말 꿈의 언어지만, 실제는 나와봐야 알 수 있겠다.

아직 개발중이라 직접 브라우저에서 사용할 수는 없고 별도의 컴파일 과정을 거쳐 자바스크립트 파일로 변환해야 한다.

자바스크립트 변환은 다트 에디터에서 가능하다.

다트 에디터는  다트 에디터만을 위해 기능이 제한된 이클립스 기반 프로그램으로 자바 버전 7이 설치되어 있어야 구동할 수 있다.

공식 페이지에서 다운로드 받을 수 있으며 간단하게 설치 가능하다.


에디터에서 실제 코딩하면 자바스크립트 파일이 컴파일되어 나온다.

생성된 자바스크립트 파일은 기본 다트 프레임워크 자바스크립트 코드 + 사용자가 코딩한 내용을 프레임워크에 맞춰 변환된 자바스크립트 코드가 합쳐져 있는데, 아무래도 자바스크립트 결과물은 기본 프레임워크 코드덕분에 아무리 다트 코드를 짧게 한다고 해도 일정량의 크기는 가질 수 밖에 없나보다.

이런 컴파일 방식 말고도 공식 사이트에서는 다트의 사용 방법은 현재 총 4가지를 제안하고 있다

1. 다트 문법으로 개발한 뒤 그것을 컴파일하여 자바스크립트 파일 생성
2. 공식 사이트의 다트보드
3. (미구현) 다트 VM을 사용자 컴퓨터에 설치하고 바로 사용
4. (미구현) 새로운 마임타입으로 사용

2번 방식은 링크를 타고 들어가면 샌드박스 안에서 다트 프로그래밍을 해 볼 수 있다.
4번은 제일 친숙한 방식으로 자바스크립트가 스크립트 태그에 타입을 "text/javascript" 라고 선언하듯이 처럼 새로운 마임타입인 "application/dart" 으로 실행하는 것이다.
그러나 아직 미구현이다.

문법을 조금 살펴보면 클래스 기반 객체지향 언어이다.
Class, Inteface가 있으며 현재 트렌드에 맞게 타입과 언타입 둘다 지원하고 있다.
그리고 공용 다양한 라이브러리를 제공하며 개발자가 직접 라이브러리를 만들 수도 있다.

직접 타이핑한 다트의 예제 코드이다.

interface Animal {
    bool isAlive();
}

class People implements Animal {
    final  num age;

    // 생성자 단축 문법. 바로 멤버변수에 인자를 할당한다.
    People(num this.age);

    bool isAlive() {
        return this.age < 100;
    }

    // 함수 반환 단축 문법. 해당 변수를 바로 반환.
    num getAge() => age;
}

// 보통 언어처럼 메인 함수가 진입점이다
main() {
    var javarouka = new People(33);
    print("javarouka'age is " + javarouka.getAge() + " years old");
    print(javarouka.isAlive());
} 

생성자의 멤버변수 단축 선언이나 함수 반환값의 지정 등 몇가지 재미있는 문법이 눈에 띈다.

...
...
...

다트가 앞으로 어떻게 발전해 나갈 지는 알 수 없다.

하지만 확실한 것은 구글측에서도 현재 자바스크립트를 어찌해 보겠다는 생각은 아닐것이다.

다트에 사용된 기술이 나중에 자바스크립트 표준으로 흡수될수도 있고 (MS의 JScript가 그랬던 것처럼 말이다), 반대로도 서로 작용하면서 다트가 발전되고 결국 다시 그것이 자바스크립트에 피드백되는 상부상조하는 방향으로 발전하지 않을까 하는 조심스런 예측을 해본다.

다트 프로그래밍 언어 수석 엔지니어의 인터뷰 링크를 걸어둔다

인터뷰 : 구글 다트가 자바스크립트보다 우월한 이유

2012. 1. 11.

자작 자바스크립트 퀴즈

자바스크립트에서 애매하고 착각하기 쉬운 문법에 대해 다시 복습하고 그것을 철저하게 설명하고 넘어갈 수 있나 체크해보는복습 차원에서 만들어본 퀴즈입니다.
공부가 깊지 않아 다소 틀린 점이 있을수도 있습니다.

퀴즈에는 @kangax : Javascript quiz 씨의 퀴즈도 많이 참고했습니다.
관심이 있으신 분은 이쪽도 가보세요.
해설은 다음 포스트에 하겠습니다

Quiz 1

var name = "javarouka";
var introduce = function() {
    var intro = "My name is ";
    if(!name) {
        var name = "unknown"
    }
    return intro += name;
}
document.write(introduce());
  • "My name is undefined"
  • "My name is null"
  • "My name is javarouka"
  • "My name is unknown"

Quiz 2

document.write(myName() + " / ");
var myName = "javarouka";
function myName() {
    return "rouxrouka";
}
delete myName;
document.write(myName);
  • 실행 오류
  • "rouxrouka / javarouka"
  • "undefined / javarouka"
  • "undefined / undefined"

Quiz 3

(function(doc) {
    document.write(new doc() === doc());
    document.write(new doc() === document);
    document.write(doc() === document);
})
(function arg() {
    return document;
})
  • 실행 오류
  • "falsefalsefalse"
  • "falsefalsetrue"
  • "falsetruetrue"
  • "truetruetrue"
  • "truefalsefalse"
  • "truetruefalse"

Quiz 4

function setter(aryUnits) {
    for(var i=0; i < aryUnits.length; i++) {
        var id = i + 1;
        aryUnits[i] = new Object();
        aryUnits[i].getId = function() {
            return id;
        }
    }
}
var ary = new Array(5);
setter(ary);
document.write(ary[3].getId());
  • 실행 오류
  • undefined
  • 4
  • 5

Quiz 5

function args() {
    return (typeof arguments) && (arguments instanceof Array);
}
document.write(args());
  • 실행 오류
  • "object"
  • true
  • false

Quiz 6

var c;
document.write(typeof typeof c);
  • "undefined"
  • "object"
  • 실행 오류
  • "string"

Quiz 7

var privacy = {
    secret: "I did not study last night.",
    getSecret : function() {
        return this.secret;
    }
};
var what = privacy.getSecret;
document.write(what());
  • 실행 오류
  • "I did not study last night."
  • undefined
  • "function() { return this.secret; }"

Quiz 8

function func1( ) { return func1; }
if(new func1() === func1()) {
    document.write("same!");
}
else {
    document.write("different!");
}
  • 실행 오류
  • "same!"
  • "different!"

Quiz 9

(function(){
    Object.prototype.scope = "prototype";
    (function(f) {
        var scope = "local";
        alert(f());
    })(function checkScope() { return scope; });
})();
  • 실행 오류
  • "local"
  • "prototype"
  • "undefined"

Quiz 10

var strZero = "0";
var numZero = 0;
var notNumer = NaN;
var strEmpty = '';
var tab = '\t';
var undef;
var nll = null;
if(strZero == true) {
    document.write("1");
}
if(notNumer == false) {
    document.write("2");
}
if(undef == nll) {
    document.write("3");
}
if(tab == 0) {
    document.write("4");
}
if(tab == true) {
    document.write("5");
}
if(strEmpty == numZero) {
    document.write("6");
}
if(false == 'false') {
    document.write("7");
}
if(strEmpty == numZero) {
    document.write("8");
}
  • ""
  • "78"
  • "3468"
  • "34568"
  • "2567"

2012. 1. 3.

머리를 때리는 자바스크립트 성능 최적화


머리에 주먹질을 해대는 책이 아니라면 왜 그 책을 읽어야 하는가?

서문에 쓰여 있는 글이고, 이 말 그대로 내 머리에 무수한 주먹질을 가한 책입니다.



이 책은 자바스크립트의 성능 향상에만 모든 포커싱을 두고 있습니다. 속도를 위해서라면 어떤 짓이든 저지를 개발자들이 쓴 책이지요. (비교적) 사소한 것에서도 속도 문제를 걸고 넘어지며 해결책을 제시합니다.

그렇다고 무작정,

"스크립트 속도를 해결하려면 이렇게 하면 됩니다!"

라고 밑도 끝도 없는 결과론적인 설명을 하지 않고, 자바스크립트의 특성부터 일반적인 나쁜 코딩 습관를 지적하거나, 필요하다면 스크립트의 구현과 엔진 해석같은 비교적 저레벨 지식까지 먼저 해설하고 그 다음에야,

"그렇기 때문에 이렇게 구현하면 속도가 빨라집니다" 

라고 설명하고 있습니다.

제목은 자바스크립트 성능 최적화지만 읽다보면 다른 언어에도 적용할만한 부분도 나옵니다. 루프 최적화라든지, 요청 줄이기. 조건문 최적화, 코드 결합등등이 좋은 예.
제일 인상깊었던 부분은 초반부의 스코프 체인 설명과, 타이머를 사용한 루프 처리였습니다. 바로 실무에 적용할만한 것들이라 그랬던것 같네요.

재미있는 점은 책의 저자들 대다수가 야후에서 일하는 개발자들이어서 그런지 야후의 유이 라이브러리의 언급이 많고, 살짝 자랑질(?)같은 뉘앙스가 풍기기도 합니다.

책의 대상은 절대 자바스크립트 입문서가 아니라서 입문자가 읽는다면 1장 조금 읽은 뒤 바로 구입처에 환불을 신청하게 될 위험이 있습니다. 개인적으로는 정규표현식 부분과 배포 부분이 살짝 어려웠던것 같네요.

2012. 1. 1.

자바에서의 volatile 키워드

volatile?

volatile은 멀티스레딩 환경시 동기화를 해주는 녀석 맞습니다. 읽기 쓰기시에 어떤 스레드가 값을 변경하든, 항상 최신값을 읽어갈 수 있게 해주죠.
그럼 syncronized 키워드하고는 뭐가 다른가?

작업 시 그 변수의 접근에 대해 같은 값을 읽어갈 수 있게 해주는 것은 volatile이나 syncronized나 동일합니다.

읽고 쓰기에 대한 원자성

하지만 volatile은 작업의 요소를 뺀 가시성, 즉 읽기, 쓰기동작에 대해서만 동기화가 됩니다. 그리고 그것은 원자성(Atomic)을 가집니다
말이 어려우니 예를 들면...

int count = 10;

이라는 변수가 있다고 가정하고 스레드 A와 B가 사용한다고 가정합니다.

거의 동시간에 A스레드가 count를 읽어 조작합니다. B도 같이 count를 읽어 조작합니다.
이 경우 두 스레드간에 count의 값은 서로 동일한 값, 즉 동일한 메모리 주소를 가르키지 않습니다.
스레드는 자신만의 저장 영역에 원본의 값을 복사하여 조작하기 때문이죠.

그래서 스레드마다 count는 다른 값을 가지고 있을 수 있습니다.
A에서 변경한 값을 B에서는 영원히 읽어갈 수 없을수도 있습니다.
이런저런 과정 후에 A와 B의 스레드는 작업이 끝나면 다시 조작한 값을 원본 값으로 돌리는데 그 작업이 끝나지 않거나 배치 최적화(reordering. 밑에서 설명) 등으로 그러지 않을 수 있습니다.
그럼 변수 count는 스레드간 불일치가 일어나겠죠.

리오더링 회피

또한 변수 읽기 불일치는 일종의 최적화인 리오더링 결과로 발생하기도 합니다.
리오더링은 보통 컴파일 과정에서 일어나며, 프로그래머가 만들어낸 코드는 컴파일 될 때 좀더 빠르게 실행될 수 있도록 조작을 가하는 최적화를 거칩니다.
그러한 최적화 과정중에 읽기와 쓰기의 순서가 바뀔 수 있는데, 그럴 경우 자칫 순서가 중요한 멀티스레드 환경에서 문제가 발생할 수 있습니다.

volatile을 쓴 변수는 리오더링에서 제외되며, 항상 프로그래머가 지정한 순서로 읽기 및 쓰기를 수행합니다.

연산의 원자성

이것은 가장 처음의 문제와 같은 특성인데요.
보통 변수의 쓰기 읽기는 일견 원자성(누군가 끼어들 틈이 없는 완벽한 작업단위로 이해합시다...)을 띌 수도 있어 보입니다만, 실제 count에 변수가 할당될 때 완벽히 한 메모리 작업 안에서 완벽히 쓰여지지 않습니다.

아, int같은 데이터 타입은 한번에 씁니다...;
그러나 그보다 더 큰 타입, long이나 객체..그리고 변수 할당 및 연산 시 일어나는 작업에서는 완벽히 원자성을 가지지 않고 여러번에 걸쳐 메모리 작업이 일어납니다.
(엄밀히 말하면 JVM 구현에 따라 다를 수 있다고 합니다...)

long stat = 324L;

위 코드에서 long은 데이터형이 8바이트. 즉 64비트입니다. 자바는 여기에 변수를 할당할 때 32비트 단위로 끊어서 할당합니다. 첫 32비트 할당 그다음 32비트 할당...
첫 32비트를 할당했을때 다른 스레드가 값을 읽어간다면...? 정상적이지 않은 상황이 나오겠죠 (물론 무지하게 짧은 순간입니다)

stat 변수가 volatile 키워드 변수라면 할당이 원자성을 가지게 되어 문제가 발생하지 않습니다.

syncronized랑 다를게 없네? 아닙니다.
아래 코드를 보면

int val = stat + 10;

이 코드는 val에 volatile 선언을 해 둬도, 멀티 스레딩 시 위험합니다.
분명 스레드의 접근 순서에 따라 val의 값을 어떤 스레드는 10을 더한값을 가져가기도 하고, 어떤 스레드는 10을 더하지 않은 값을 읽기도 할것입니다.
위 코드의 작업은 stat을 int로 캐스팅하고 10을 더하고 다시 val에 할당하는 3가지의 작업을 거치기 때문이죠.

syncronized는 이럴 때 작업자체를 원자화해버립니다.
volatile이 할 수 없는 일이죠.

1.5이전버전 유의

volatile 사용시에는 자바 버전에 주의해야 합니다.
1.5 이전 버전에서는 정상 지원이 되지 않고 있거든요. 그 이후라면 안전하게 보장해준다고 합니다.

이터레이터 패턴의 내부 반복자와 외부 반복자

스터디중의 의문. Internal and External Iterator

금일 자바카페 스터디 디자인패턴 이터레이터 패턴(Iterator Pattern) 중에 내부반복자와 외부 반복자 이야기가 나왔습니다.

반복자는 알겠는데, 외부 반복자와 내부 반복자로 구분된다고???
바로 검색에 들어갔습니다.

역시 구글의 힘... 언어별로 다양한 예가 나와 있더군요.

외부 반복자

외부 반복자는 흔히 배열애서 요소를 forwhile 등의 루프로 요소를 하나씩 꺼내서 직접 조작하는 방식입니다.
List<String> list = new ArrayList<String>();
list.add("h");
list.add("e"); 
list.add("l"); 
list.add("l"); 
list.add("o"); 
Iterator<String> iter =  list.iterator();
while(iter.hasNext()) {
    String value = iter.next();
    /* 뭔가 하기... */
}
클라이언트 코드에서 직접 반복 객체를 가져오고 직접 처리하고 있죠.

내부 반복자

그럼 내부 반복자는 뭘 말하는 것일까요.
컬렉션의 반복을 사용자가 하는게 아니라, 사용자는 반복당 수행할 액션만을 제공하고, 그 액션을 컬렉션이 받아 내부적으로 처리하는 방식입니다.
이때 수행 액션은 보통 콜백 함수로 전달됩니다.
내부 반복자는 기본 자바 API 의 기본 콜렉션에서는 지원하지 않는 것 같습니다. 다른 언어에서는 지원하고 있는데, 자바에서 사용하려면 직접 구현해야 합니다.
보통 타 언어에서는
$.each(ary, function(index, element) {
    // 요소마다 한번씩 호출
    console.log(element);  
});
같은 방식의 메서드를 지원하고 있습니다.
위는 javascript의 jQuery 형식입니다.

자바에서는 앞서 말했듯이 없기에 직접 구현해야 할 것 같습니다.
제가 구현한 코드를 써볼게요...
package me.javarouka.dp.iterator;

import java.util.ArrayList;

// 제네릭스 사용.
// 편의상 ArrayList를 상속했습니다.
public class InternalIterationList<E> extends ArrayList<E> {
    
    private static final long serialVersionUID = 1L;

    // 반복 작업을 처리하게 할 콜백 인터페이스.
    // 편의상 스테틱 내부 인터페이스로 구현
    public static interface Callback<E> {
        public E map(E e);
    }
    
    // 실제 반복자. 자신의 요소에 콜백을 한번씩 호출
    public void each(Callback<E> callback) {
        int len = this.size();
        for (int i = 0; i < len; i++) {
            callback.map(this.get(i));
        }
    }
    
    @Test
    public void testClass() {
        InternalIterationList<String> internalIterator 
            = new InternalIterationList<String>();
        
        internalIterator.add("Hello");
        internalIterator.add(", ");
        internalIterator.add("World");
        
        // 내부 반복
        internalIterator.each(new Callback<String>() {
            
            @Override
            public String map(String e) {
                System.out.print(e);
                return e;
            }
        });
    }
}

결론

  • 외부반복자는 일반적으로 사용하는 루프처럼 요소를 사용하는 측(클라이언트)가 직접 컬렉션 요소를 하나씩 꺼내와서 반복 처리
  • 내부반복자는 처리할 행동(보통 콜백 함수)을 컬렉션 요소에 넘겨주어 반복 처리
입니다.