최근 프론트엔드 경력자 면접을 봤습니다.
리액트를 사용하는 프론트엔드 개발자를 구하는 자리기에
리액트 관련 질문을 예상했지만, 보기 좋게 빗나갔습니다.
질문의 대다수가 웹개발의 기본인 JS, CSS에 대한 질문이었고 설명을 하려니 잘 안되는 부분이 있었습니다. 제가 받은 질문 중 저 스스로도 잘 모르거나 헷갈렸던 질문들에 대해 정리를 해보았습니다.

편의상 높임말을 사용하지 않고 작성하였습니다. 내용 중 틀린 내용이 있을 겁니다. 댓글로 알려주시면 찾아서 더욱 정확하게 수정하도록 하겠습니다.

면접에 나왔던 질문

  1. DocType
  2. meta태그들
  3. script 태그의 위치
  4. 이벤트 버블링, 이벤트 캡쳐
  5. 호이스팅, 실행컨텍스트
  6. 이벤트루프
  7. 체이닝의 장단점
  8. DOM에서 id와 class의 차이, # SPA
  9. float을 해제하지 않으면 안되는 이유
  10. css 애니메이션과 js 애니메이션 차이
  11. 크로스 도메인 이슈와 대응방법, jsonp

1. DocType

웹표준을 지키는 문서타입이 여러 종류가 존재, 각 문서들의 차이는 엄격하게 보냐 느슨하게 보냐의 차이
예를들면 아래는 HTML 방식. 태그가 닫히지 않았지만, 이런 방식도 허용.조금 느슨하게 보는 것

<img src="../img.png" alt="이미지는 그 스스로가 내용입니다.">

그리고 우리가 익숙한 닫는 태그형태는 XHTML 방식

<img src="../img.png" alt="이미지는 그 스스로가 내용입니다."/>
<p>p 요소는 내용을 감싸기 때문에 열리고 닫힙니다.</p>

우린 더 엄격한 방식인 XHTML에 익숙하다.

문서는 HTML5외에 대표적으로 아래의 것들이 있다. 문서 포맷이라고 볼수 있겠다.

  • HTML 4.01
  • XHTML 1.0
  • XHTML 1.1

Strict

엄격한 규격으로, 비표준 규격인 center, font 등을 사용할 수 없다.(css의 center와 font와 다른듯) 이러한 값은 css로 대체할 수 있고 그것을 권장한다.

Transitional

과도기적 규격으로, 표준이 정립되지 않은 때에 만들어진 문서들

Frameset

거의 사용하지 않는 타입인데, 나모웹에디터 시절 html안에 html을 분리해서 작성하는 방식

아래 그림의 예처럼 보면 header.html, menu.html, content.html을 따로 만들어 하나의 페이지를 구성하는 방식

html4.01 Strict

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

html4.01 Transitional

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

HTML 4.01 Frameset

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" http://www.w3.org/TR/html4/frameset.dtd">

XHTML 1.0 Strict

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

XHTML 1.0 Transitional

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

XHTML 1.0 Frameset

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">

XHTML 1.1

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

HTML5

<!DOCTYPE html>

크롬에서는 비표준일지라도 잘 보여준다. 비표준 문서

아래는 IE8에서 렌더링 한것으로, IE8에서 렌더링에서는 레이아웃이 깨지는 것을 볼 수 있다.
IE8 에서 렌더링

출처 : 웨버스터디
참고 : Hooney.net


2. meta 태그

HTML문서가 어떤내용을 담고 있고, 키워드는 무엇이며, 누가 만들었는지에 대한 정보. 즉, 문서 자체의 특성

대표적인 속서값으로는 subject, keywords, title, author
검색엔진에 노출시킬 정보

<meta name="keywords" content="JS, CSS">
<meta http-equiv="content-type" content="text/html; charset=utf-8">

출처 : 홈짱닷컴


3. script 태그의 위치

html에서 script 태그는 어느 위치에나 올수 있음
그러나 브라우저는 HTML의 구조와 CSS 스타일을 렌더링하는 도중 자바스크립트를 만나게 되면, 이에 대한 해석과 구현이 완료될 때까지 브라우저 렌더링을 멈추게 되는데, 이때 프리징 현상이 발생될 수 있다.
이렇게 때문에 자바스크립트의 삽입 위치에 따라 스크립트 실행순서와 브라우저 렌더링에 영향을 미치게된다.

앞에 둘 경우,

<head> 
    <script> 
        //코드내용 
    </script> 
</head>

DOM이 해석되는 중에 스크립트를 읽기 때문에 장시간 완성되지 못한 화면을 노출 할 수 있다.
주로 문서를 초기화하는 가벼운 스크립트들이 사용되기를 권장한다.
DOM구조가 완성되지 않았기 때문에, 특정 엘리먼트의 참조가 실패할 수 있다. 그렇기 때문에
document.onload와 같은 로드 이벤트가 발생된 후 스크립트가 실행할 수 있도록 해야 한다.

....
    <script> 
        //코드내용 
    </script> 
</body>

브라우저가 렌더링이 완료된 상태에서 스크립트가 실행되기 때문에 화면이 뜬 후 스크립트를 읽는다.
대부분의 스크립트의 위치로 추천되는 위치다.

출처 : 흉내쟁이님의 블로그


4. 이벤트 버블링, 캡쳐링

이벤트 버블링 - 하위 엘리먼트에서 상위 엘리먼트로 이벤트가 전파되는 특성

<body>
    <div class="one">
        <div class="two">
            <div class="three">
            </div>
        </div>
    </div>
</body>
<script>

var divs = document.querySelectorAll('div');
divs.forEach(function(div) {
    div.addEventListener('click', logEvent);
});

function logEvent(event) {
    console.log(event.currentTarget.className);
}
</script>

이벤트 캡쳐링 - 버블링의 반대방향으로 진행

<body>
    <div class="one">
        <div class="two">
            <div class="three">
            </div>
        </div>
    </div>
</body>

<script>

var divs = document.querySelectorAll('div');
divs.forEach(function(div) {
    div.addEventListener('click', logEvent, {
        capture: true // default 값은 false입니다.
    });
});

function logEvent(event) {
    console.log(event.currentTarget.className);
}
</script>

출처 : 캡틴 판교님의 블로그


5. 실행컨텍스트

실행가능한 코드를 형상화하고 구분하는 추상적인 개념
자바의 스택영역의 역할과 유사하다.

스코프 - 어떤 변수의 대한 유효범위
자바스크립트에서 스코프는 함수단위로 가졌는데, 그 원인이 실행컨텍스트
6부터는 블락 스코프를 지원하는 변수들이 추가됐다. let, const

실행가능한 코드는 함수라고 생각할 수 있고,
형상화 대상은 변수, this, arguments, [[scope]], function 등이 있다.

실행 컨텍스트가 생성되어 코드가 실행되는 과정

1. 함수가 실행되면, 실행 컨텍스트를 생성한다.

      실행 컨텍스트       
 

2. 컨텍스트가 생성된 후, 이 컨텍스트에서 실행에 필요한 정보들을 담을 객체인 활성 객체를 생성한다.
활성 객체가 형상화한 대상을 가지는 객체다.

      실행 컨텍스트       
활성 객체

3. 활성 객체 내에, 매개변수의 정보를 갖는 arguments 객체를 생성하고, 함수가 호출될 때 사용된 인자들을 넣는다.

       활성 객체        
arguments --> [r]

4. 활성 객체 내에, 이 실행 컨텍스트의 스코프 체인을 생성한다.
스코프 체인은 리스트의 형태이며, 현재 함수를 호출한 함수의 스코프체인에 현재 활성객체를 마지막에 추가한 리스트를 현재 활성객체에 추가한다.
스코프 체인에 대한 설명 세바의 코딩교실 링크

       활성 객체        
arguments --> [] (유사배열)
[[scope]] --> [list]

5. 변수 객체가 생성되고, 함수가 가지고 있는 변수 및 객체 정보를 생성한다.
변수객체는 새로 생성되지만, 변수 객체와 활성 객체는 같다.
함수내에서 선언된 모든 변수를 생성한다. (코드가 실행되기 전에 변수에는 값이 할당되지 않기 때문에 생성한 변수, 함수들 모두 undefined가 된다.

    활성 객체 == 변수 객체    
arguments --> [] (유사배열)
[[scope]] --> [list]
foo : undefined
var : undefined
func : undefined

6. this에 대한 정보를 저장하며, 이 객체에 바인딩한다.
this에 바인딩할 대상이 없을 수도 있는데, 그때는 window로 바인딩한다.
브라우저의 경우 window 객체에 바인딩하지만 node라면 global 객체에 바인딩 --> 틀린 정보면 제보 부탁

    활성 객체 == 변수 객체    
arguments --> [] (유사배열)
[[scope]] --> [list]
foo : undefined
var : undefined
func : undefined
this : object

7. 활성 객체(변수 객체)가 생성되면 코드를 실행할 준비를 마침. 코드를 실행시킨다.

       실행 컨텍스트       
활성 객체

코드를 실행하면 할당하지 실제로 foo, var, func에 값이 할당된다.

8. 실행컨텍스트 파기. 코드를 실행 후 실행컨텍스트를 파기한다.

참조 : 화투의 개발블로그, PoiemaWeb


6. 이벤트 루프

자바스크립트의 큰 특징 중 하나가 '단일 스레드' 기반의 언어라는 점이다. 이벤트 루프는 이러한 자바스크립트가 '동시성'을 지원하는 방식이다.동기방식 프로그래밍(JAVA)에 익숙한 사람들이 비동기 방식의 이벤트 루프에 익숙치 않는 경우가 있다.

ES6 이전의 ECMAScript에는 이벤트 루프가 없다고 한다. 이런 동시성에 대한 처리는 브라우저나 Node.js가 담당한다고 한다.(그래서 js책에는 이벤트 루프에 대한 언급이 거의 없고 node.js 책에는 포함시키는 듯) V8같은 자바스크립트 엔진은 단순히 호출 스택을 사용하여 요청을 순차적으로 호출 스택에 담아 처리만 한다.

이벤트 루프는 아래의 그림으로 표현된다. setTimeout, XMLHttpRequest가 같은 함수들은 자바스크립트 엔진이 아닌 web APIs에 정의가 되어 있다.

잠깐 소개하자면 노드는 아래 그림과 같은 구조를 가지며, libuv 라는 라이브러리를 사용하여 이벤트 루프로 동작한다.

아래 예제를 보자.

function delay() {
    for (var i = 0; i < 100000; i++);
}

function foo() {
    delay();
    bar();
    console.log('foo!'); // (3)
}
function bar() {
    delay();
    console.log('bar!'); // (2)
}
function baz() {
    console.log('baz!'); // (4)
}

setTimeout(baz, 10); // (1)
foo();

------------------ console -------------------
bar!
foo!
baz!

call stack을 나타내면 아래오 와같다. 아시겠지만 setTimeout에서 0.01초 후 실행되는 것을 보장하지 못했다. setTimeout은 실행 시간을 보장하지 못한다.!

setTimeout 함수는 실행 후 callback함수(여기선 bar)를 task queue에 넣는다. task queue에 들어간 이벤트는 바로 실행되지 않는다.
그리곤 foo, bar가 위 그림 처럼 호출 스택에 쌓이게 되고, call stackstack frame(call stack에서 하나의 스택을 스택 프레임이라 한다.)이 남아 있다면, event loop는 아무것도 하지 않는다.
foo 함수가 실행된 후 call stack은 비어있게 되고, event looptask queue에서 대기 중인 첫 번째 태스크를 꺼내 call stack에 추가한다.

event loop를 가상 코드로 나타내면 아래와 같다.

while(queue.waitForMessage()){
  queue.processNextMessage();
}

비동기 api는 콜백 함수를 task queue에 추가 한다.

그런데, micro task라는 개념이 등장 한다. 아래 예제 코드를 보자.

setTimeout(function() { // (A)
    console.log('A');
}, 0);
Promise.resolve().then(function() { // (B)
    console.log('B');
}).then(function() { // (C)
    console.log('C');
});

예상해보자.. A -> B -> C 일 까? B -> C -> A일 까?
Promise가 바로 micro task를 사용 한다. micro task는 일반 task 보다 높은 우선순위를 가진다고 할 수 있다. 그렇기 때문에 B -> C -> A 순으로 출력하게 된다.

프라미스A+ 스펙문서를 보면 Pomisemicro task와 일반 task모두 사용할 수 있다고 적혀있다

출처 : Toast Meetup
참조 : Huiseoul - v8 엔진, Huiseoul - js의 엔진, 런타임, 콜스택


7. 체이닝의 장단점

장점은 코드가 간결하지만, 디버깅을 어렵게 한다.


8. DOM에서 id와 class의 차이, # SPA

id는 유일한 값, class는 여러번 중복이 가능하며, 여러 값을 띄어쓰기로 구분하여 넣을 수 있음.
문서내에 id를 통해 해당 위치로 이동할 수 있다. (href="#id")
이것을 응용하여 SPA를 만든다고 한다. 바닐라 스크립트로 SPA 만드는 예제


9. float을 해제하지 않으면 안되는 이유

예제를 먼저 보면, container 내부에 left와 right 두개의 박스가 들어있는 태그다. 물론 left, right는 각각 float left, float right를 주었다.

<head>
  <style type="text/css">
    .container {
      padding: 10px;
      border: 10px solid #ddd;
      background: #fff;
    }

    .red-box {
      width: 55%;
      padding: 10px;
      border: 10px solid #000;
      background: #f00;
    }
  </style>
</head>

<body>
  <div class="container">
    <p>container</p>
    <div id="left" class="red-box" style="background:aqua; float:left">
      <p><strong>left</strong></p>
    </div>
    <div class="red-box" style="float: right;">
      <p><strong>#float-box {float:right;}</strong></p>
    </div>
  </div>
  <div style="background:plum">
    <p>footer</p>
  </div>
</body>

</html>


이런 형태를 예상했지만, 아래 코드는 조금 다르게 그려진다

이런식으로 자식이 float속성을 가지면 부모의 너비는 자식의 너비를 계산하지 않고 그리는 것이다.
이러한 문제를 해결하는 4가지 방법이 존재하지만, 그 중 추천되는 방식 두 가지를 소개한다. 나머지 두 방법은 부분적으로 해결 가능하나 사이드 이펙트를 발생시킬 수 있기 때문에 소개하지 않는다.

첫 번째 방법 : float을 빈 엘리먼트로 clear하는 방법
float자식을 가지는 container의 마지막 자식에 빈 엘리먼트를 추가하고 clear: both 스타일을 준다.


<body>
  <div class="container">
    ...
    <div style="clear: both;"></div>
  </div>
</body>

두 번째 방법 : :after에 clear속성 넣기

.container:after {
  content: "";
  clear: both;
  display: block;
}

이 방법은 세가지 속성을 모두 추가 해야 동작 한다. 대신 불필요한 빈 엘리먼트가 추가 되지 않기 때문에 가장 추천되는 방식이다. (내부적으로 빈 텍스트 영역이 생긴다고 한다.)

출처 : 나라디자인


10. css 애니메이션과 js 애니메이션 차이

비교 대상 css 애니메이션, jquery 애니메이션, velocity.js 애니메이션

속도차이
velocity.js > css 애니메이션 > jquery 애니메이션

velocity.js, GSAP 등은 구글이나 어도비 같은 회사들이 모바일 페이지에서 최적화된 애니메이션을 위해 사용하는 라이브러리

jquery 애니메이션이 느린 이유는 애초에 jquery 가 성능에 초점을 두지 않았기 때문
jquery는 레이아웃 스레싱이나 가비지 콜렉션 트리거가 발생하기도 함
아래는 레이아웃 스레싱이 일어나는 상황과 아닌 상황을 보여줌

var currentTop, currentLeft;

// 레이아웃 스레싱 발생
currentTop = element.style.top;        // 레이아웃을 계산
element.style.top = currentTop;

currentLeft = element.style.left;    // 위에서 style.top을 설정했기 때문에 레이아웃을 다시 구함
element.style.left = currentLeft + 1;


// 레이아웃 스레싱이 없음
currentTop = element.style.top;        // 레이아웃을 계산
currentLeft = element.style.left;    // 변경 사항이 없기 때문에 레이아웃을 그대로 가져옴

element.style.top = currentTop;
element.style.left = currentLeft + 1;

레이아웃 스레싱 참고

jquery 애니메이션이 느린 또 다른 이유로는 requestAnimationFrame이 아닌 setInterval을 사용하는 것이다. (requestAnimationFame은 줄여서 RAF로 표기)

var startingTop = 0;
function tick () {
    element.style.top = (startingTop += 1/60);
}

// 60fps를 만족하기 위해 16ms마다 실행. (16ms * 60 == 960ms, 약 1000ms)
setInterval(tick, 16);

// 브라우저가 기본적으로 60fps로 실행시킴. 브라우저가 알아서 상태를 판단하여, 때때로 몇몇 프레임을 스킵할 수 있음
window.requestAnimationFrame(tick);

setInterval의 경우 명령을 어떡해서든 실행시키기 때문에 성능이 좋지 않을 수 있습니다. 반면에, RAF는 조금 더 유연한 대처가 가능하죠.

google developers web group에서는 UI 요소에 대해 더 작은 자체 포함 상태가 있는 경우 CSS 애니메이션을 사용하고, 보다 세밀한 제어를 위해서는 자바스크립트 사용을 권장한다. 링크1
또, 가급적이면 애니메이션을 opacitytransform로 제한할 것을 권장한다. 그 이유로는 애니메이션이 레이아웃(리플로우)을 자주 유발하기 때문이다. 링크2

레이아웃(리플로우)을 발생시키는 css 속성을 css 트리거라고 한다

다음은 velocity.js의 애니메이션 최적화 방법이다.

  1. 레이아웃 스레싱을 최소화
  2. DOM 질의를 최소화 하기 위해 속성값을 캐싱
  3. px, em 같은 단위 변환 비율을 캐싱
  4. 업데이트가 시각적으로 판별할 수 없는 수준이면 스타일 업데이트 스킵

높은 수준의 최적화 애니메이션을 사용하기 위해서는 라이브러리를 사용하는 것이 제일 좋을 것 같다.
출처 : CyberImagination Blog


크로스 도메인 이슈 (CORS)

웹 개발시 주요한 이슈중 하나로, 웹 개발을 하다보면 어떤 경로던 이 이슈를 마주하게 된다.
동일 출처 정책(same-origin-policy)은 하나의 웹 페이지에서 다른 도메인 서버에 요청하는 것을 제한하는 것이다. 제한하는 이유는 간단한데, 내가 네이버라고 가정해보자.
누군가 다른 포탈 서비스를 만들고, 네이버에서 검색한 결과만 가져온다면 문제가 되지 않을까? 때문에 보통의 브라우저에서는 외부 도메인으로의 Ajax로 요청을 보낼 때, cors를 체킹한다.
아래는 크롬에서 발생하는 cors 에러창이다.

그런데 어떤 경우에는 이러한 제한이 또다른 문제를 발생시킨다는 것이다. 이번엔 내가 카카오라 하자. 카카오톡 앱에서는 #검색으로 다음에서의 검색 결과를 가져오는 서비스가 있다. 이때, 다음과 카카오는 다른 도메인을 사용하기 때문에 cors문제가 발생하게 된다. 특정 사용자에게는 허용을 해줘야 하는 경우가 생기게 된다.
(심지어 https://abc.com:8080과 https://abc.com:8888 사이에서도 CORS문제는 발생한다!!!)

이러한 문제를 해결할 수 있는 여러 방법들이 있지만, 제일 일반적인 방법인 헤더를 이용하는 방법과 jsonp에 대해서 소개한다.
먼저 헤더를 이용하는 방법이다.

  • response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
    • POST, GET, OPTIONS, DELETE 요청에 대해 허용하겠다는 의미.
  • response.setHeader("Access-Control-Max-Age", "3600");

    • HTTP Request 요청에 앞서 Preflight Request 라는 요청이 발생되는데, 이는 해당 서버에 요청하는 메서드가 실행 가능한지(권한이 있는지) 확인을 위한 요청. Preflight Request는 OPTIONS 메서드를 통해 서버에 전달된다. (위의 Methods 설정에서 OPTIONS 를 허용해 주었다.)

    여기서 Access-Control-Max-Age 는 Preflight request를 캐시할 시간. 단위는 초단위이며, 3,600초는 1시간. Preflight Request를 웹브라우저에 캐시한다면 최소 1시간동안에는 서버에 재 요청하지 않음.

  • response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
    이는 표준화된 규약은 아니지만, 보통 AJAX 호출이라는 것을 의미하기 위해 비공식적으로 사용되는 절차. JQuery 또한 AJAX 요청 시, 이 헤더(x-requested-with)를 포함하는 것을 확인하실 수 있음. 여기서는 이 요청이 Ajax 요청임을 알려주기 위해 Header 에 x-request-width를 설정. Form을 통한 요청과 Ajax 요청을 구분하기 위해 사용된 비표준 규약지만, 많은 라이브러리에서 이를 채택하여 사용. (참고로 HTML5 부터는 Form 과 Ajax 요청을 구분할 수 있는 Header가 추가됨.)
  • response.setHeader("Access-Control-Allow-Origin", "*");
    * 는 모든 도메인에 대해 허용하겠다는 의미. 즉 어떤 웹사이트라도 이 서버에 접근하여 AJAX 요청하여 결과를 가져갈 수 있도록 허용하겠다는 의미.
    만약 보안 이슈가 있어서 특정 도메인만 허용해야 한다면 * 대신 특정 도메인만을 지정할 수 있음.

출처: 이러쿵저러쿵

JSONP

CORS를 해결하는 유명한 방법 중 하나가 JSONP를 사용하는 것이다
jsonp의 원리는 다음과 같다.

<script/> 태그는 same-origin-policy (SOP) 정책에 속하지 않는다는 사실을 근거로, 서로 다른 도메인간의 javascript 호출을 위하여 jsonp (JSON with Padding) 이 사용되었다.
jsonp를 사용하기 위해서는 필수적으로 서버단에서 jsonp의 포맷을 따라야한다. 이것은 jsonp를 사용하기 위한 “규칙”이다.

<script type="text/javascript" src="http://kingbbode.com/result.json"></script>

html 문서에 script태그는 보안 정책에 적용되지 않는 점을 이용한 것

<script type="text/javascript" src="http://kingbbode.com/result.json?callback=parseResponse"></script>

여기서 script 태그는 javascript 내용을 포함시킨 것이 아니라 실행시킨 것이다.
아래 코드는 jsonp를 호출할 script태그를 동적으로 생성하는 코드다. 물론 생성과 동시에 실행시킨다.

var script = document.createElement('script'); 
script.src = '//kingbbode.com/jsonp?callback=parseResponse';
document.getElementsByTagName('head')[0].appendChild(script); 
function parseResponse(data){ 
  //callback method 
}

그러나 jsonp는 서버에서 지원하지 않으면 사용할 수 없다.
parseResponse함수가 실행되려면 script태그는 다음과 같아야 한다.

<script type="text/javascript">
    parseResponse({"Name": "Foo", "Id": 1234, "Rank": 7})
</script>

서버에서는 요청된 내용을 json형태의 응답을 만들어 callback 파라미터로 전달 받은 콜백이름을 래핑하여 위와 같은 응답을 내려준 것이다.

출처: Seotory, 개발노트 - kingbbode

참조: MDN - HTTP 접근 제어(CORS)


여기까지가 인터뷰에서 질문에 대한 정리입니다. 웹 개발자 면접을 준비하시는 분들에게 도움이 되었으면 좋겠습니다.