Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

kangax의 Javascript Quiz 내용 파악하기

Perfection kills라는 블로그를 운영하고 있는 kangax님이 14문항의 Javascript Quiz를 블로그에 올렸습니다. kangax님은 JS프레임워크인 prototype.js의 Core 개발자이기도 합니다. 퀴즈는 아래 링크에서 풀어볼 수 있습니다. 다 풀고 제출하면 틀린문항을 알려줍니다.


Dmitry님의 퀴즈를 보고 자기도 퀴즈를 냈다고 하는데 이 테스트는 행동이 불특정한 window같은 host object에 대한 부분은 포함하지 않았다고 합니다. pure ECMAScript(3rd ed)에 대해서 테스트하며 주로 범위(scoping), 함수표현(function expression), 참조(reference), 변수와 함수의 진행(process of variable and function declaration)를 다룹니다. 이 테스트는 비교적 간단한 컨셉이며 프로페셔널 자바스크립트 개발자라면 반드시 알아야할 내용이라고 합니다. 하지만 모든 퀴즈가 다 실용적인 것은 아니므로 대답못하는 문항이 있다고 하더라도 너무 걱정말라고 말도 덧붙혔습니다.

전 프로페셔널 자바스크립트 개발자가 아니기 때문에 걱정하면서 문제를 풀기는 했는데 총 14문에 3개 맞췄습니다. 잘 안쓰는 표현이 많아서 많이 헷갈렸습니다. Javascript의 type나 scope는 상당히 헷갈리는 부분이죠. 문제는 직접 풀어보시고 복습 겸 해서 문제를 실행시켜 본 결과들입니다.(문제 푸실분들은 아래 내용 보지 마세요. ㅎ)




(function(){ 
    return typeof arguments;
})();

결과는 "object"입니다. 함수의 파라미터를 받는 arguments의 type는 object입니다. 설사 파라미터가 넘어오지 않았다고 하더라도....



var f = function g(){ return 23; };
typeof g();


결과는 Error 입니다. g is not defined이라는 오류가 나타납니다. 기대했던 값은 리턴값인 23의 type인 number입니다만 이런저런 테스트를 해본 결과 저런식으로 선언된 function은 f로 정의가 되고 g라는 펑션은 정의되지 않는것 같습니다. f라는 함수만 존재하고 g라는 것은 정의되지 않았기 때문에 g자체가 undefined로 떨어지고 당연히 g()자체가 실행되지 않습니다.


(function(x){
    delete x;
    return x;
})(1);


결과는 1 입니다. 익명함수로 정의하고 파라미터를 1을 넘겨준 뒤에 delete x를 실행한 뒤에 x값이 어떻게 바뀌는지를 묻는 문제인데 사실 delete라는 명령어는 이번에 처음 봤습니다. delete Operator에 대해서는 MDC에 자세하게 나와있습니다. delete를 모르는 상태에서 봤을때는 x를 지웠으니까 return x에서는 1이 아닌 다른 값이 찍힐것 같은데 1이 그대로 나옵니다.
delete는 뒤에오는 expression이 property로 평가될 때만 그것을 지워주는 동작을 하고 property가 아닐 경우에는 아무것도 하지 않는다고 합니다. 이 property라는 것의 개념이 좀 어렵긴 합니다만 제가 어줍잖게 설명하는 것 보다는 MDC에 정리된 문서를 보는게 더 나을듯 하군요.



var y = 1, x = y = typeof x;
x;



결과는 "undefined"입니다. LHS와 RHS(right-hand side)에 순서에 대해서 묻는 문제입니다. x = y = typeof x; 부분에서 (x=y)=typeof x 냐 x=(y=typeof x)의 순서로 실행되냐는 것을 물어본것입니다.(undefined가 쌍따옴표가 있는것과 없는것의 차이는 잘 모르겠군요 ㅠ..ㅠ) 이해할려고 하면할수록 헷갈려서 댓글들을 좀 봤는데 JS 인터프리터는 left to right로 해석하는 특성을 가지고 있기는 한데 이 경우에서는 LHS가 x이고 RHS y=typeof x가 되는데 RHS내부에서 다시 LHS와 RHS로 나뉘면서 할당되어서 결과가 undefined로 나온다는 해석이 맞는듯 합니다.



(function f(f){ 
    return typeof f(); 
})(function(){ return 1; });



결과는 “number”입니다. 똑같은 이름을 써서 헷갈리게 만들어 놓은 문제입니다. 익명함수에 파리미터로 1을 리턴하는 함수를 넘겨줍니다. 그러면 함수f가 파라미터f에 함수를 받게 되고 리턴해주는 f()는 파라미터로 넘겨받은 함수가 되기 때문에(내부에서는 당연히 지역변수가 우선이므로) 1이 리턴되고 typeof는 number로 나오게 됩니다.



var foo = { 
    bar: function() { return this.baz; }, 
    baz: 1
};
(function(){ 
    return typeof arguments[0]();
})(foo.bar);



결과는 "undefined"입니다. Javascript의 가장 헷갈리는 녀석중의 하나인 context에 따른 this에 대한 문제로 보입니다. foo가 정의된 부분에서 baz가 아래쪽에 있어서 이상해 보이긴 하지만 Javascript에서 변수의 스코프는 function이기 때문에 bar()는 정상적으로 1을 리턴합니다. 그냥 foo.bar()를 실행하면 1이 나옵니다. 이걸 마지막에는 익명함수로 실행시켰습니다. 익명함수로 foo.bar함수를 파라미터로 넘기고 arguments[0]으로 받아서 실행했습니다.(arguments[0]로 받는지 파라미터 지정해서 받든지 결과는 동일한듯 합니다.)
익명함수내에서 실행된 foo.bar()는 context가 달라져서 this가 foo가 아니기 때문에 this.baz는 undefined로 나옵니다. foo.bar()내부에서 this를 찍어보면 익명함수에서 실행되었을 경우 Global Object인 window보다도 상위객체가 나와버립니다. FF 3.6에서는 this가 거의 브라우져객체자체를 가르키는듯 합니다.(플러그인의 버전들 나오고 막 그러네요.) 잘 안쓰던 익명함수인데 많이 헷갈리는군요.



var foo = {
    bar: function(){ return this.baz; },
    baz: 1
}
typeof (f = foo.bar)();



결과는 "undefined"입니다. 6번 문항과 동일한 것을 물어보지만 형태가 다릅니다. foo.bar를 f에 할당하고는 익명함수 형태로 함수를 실행했지만 6번과 동일한 결과가 나타납니다. 6번문항을 테스트할때는 익명함수가 context에 영향을 주나 했는데 여기서는 익명함수 밖에서 foo.bar를 변수에 할당하고 변수로 함수호출을 하여도 익명함수로 실행한 것과 동일하게 this가 바뀝니다. 위의 형태에서는 context가 foo가 아닌게 문제인 것으로 보입니다.



var f = (function f(){ return "1"; }, function g(){ return 2; })();
typeof f;



결과는 "number"입니다. 계속되는 익명함수입니다. ㅠ..ㅠ 익명함수로 2개의 함수를 정의하고 실행시켜서 f에 저장을 하였습니다. 테스트해본 결과 위와같이 작성할 경우 마지막인 g()만 실행이 되고 f()는 실행조차 되지 않습니다. 둘의 순서를 바꿀경우 당연히 f()가 실행되고 위소스에서는 g()가 실행되었기 때문에 f는 2가 할당되고 타입은 number가 나옵니다.



var x = 1;
if (function f(){}) {
    x += typeof f;
}
x;



결과는 “1undefined”입니다. 헷갈리게 많이 꼬아놨는데 소스를 돌려보고 나니 명확합니다. x는 숫자타입의 1이고 if문의 조건으로 f함수를 정의합니다만 조건문내에서 정의한 것이라 실제로 생기지는 않습니다만 실행에는 문제가 없으므로 조건문은 true입니다. 함수 f는 존재하지 않으므로 if문내에서 f의 type는 undefined가 나오게 되고 number와 string을 +연산했으므로 문자열로 이어붙혀서 "1undefined"라는 결과가 나옵니다.



var x = [typeof x, typeof y][1];
typeof typeof x;



결과는 “string”입니다. x와 y의 type으로 배열을 만들고 2번째 값을 x에 설정합니다. x,y는 미리 선언되지 않았으므로  ["undefined", "undefined"]의 형태가 되고 2번째의 "undefined"가 x에 할당됩니다. typeof는 결과값을 string으로 리턴을 해주기 때문에 x의 typeof는 "undefined"이고 다시 typeof를 하면 "string"이 나옵니다. .



(function(foo){
    return typeof foo.bar;
})({ foo: { bar: 1 } });



결과는 "undefined"입니다. 이건 기가막힌 함정이네요. ㅎㅎ 익명함수의 파라미터로 리터럴형식의 { foo: { bar: 1 } }를 넘겨주고 파라미터 foo가 이를 받습니다. 그리고 1의 타입인 "number"를 기대하게 하며 foo.bar를 찍어보지만 결과는 "undefined"입니다. 왜냐하면 typeof할때의 foo는 파라미터 foo이기 때문에 마치 foo = { foo: { bar: 1 } }를 한 것과 같습니다. 1을 찍어보고 싶으면 foo.foo.bar를 해야 합니다.



(function f(){
    function f(){ return 1; }
    return f();
    function f(){ return 2; }
})();



결과는 2입니다. 함수내부의 스코프에 대한 이해를 묻는 문제이군요. 헷갈리게 작성해 놓으니까 엄청 헷갈리네요. 익명함수로 f를 실행합니다. 내부에는 f함수가 2개 정의되어 있고 중간에 f()를 리턴해 줍니다. function에서 return을 하면 그 이하의 코드는 실행되지 않는다는 일반적인 상식을 이용한 함정인데 Javascript는 함수스코프를 타기 때문에 예를 들면 하단에 선언된 함수도 그위의 코드에서 실행시킬수 있습니다.(같은 스코프내에 있는거죠.) 댓글들에 있는 말로 설명을 하면 함수정의는 최상단으로 이동한다는 게 더 쉽게 이해되는듯 합니다. 보기에는 return때문에 2를 리턴하는 함수정의는 실행조차 안될것 같지만 함수정의는 모두 최상단으로 이동되어 해석되고 f()함수는 2를 리턴하는 것으로 재정의 되어 있는 것입니다.



function f(){ return f; }
new f() instanceof f;



결과는 false입니다. new는 object같은 것을 리턴하는 클래스를 정의한 function가 아닌 클래스의 인스턴스를 리턴한다고 합니다. 그래서 이 경우에 new f()는 f함수의 인스턴스가 아닌 f함수 그 자체를 리턴해주므로 f의 인스턴스가 아니라서 false가 나오게 됩니다.



with (function(x, undefined){}) length;



결과는 2입니다. 알고 보면 간단한건데 써본적이 없는거라 파악하는데도 오래 걸렸네요. with문을 이용한 간단한 구문이며 실제로는 Function의 length에 대해서 묻고 있는 것입니다. Function의 length프로퍼티는 함수의 아규먼트의 수를 나타낸다고 합니다.



그냥 쓰던 형태로만 항상 쓰다보니 깊게 들어가닌까 내공이 여실히 들어나게 되는군요. ㅠ..ㅠ 요즘 스터디에서도 자바스크립트를 좀 깊게 보고 있는데 종종 깊게 파봐서 이해도를 높여야 겠습니다. 기본적인 동작들을 이해할 수 있어야 그 이상도 이해할 수 있는거니까요. 가벼운 마음으로 퀴즈풀었다가 많은걸 배우게 되는군요. ㅎㅎ
2010/02/11 02:23 2010/02/11 02:23