블로그 내 검색

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" 입니다.


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

댓글 7개:

  1. 첫번째 퀴즈의 경우 설명이 조금 틀린거 같습니다.

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

    위와 같이 설명해 주셨는데..전역의 name 을 가르키는게 아니라
    "function 스코프 안에서 var로 선언된 모든 변수는 정의만 위로 끌어 올려져(호이스팅) 아직 값을 할당받지않은 undefined 상태"
    라고 설명을 해주시는게 올바르지 않은가 싶네요.

    답글삭제
    답글
    1. 답글 감사합니다.

      제가 단 답글도 그걸 의도한 게 맞습니다; 제 글 표현력이 부족했네요.
      호이스팅된 변수가 글로벌 변수를 가린다(보통 shadow된다고 하더군요...) 를 말하고 싶었습니다;

      답글 하나하나가 포스팅하는데 힘이 됩니다.
      정말 감사합니다.

      삭제
  2. 좋은 글 잘봤습니다.
    10번에 덧붙여 드릴게요.저도 정리노트가 있어서.. 좀더 명확히...

    == 비교연산
    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');
    }

    답글삭제
  3. 추가
    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');
    }

    답글삭제
  4. 깨알같네요. 고맙습니다. 글과 댓글 잘 보고 갑니다. :)

    답글삭제