Skip to content

1. Essential Questions

Kwan-Ung Park edited this page Feb 1, 2018 · 9 revisions

https://www.toptal.com/javascript/interview-questions 에 나온 내용을 옮겼거나 각색함

1. typeof bar === "obejct" 에 대해 체크 할때 어떤 잠재적인 위험이 있는가?

bar는 객체라면 typeof bar === "object"는 맞지만, 만약 bar가 null일 경우 놀랍게도 object로 인식하여 true를 반환한다.

var bar = null;
console.log(typeof bar === "object") // true

그래서 var bar = null 일경우 더블체킹하여야 한다.

var bar = null;
console.log((bar !== null) && (typeof bar === "object"));  // logs false
  • 만약 bar가 function일 경우, 위 예제는 false를 반환한다. 이를 해결 하기 위해서, 아래 예제 처럼 수행한다.
var bar = function(){};
console.log((bar !== null) && ((typeof bar === "object") || (typeof bar === "function"))); // true
  • 그런데 만약 bar가 array일 경우에도 위 예제는 true를 반환한다. 하지만 우린 array일 경우에는 false를 원할 것 이다. 다음과 같이 하자.
var bar = [];
console.log((bar !== null) && (typeof bar === "object") && (toString.call(bar) !== "[object Array]")); // false

2. 다음 예제의 output은?

(function(){
  var a = b = 3;
})();

console.log("a defined? " + (typeof a !== 'undefined'));
console.log("b defined? " + (typeof b !== 'undefined'));

output:

a defined? false
b defined? true

해설

위 코드를 보면 한 함수 스코프안에 정의되어 있으며, 한라인에 var 키워드와 함께 시작된다. 그래서 대부분의 개발자들은 typeof a , typeof bundefined라고 예상할 것이다.
그러나 그렇지 않다. 대부분 개발자들은 var a=b=3은 아래 처럼 요약한 거라고 잘못 생각한다.

var b = 3;
var a = b;

그러나 사실 var a=b=3은 아래를 요약 한 것이다.

b = 3;
var a = b;

결과적으로 (use stric mode가 아니라면) output은 다음과 같습니다.

a defined? false
b defined? true

그러나 어떻게 b는 함수 바깥 쪽 에서 정의할 수 있는건가? var a=b=3b=3그리고 var a=b를 요약시킨 수식이다. 여기서 var a=b; 의 마지막 b는 글로벌 변수이다.(var 키워드로 선행되지 않아서..) 그러므로 함수 밖의 스코프이다.

만약 use strict 모드 일 경우 var a=b=3ReferenceError: b is not defined;라고 런타임 에러를 반환한다.

3. 아래 예제의 output과 그이유는?

var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log("outer func:  this.foo = " + this.foo);
        console.log("outer func:  self.foo = " + self.foo);
        (function() {
            console.log("inner func:  this.foo = " + this.foo);
            console.log("inner func:  self.foo = " + self.foo);
        }());
    }
};
myObject.func();

output:

outer func:  this.foo = bar
outer func:  self.foo = bar
inner func:  this.foo = undefined
inner func:  self.foo = bar

why?

outer functionthis, selfmyObject를 참조 하여 bar:foo에 접근이 가능하다.
inner functionthismyObject를 더이상 참조 하지 않는다. 결과적으로 this.fooinner function에서 undefiend이다. 지역변수 self는 scope안에서 참조가 유지되기 때문에 bar:foo에 접근이 가능하다(클로저)(ECMA5이전에는 inner function에서 this는 전역 window를 참조 한다. 하지만 ECMA5에서는 thisundefined이다.)

4. 함수 블록에서 javascript소스파일 전체 내용을 감싸는 의미와 이유는?

이는 대부분 인기있는 javascript 라이브러리에 많이 일반적인 관행이다.(jQuery, Nodejs..) 이 기술은 파일의 전체 소스를 감싸서 클로저를 생성한다. 이 기술은 private namespace를 생성하여 다른 javascript모듈과 라이브러리로부터 잠재적인 이름 충돌을 피할 수 있게 도와준다.

이 방법의 다른 특징은 전역 변수를 위해 쉽게 참조 가능한 별칭(아마도 짧게..)을 허락한다. 예를들어 jQuery 플러그인에서 종종 사용한다. jQuery 네임스페이스를 jQuery 로 사용하기 위해 $ 네임스페이스를 비활성화 할 수 있다.
하지만 클로저에 의해 다음과 같은 예제에서는 $를 사용 할 수도 있다.
(function($) { /* jQuery plugin code referencing $ */ } )(jQuery);

5. use strict 를 javascript 파일 첫번째로 작성하는 의미와 장점은?

여기 짧지만 가장 중요한 답이 있다. use strict은 런타임에 javascript 코드를 엄격하게 파싱하고 에러핸들링을 하도록 자발적으로 강제하는 방법이다.
strict mode 몇가지 이점

  • 디버깅을 쉽게 만들어준다.
  • 전역적인 실수들을 방지해준다.
  • strict mode없이 undeclared 변수는 자동적으로 전역 변수로 할당 된다. 이는 매우 흔한 javascript오류이다.strict mode에서는 이경우 에러를 반환한다.
  • this강제 변환
  • strict mode가 아닐 경우 this 참조값이 null,undefied이면 자동적으로 전역에서 삭제된다. 하지만 이는 버그이다. strict mode에서는 thisnull, undefied이면 에러를 반환한다.
  • 속성이름 또는 매개변수 이름 중복을 허락하지 않는다.
  • eval()을 안전하게 만들어준다.
  • 잘못된 delete사용에 에러를 반환한다.
  • delete는 객체 속성을 삭제 하는 연산자 이며, 객체 속성이 설정되지 않은 것에서는 사용할 수 없다. 그런대 stric mode가 아닌경우에서는 에러를 반환하지 않는다.

6. 아래 두 함수를 보고, 같은 것을 반환할까? 왜? 아니면 왜아니지?

function foo1()
{
  return {
      bar: "hello"
  };
}

function foo2()
{
  return
  {
      bar: "hello"
  };
}
console.log("foo1 returns:");
console.log(foo1());
console.log("foo2 returns:");
console.log(foo2());

output:

foo1 returns:
Object {bar: "hello"}
foo2 returns:
undefined 

해설

놀랍게도 두 함수는 다른것을 반환한다. 하지만 이건 놀라울 것이 아니다. foo2는 에러를 반환하지 않고 undefined를 반환 합니다.
이는 javascript 세미콜론의 기술적인 이유가 있다.
이것은 놀랍지 않을뿐만 아니라 특히 foo2 ()가 오류가 발생하지 않고 정의되지 않은 상태로 반환되어 매우 괴로운 결과를 낳습니다.
그 이유는 자바 스크립트에서 세미콜론은 기술적으로 선택적이라는 사실과 관련이 있습니다 (생략하는 것은 일반적으로 매우 나쁜 형식입니다). 결과적으로, foo2 ()에서 return 문을 포함하는 행 (다른 행은 없음)이 발견되면 return 문 바로 다음에 세미콜론이 자동으로 삽입됩니다.

아무런 코드도 호출되지 않거나 아무 것도하지 않더라도 코드의 나머지 부분이 완벽하게 유효하기 때문에 오류가 발생하지 않습니다. 단순히 "hello"문자열과 동일한 속성 막대를 정의하는 사용되지 않는 코드 블록 일뿐입니다.
이 동작은 또한 줄 바꿈이 새 줄 시작 부분이 아니라 JavaScript 끝 부분에 배치되는 규칙을 따르는 것으로 주장합니다. 여기에 표시된 것처럼 이것은 JavaScript의 단순한 문체 선호 이상의 의미를 갖습니다.

7. NaN은 무엇이고, 어떤 타입이고, 그리고 만약 값이 NaN과 같으면 어떻게 안정적인 테스트 할 수 있을까?

NaN속성을 표현하는 값은 not a number 입니다. 그러나 NaN 타입은 실제로는 Number이다.

console.log(typeof NaN === "number");  // logs "true"

게다가 NaN은 어느것이랑 비교해도(심지어자신이랑도) false이다.

console.log(NaN === NaN);  // logs "false"

NaN을 검증하기 위해서 isNaN() 이라는 빌트인 함수를 로 반안정적인 테스트를 할 수 있다.(완벽하지않음)
NaN 타입 체크 최적의 솔루션은 value !== value 이다. 만약 값이 NaN과 같으면 true를 항상 반환할 것이다. ES6에서느 새로운 Number.isNaN() 함수를 제공하고, 이전 isNaN() 함수보다 더 신뢰성있다.

8. 아래 예제 output을 예상해보라.

console.log(0.1 + 0.2);
console.log(0.1 + 0.2 == 0.3);

output:

0.30000000000000004
false

해설

예상 답변은 0.3, true 일 것 이다. 하지만 javascript Number는 부동소수점 연산을 한다. 그래서 항상 기대 하는 값을 산출하지 않는다.

9. 정수 x 인지 확인하는 isInteger(x)를 작성하는 방법에 대해 토론

사실 이 토론은 ES6에서는 쓸데없는 것이다. Number.isInteger() 함수가 새롭게 나왔기 때문이다.
그러나 ES6이전에는 좀더 복잡하다. 문제는 ECMAScript 명세서에는 정수는 단지 개념적으로 존재한다는 것이다. 즉 숫자값은 항상 부동소수점으로 저장된다는 것이다.
이걸 염두에 두고 간단하고 깔끔한 pre-ECMAScript6 솔루션은 다음과 같다.

function isInteger(x) { return (x^0) === x; } 

다음 솔루션 또한 작동한다. 비록 위 솔루션 보단 아름답지 못하지만.

function isInteger(x) { return Math.round(x) === x; }

Math.round() 대신에 Math.ceil(), Math.floor() 또한 똑같이 작동한다.
또는 다른 방법으로

function isInteger(x) { return (typeof x === 'number') && (x % 1 === 0); }

한가지 매우 일반적인 잘못된 솔루션은 다음과 같습니다.

function isInteger(x) { return parseInt(x, 10) === x; }

parseInt기반의 접근방식은 많은 x값을 위하여 잘 작동할 것이지만 x값이 점점 커지면 그것은 아마도 실패할 것이다.
parsInt()의 문제는 숫자를 파싱하기 전에 문자열 첫번째 매개변수를 강제 변환 한다. 그러므로 숫자는 점점 커져가고, 그것의 캐릭터 형태는 지수 형태로 표현 될 것 입니다.(예를 들어 1e+21) 따라서, parseInt()1e+21를 파싱할려고 노력하지만, 1e 지수형태에 도달하면 파싱을 멈추고 1 값을 반환 하게 됩니다.

> String(1000000000000000000000)
'1e+21'

> parseInt(1000000000000000000000, 10)
1

> parseInt(1000000000000000000000, 10) === 1000000000000000000000
false

10. 아래 함수가 실행되면 1~4 숫자가 어떤 순서로 console에 찍힐가? 왜?

(function() {
    console.log(1); 
    setTimeout(function(){console.log(2)}, 1000); 
    setTimeout(function(){console.log(3)}, 0); 
    console.log(4);
})();

output:

1
4
3
2

해설

첫번째로 예상하는 명백한 부분에 대해 설명해보자.

  • 처음 1,4는 console.log()에 의해 딜레이 없이 간단히 바로 실행되어 찍히게 된다.
  • 2는 3뒤에 찍히게된다. 왜냐하면 3의 딜레이시간은 0ms이고 2는 1000ms만큼의 딜레이후 실행된다.

자 그럼 3은 0ms 딜레이 이후 찍히는 건데 곧바로 찍힌다는 의미가 아닌건가? 그리고 4는 코드라인 맨 마지막에 위치한데 왜 그것은 4전에 찍히지 않은 것인가?
이를 이해하려면 javascript events and timing을 이해해야 한다.
브라우저는 이벤트 큐를 확인하는 이벤트 루프를 가지고 있으며, 대기중인 이벤트를 처리한다. 예를들어, 만약 브라우저가 바쁜 와중에(예로 onclick처리중.) 백그라운드에서 이벤트가(onloadscript 이벤트) 발생하면 이벤트는 큐에 추가 된다. onclick핸들러가 완료되면 큐를 확인하여 이벤트를 실행한다.(onload script 실행)
비슷하게 브라우저가 바쁘다면, setTimeout() 또한 이벤트 큐에 추가되어 실행하게 된다.
setTimeout() 2번째 파라미터로 0을 전달 하면, 즉시 바로 함수를 실행하라는 것이다. 특별하게도, 함수의 실행은 next time tick에 발행을 위해 이벤트큐에 위치한다. 즉시가 아니라는 것에 유의하자. 함수는 next timer tick 이후에 실행하지 않는다. 이런 이유 떄문에 console.log(3) 호출 전에 console.log(4)가 호출 됩니다.

11. 80자이하 문자열을 거꾸로 해도 같은 문자열이 나오는 palindrome 체크 함수만들어보쟈

function palindrome(str){
 str = str.replace(/\W/g, '').toLowerCase();
 return (str == str.slit('').reverse().join(''));
}

output:

console.log(isPalindrome("level"));                   // logs 'true'
console.log(isPalindrome("levels"));                  // logs 'false'
console.log(isPalindrome("A car, a man, a maraca"));  // logs 'true'

12. 아래 예시와 같이 실행되게끔 sum()를 작성하자.

sum(2,3) // 5
sum(2)(3) //5

2가지 방법이 있다.

function sum(x){
  if (arguments.length == 2) {
    return arguments[0] + arguments[1];
  } else {
    return function(y) { return x + y; };
  }
}

function sum(x, y) {
  if (y !== undefined) {
    return x + y;
  } else {
    return function(y) { return x + y; };
  }
}

13. 아래 코드에 대해 생각해보고 2가지 질문에 답해보자.

for (var i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', function(){ console.log(i); });
  document.body.appendChild(btn);
}

1. Button4를 클릭하면 어떤 콘솔이 찍히고 왜찍히나요?

어떤 버튼을 클릭 하던 숫자5가 항상 콘솔로 찍힌다.
onclick 메서드가 실행되는 시점에는 for loop는 완료되었고 변수 i 는 이미 숫자5의 값을 가지고 있기 때문이다. (클로저, 실행컨텍스트 참고)

2. 예상되는 방법대로 기능을 제공하기 위하여, 하나 이상의 대체 할 수 있는 기능을 제공하라.

새로운 함수객체를 생성하여 for loop를 통해서 전달되는 변수i를 캡쳐하는 방식으로 만들어준다.

for (var i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', (function(i) {
    return function() { console.log(i); };
  })(i));
  document.body.appendChild(btn);
}

다른 방식으로 btn.addEventListener를 새로운 익명함수안에 둬서 전체를 호출하게 랩핑한다.

for (var i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  (function (i) {
    btn.addEventListener('click', function() { console.log(i); });
  })(i);
  document.body.appendChild(btn);
}

마지막으로 for 를 array native 메서드인 foreach로 교체한다.

['a', 'b', 'c', 'd', 'e'].forEach(function (value, i) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', function() { console.log(i); });
  document.body.appendChild(btn);
});

14. 아래 코드는 어떤 output이 나올까?

var arr1 = "john".split('');
var arr2 = arr1.reverse();
var arr3 = "jones".split('');
arr2.push(arr3);
console.log("array 1: length=" + arr1.length + " last=" + arr1.slice(-1));
console.log("array 2: length=" + arr2.length + " last=" + arr2.slice(-1));

output:

"array 1: length=5 last=j,o,n,e,s"
"array 2: length=5 last=j,o,n,e,s"

15. 아래 코드는 어떤 output이 나올까요

console.log(1 +  "2" + "2");
console.log(1 +  +"2" + "2");
console.log(1 +  -"1" + "2");
console.log(+"1" +  "1" + "2");
console.log( "A" - "B" + "2");
console.log( "A" - "B" + 2);

output:

"122"
"32"
"02"
"112"
"NaN2"
NaN

16. 아래 코드는 만약 array 사이즈가 크면 recursive 코드는 stack overflow가 발생한다. recursive 패턴을 유지한채로 어떻게 fix할수있나?

var list = readHugeList();

var nextListItem = function() {
    var item = list.pop();

    if (item) {
        // process the list item...
        nextListItem();
    }
};

해설

잠재적인 stack overflow를 nextListItem() 함수를 수정함으로써 피할수 있습니다.

var list = readHugeList();

var nextListItem = function() {
    var item = list.pop();

    if (item) {
        // process the list item...
        setTimeout( nextListItem, 0);
    }
};

stack overflow는 제거됩니다. 왜냐하면 호출스택이 아닌 이벤트 루프가 처리하기 때문입니다. nextListItem() 실행될때, 만약 item이 null이 아니면, timeout 함수는 이벤트 큐에 nextListItem()를 추가합니다. 그리고 함수는 종료되고, 호출스택은 초기화 됩니다. 이벤트큐 실행중 시간이 지나면 다음 item이 처리되고, 타이머가 다시 세팅되고 다시 nextListItem()을 실행시킵니다.
따라서 이 함수는 처음부터 끝까지 직접적인 재귀호출 없이 처리됩니다. 그래서 호출스택은 깨끗해집니다.

17. 클로저는 무엇인가? 예제를 제공하라

클로저는 스코프 체인으로 인해 내부함수가 외부함수에 있는 변수에 접근하는 것을 말한다. 클로저는 3개 스코프안에 변수에 접근 할수있다.

  • 자신의 스코프에있는 변수
  • 자신을 감싸고있는 함수 스코프에있는 변수
  • 전역 변수 아래 예제를 보자
var globalVar = "xyz";

(function outerFunc(outerArg) {
  var outerVar = 'a';
  
  (function innerFunc(innerArg) {
    var innerVar = 'b';
    
    console.log(
      "outerArg = " + outerArg + "\n" +
      "innerArg = " + innerArg + "\n" +
      "outerVar = " + outerVar + "\n" +
      "innerVar = " + innerVar + "\n" +
      "globalVar = " + globalVar);
    
  })(456);
})(123);

//output;
outerArg = 123
innerArg = 456
outerVar = a
innerVar = b
globalVar = xyz

18. 아래 코드의 output은 무엇이고, 왜그런지 설명하라. 그리고 클로저로 어떻게 활용할수있을까?

for (var i = 0; i < 5; i++) {
  setTimeout(function() { console.log(i); }, i * 1000 );
}

예상하는 바와 다르게 0,1,2,3,4 가 아니라 5,5,5,5,5가 출력된다.
그 이유는 각 setTimeout함수는 loop안에서 실행되고 전체 loop가 완료 된 후 실행된다. 그래서 마지막 i 값을 참조하고 있기 때문에 5 인것이다.

클로저는 위와 같은 문제를 각 iteration을 위한 특별한 스코프를 생성하여 각 유니크한 변수 i 값을 저장하여 해결 할 수 있다.

for (var i = 0; i < 5; i++) {
	(function(x) {
    	setTimeout(function() { console.log(x); }, x * 1000 );
    })(i);
}

//output
0,1,2,3,4

19. 아래 코드의 각 라인별 output은?

console.log("0 || 1 = "+(0 || 1));
console.log("1 || 2 = "+(1 || 2));
console.log("0 && 1 = "+(0 && 1));
console.log("1 && 2 = "+(1 && 2));

output:

0 || 1 = 1
1 || 2 = 1
0 && 1 = 0
1 && 2 = 2

javascript는 ||, && 인 논리 연산자를 가지고있으며, 이는 왼쪽에서 오른쪽으로 평가하여 첫번째로 완전히 결정된 논리값을 반환한다. || 연산자는 예를들어 x||y 일때 첫번째 x를 평가하고, boolean값으로써 해석한다. 만약 boolean 값이 true라면 true를 반환하고, 두번쨰인 y는 평가하지 않는다. or연산자를 이미 만족하였기 때문이다. &&x를 평가하고 boolean값으로 해석하고, false일 경우 false를 반환하고 y를 평가하지 않는다. &&는 첫번째, 두번째 모두 만족해야하는 연산자이다.
그러나 흥미로운 점은 &&연산자는 true로 평가되면, 그 자신의 값을 반환한다. 1&&2 는 2를 반환

20. 아래 코드의 output은 무엇인가?

console.log(false == '0')
console.log(false === '0')

output:

true
false

21. 아래 코드의 output은 무엇인가? 설명하여라

var a={},
    b={key:'b'},
    c={key:'c'};

a[b]=123;
a[c]=456;

console.log(a[b]);

output:
456

해설

객체 속성을 세팅 할때, javascript는 암시적으로 매개변수 값을 stringify화 할 것이다.
이 경우, b,c는 둘다 객체이기 때문에, 그들은 "[object Object]" 로 둘다 변환 될 것입니다. 결과적으로 a[b],a[c]는 동등하게 a["[object Object]"] 이여서 값이 교체되어 사용가능하게 됩니다. 그러므로 a[c]a[b]는 둘다 정확하게 같습니다.

22. 아래 코드의 output은? 설명하여라

console.log((function f(n){return ((n > 1) ? n * f(n-1) : n)})(10));

output:

10 팩토리얼 값이다.(10! 또는 3,628,800)

해설

기명함수 f()는 1을 반환하는 f(1) 을 호출할 때가지 재귀합니다. 그러므로 다음과 같습니다.

f(1): returns n, which is 1
f(2): returns 2 * f(1), which is 2
f(3): returns 3 * f(2), which is 6
f(4): returns 4 * f(3), which is 24
f(5): returns 5 * f(4), which is 120
f(6): returns 6 * f(5), which is 720
f(7): returns 7 * f(6), which is 5040
f(8): returns 8 * f(7), which is 40320
f(9): returns 9 * f(8), which is 362880
f(10): returns 10 * f(9), which is 3628800

23. 아래코드를 보고, output이 무엇인지, 왜그런지?

(function(x) {
    return (function(y) {
        console.log(x);
    })(2)
})(1);

output:

1 // 비록 x값은 내부함수에 세팅 되지 않지만,

24. 아래 코드에 output은 그리고 왜!?

var hero = {
    _name: 'John Doe',
    getSecretIdentity: function (){
        return this._name;
    }
};

var stoleSecretIdentity = hero.getSecretIdentity;

console.log(stoleSecretIdentity());
console.log(hero.getSecretIdentity());

output:

undefined
John Doe

25. 모든 DOM 노드를 탐색하는 함수를 작성하시오.

매개변수 조건

  • DOM 요소
  • 콜백 함수

DFS 알고리즘으로 dom을 탐색한다.

function Traverse(p_element, p_callback){
 p_callback(p_element);
 var list = p_element.children;

 for(var i=0; i<list.length; i++){
  Traverse(list[i], p_callback); // recursive call
 }
}
Clone this wiki locally