본문 바로가기

FrontEnd

빅테크 프론트엔드 기술 인터뷰 : JS 편

반응형

Front End Interview Handbook의 JS 파트를 번역 및 정리한 글입니다

faang

Meta, Amazon., Google과 같은 미국 빅테크 프론트엔드 면접의 기출 문제라 합니다.

2019년 기준으로 작성된 것들이 꽤 있어서, 몇가지 경험 기반 답변들은 스스로 생각해 보시는 것도 좋은 듯 합니다.

원문 링크

 

JavaScript trivia questions in front end interviews | Front End Interview Handbook

Answers to Front-end Job Interview Questions - JS Questions. Pull requests for suggestions and corrections are welcome!

www.frontendinterviewhandbook.com

 

1. 이벤트 위임(event delegation)을 설명해 주세요

이벤트 위임은 이벤트 리스너를 하위 컴포넌트에 추가하는 대신 상위 컴포넌트에 추가하는 기술입니다.
이벤트 버블링으로 인해 하위 컴포넌트에서 이벤트가 트리거될 때, 상위 컴포넌트의 이벤트 리스너가 실행됩니다.
 
이 기술의 이점은 다음과 같습니다.
  • 하위 컴포넌트에 이벤트 핸들러를 연결하지 않고 상위 컴포넌트에 하나의 이벤트 핸들러만 사용하여 메모리를 절약할 수 있습니다.
  • 하위 컴포넌트 제거/추가 시 이벤트 리스너를 해제 / 바인딩 할 필요가 없습니다.

참고


2. this 키워드의 동작을 설명해 주세요

this의 값은 함수가 호출되는 방식에 따라 달라집니다.
  1. 함수를 호출할 때 new 키워드를 사용하면 함수 내부의 this는 새로운 객체입니다.
  2. 함수를 호출/생성하기 위해 apply, call 또는 bind를 사용하는 경우, 함수 내부의 this는 인수로 전달되는 객체입니다.
  3. obj.method() 와 같이 함수가 메서드로 호출되는 경우, this는 함수를 속성으로 갖는 객체입니다.
  4.  함수가 자유 함수 호출(free function invocation)로 호출되는 경우(위에 제시된 조건 없이 호출됨을 의미) this는 전역 객체 입니다.
    1. 브라우저에서는 window 객체입니다. 엄격 모드('use strict')인 경우 전역 객체가 아닌 undefined 입니다.
  5. 위의 규칙 중 여러 개가 적용되는 경우 먼저 언급한 규칙이 우선합니다
  6. ES2015 화살표 함수인 경우 위의 모든 규칙을 무시하고 함수 선언 시 주변 scope의 this 값을 사용합니다. 

참고


3. ES6 도입과 함께 변경된 작업 방식 중 하나의 예를 들어주실 수 있나요?

ES6에서는 주변 환경의 렉시컬 스코프(enclosing lexical scope)를 사용하는 화살표 함수(arrow functions)을 사용할 수 있습니다.
화살표 함수는 사용이 편리하지만, 호출자가 .call 또는 .apply를 통해 컨텍스트를 제어하는 ​​것을 방지합니다.
그 결과 jQuery와 같은 라이브러리는 이벤트 핸들러 함수에 이를 적절하게 바인딩하지 않습니다.
대규모 레거시 애플리케이션을 리팩토링할 때 이를 염두에 두는 것이 중요합니다.


4. 프로토타입 상속의 동작을 설명해 주세요

(Object.create(null)로 생성된 객체를 제외하고.)
모든 JavaScript 객체는, 객체의 "프로토타입"이라고 하는 다른 객체에 대한 참조인 __proto__ 속성을 갖고 있습니다.
 
JavaScript 엔진은 객체 속성에 액세스할 때 해당 객체에서 속성을 찾을 수 없는 경우
__proto__ 중 하나에 정의된 속성을 찾을 때까지 객체의 __proto__ 및 __proto__의 __proto__ 등을 살펴봅니다.
이 동작은 고전적인 상속을 모방하지만, 실제로는 상속보다 위임(delegation than inheritance)에 가깝습니다.

참고 : 프로로타입의 예시

function Parent() {
  this.name = 'Parent';
}

Parent.prototype.greet = function () {
  console.log('Hello from ' + this.name);
};

const child = Object.create(Parent.prototype);

child.cry = function () {
  console.log('waaaaaahhhh!');
};

child.cry();
// waaaaaahhhh!

child.greet();
// hello from Parent

child.constructor;
// ƒ Parent() {
//   this.name = 'Parent';
// }

child.constructor.name;
// 'Parent'
참고할 사항은 다음과 같습니다.
  • .greet은 child에 정의되어 있지 않으므로 엔진은 프로토타입 체인으로 올라가서 상위에서 상속된 .greet를 찾습니다.
  • 프로토타입 메서드를 상속하려면 다음 방법 중 하나로 Object.create를 호출해야 합니다.
    • Object.create(Parent.prototype);
    • Object.create(new Parent(null));
    • Object.create(objLiteral);

이렇게 하면, Childr의 생성자는 Parent를 가리키게 됩니다.

해당 문제를 고치는 방법은 다음과 같습니다. (혹은 ES6 Class 문법을 사용합니다.)

function Parent() {
  this.name = 'Parent';
}

Parent.prototype.greet = function () {
  console.log('Hello from ' + this.name);
};

function Child() {
  Parent.call(this);
  this.name = 'Child';
}

Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

const child = new Child();

child.greet();
// hello from Child

child.constructor.name;
// 'Child'

참조


5. AMD와 CommonJS에 대해 아시나요?

둘 다 ES2015가 등장하기 전까지 JavaScript에 기본적으로 존재하지 않았던 모듈 시스템을 구현하는 방법입니다.

  • CommonJS는 동기식이며,
  • CommonJS는 서버 측 개발을 염두에 두고 설계되었습니다.
  • AMD(Asynchronous Module Definition)는 비동기식입니다.
  • AMD는 모듈의 비동기 로드를 지원하므로 브라우저용으로 더 적합합니다.
CommonJS가 다른 언어의 import 문을 작성하는 스타일에 더 가깝습니다.
AMD 구문은 매우 장황하며, 대부분의 경우 AMD는 불필요하다고 생각합니다.
모든 JavaScript를 하나의 연결된 번들 파일로 제공하면 비동기 로딩의 이점을 누릴 수 없기 때문입니다.
 
또한 CommonJS 구문은 노드 작성 모듈 스타일에 더 가깝고
클라이언트 측과 서버 측 JavaScript 개발 간에 전환할 때 컨텍스트 전환 오버헤드가 적습니다.
동기식 및 비동기식 로딩을 모두 지원하는 ES2015 모듈 덕택에 마침내 한 가지 접근 방식을 고수할 수 있게 되어 기쁩니다.
브라우저와 노드에서 완벽하게 지원하는 것은 아니지만 트랜스파일러를 사용하여 코드를 쉽게 변환할 수 있습니다.
 
참조

6.  IIFE 함수 function foo(){}()가 왜 동작하지 않을까요?

function foo(){}()
IIFE(Immediately Invoked Function Expressions)는 즉시 호출된 함수 표현식을 의미합니다.
JavaScript 파서는 function foo(){ }()를 다음과 같이 해석합니다.
  1. function foo(){ }를 함수 선언문으로 분석합니다.
  2. ()를 함수 호출로 분석합니다.
함수 선언문 function foo(){ }를 통해 새로운 함수가 선언되고 undefined를 리턴합니다.
()는 함수 호출 연산자 이지만, ()은 function foo{}()에 접근할 방법이 없으므로,
Uncaught SyntaxError: Unexpected token 오류가 발생합니다.
 
따라서 아래 두 가지 방법으로 해당 문제를 고칠 수 있습니다.

1. (function foo(){ })()로 앞에 괄호를 하나 더 추가합니다.

function으로 시작하는 문은 함수 선언문으로 간주됩니다.
이 함수를 () 안에 래핑하면 함수 표현식이 됩니다.
따라서 해당 표현식이 리턴하는 함수를 ()로 호출할 수 있습니다.
이러한 함수는 전역 범위에 노출되지 않으며, 본문 내에서 자신을 참조할 필요가 없는 경우 이름을 생략할 수도 있습니다.

2. void 연산자를 사용합니다.

즉 void function foo(){ }()와 같이 사용할 수 있습니다.
이러한 접근 방식에는 한 가지 문제가 있습니다.
주어진 식의 평가는 항상 undefined 입니다.

const foo = void (function bar() {
  return 'foo';
})();

console.log(foo); // undefined

참조


7.  null, undefined, 선언되지  않은 변수(undeclared variables)의 차이를 설명해 주세요

var, let 또는 const를 사용하여 이전에 생성되지 않은 식별자에 값을 할당하면 선언되지 않은 변수가 생성됩니다.
  • 선언되지 않은 변수는 현재 스코프 밖에 전역적으로(globally) 정의됩니다.
  • 엄격 모드(strict mode)에서 선언되지 않은 변수에 할당하려고 하면 ReferenceError가 발생합니다.
선언되지 않은 변수는 전역변수처럼 반드시 피해야 합니다.
linter를 사용하는 경우 일반적으로 선언되지 않은 변수를 참조하지 않는지 쉽게 확인할 수 있습니다.
 
참고 : 해당 현상을 확인하려면 사용법을 try/catch 블록에 래핑해 봅니다.
function foo() {
  x = 1; // Throws a ReferenceError in strict mode
}

foo();
console.log(x); // 1
값이 undefined인 변수는 선언되었지만 값이 할당되지 않은(unassigned) 변수입니다.
값을 리턴하지 않는 함수의 실행 결과가 변수에 할당되어도 해당 변수의 값이 undefined가 됩니다.
 
undefined의 타입은 'undefined' 입니다.
이를 확인하려면 ===(strict equality operator) 또는 'undefined' 문자열을 리턴하는 typeof를 사용합니다.
값이 null인 경우에도 true를 반환하므로 ==(abstract equality operator)를 사용하면 안됩니다.
var foo;
console.log(foo); // undefined
console.log(foo === undefined); // true
console.log(typeof foo === 'undefined'); // true

console.log(foo == null); // true. Wrong, don't use this to check!

function bar() {}
var baz = bar();
console.log(baz); // undefined
 
null인 변수는 명시적으로 null 값을 할당한 변수입니다.
null의 타입은 'object'입니다.
 
undefined와 동일하게 값이 없음을 나타내지만, undefined와 달리 명시적으로 할당되었다는 점에서 다릅니다.
null을 확인하려면 ===(strict equality operator)를 사용합니다.
값이 undefined인 경우에도 true를 반환하므로 ==(abstract equality operator)를 사용하면 안됩니다.
var foo = null;
console.log(foo === null); // true
console.log(typeof foo === 'object'); // true

console.log(foo == undefined); // true. Wrong, don't use this to check!
변수를 undefined인 상태로 두거나(초기화 생략;unassigned) 선언되지 않은(undeclared) 상태로 두지 않는 것이 좋습니다.
값이 아직 준비되지 않은 경우, 명시적으로 null을 할당합니다.
linter를 사용하는 경우 선언되지 않은 변수를 참조하지 않는지 쉽게 확인할 수 있습니다.
 
References

8.  클로저(closure)를 설명해 주세요

클로저는 선언된 함수와 해당 함수가 선언된 어휘 환경(lexical environment)의 조합입니다.
클로저는 외부 함수의 변수에 엑세스 할 수 있는 함수입니다.
클로저는 외부 함수가 반환된 후에도 외부 함수의 스코프체인에 엑세스 할 수 있는 함수입니다.
  • 외부 함수 : 해당 함수의 선언을 둘러싸는 함수; enclosing function
"lexical"라는 단어는 어휘 범위 지정이 변수가 사용 가능한 위치를 결정하기 위해,
소스 코드 내에서 변수가 선언된 위치를 사용한다는 사실을 나타냅니다.

언제 사용할까요?

  • 캡슐화를 위해 사용합니다.
    • 클로저가 있는 프라이빗 메소드 에뮬레이션.
    • module pattern에서 일반적으로 사용됩니다.
  • 부분 적용과 커링에 사용합니다.
References

9. .forEach 반복문과 .map 반복문의 차이점과 사용방법에 대해 설명해 주세요

forEach

  • 각 배열의 요소를 순회합니다.
  • 각 요소에 대해 콜백을 실행합니다.
  • 값을 반환하지 않습니다.
const a = [1, 2, 3];
const doubled = a.forEach((num, index) => {
  // Do something with num and/or index.
});

// doubled = undefined
map
  • 각 배열의 요소를 순회합니다.
  • 각 요소에 대해 함수를 호출하여 각 요소를 새 요소에 "매핑"하고, 결과적으로 새 배열을 만듭니다.
.forEach와 .map()의 주요 차이점은 .map()은 새 배열을 반환한다는 것입니다.
원래 배열을 변경하지 않으면서 각 배열 요소를 수정할 필요가 있다면 .map()이 좋습니다.
단순 배열 순회에는 forEach를 사용하는 것이 좋습니다. (부수 효과)
 


10. 익명 함수의 사용사례를 설명해 주세요

1. 로컬 스코프 내 코드 캡슐화

IIFE 내에서 선언된 변수가 전역 범위로 누출되지 않도록 합니다.

(function () {
  // Some code here.
})();

2. 함수형 프로그래밍 구조의 인수로 사용

한 번 사용하고 다른 곳에서 사용할 필요가 없는 콜백을 정의할 때 사용합니다.
핸들러를 호출하는 코드 내에서 바로 정의하면, 함수를 찾기 위해 다른 곳을 볼 필요가 없기에,
코드는 더 독립적이고 읽기 쉬워집니다.
setTimeout(function () {
  console.log('Hello world!');
}, 1000);

3. 함수형 프로그래밍 구조의 인수로 사용할

const arr = [1, 2, 3];
const double = arr.map(function (el) {
  return el * 2;
});
console.log(double); // [2, 4, 6]

References


11. 코드를 구조화하는 방법을 설명해 주세요(모듈 패턴, 상속)

과거에는 더 많은 OOP 접근 방식을 권장하는 Backbone을 사용하여 모듈 패턴을 구현했습니다.
Backbone 모델을 생성하고 모델에 메소드를 추가했습니다.

 

모듈 패턴은 여전히 ​​훌륭하지만,

요새는 Flux 아키텍처를 기반으로 단방향 데이터 흐름을 활용하는 React/Redux를 사용합니다.

일반적인 객체를 사용하여 내 앱의 모델을 나타내고, 이러한 객체를 조작하는 유틸리티 순수 함수를 작성합니다.

상태는 다른 Redux 애플리케이션과 마찬가지로 액션과 리듀서를 사용하여 조작합니다.

 

가능하면 고전적 상속을 사용하지 않습니다. 개인적으로 준수하는 원칙들은 다음과 같습니다,(these rules)


12. 호스트 객체와 네이티브 객체의 차이점은 뭔가요?

  • 네이티브 객체는 String, Math, RegExp, Object, Function 등과 같이 ECMAScript 사양에 정의된 JavaScript 언어의 일부인 객체입니다.
  • 호스트 객체는 window, XMLHTTPRequest 등과 같이 런타임 환경(브라우저 또는 노드)에서 제공하는 객체입니다.

References


13. function Person(){}, var person = Person(), var person = new Person()의 차이점은 뭘까요?

(해당 질문의 의도는 JS 생성자에 대한 이해를 파악하기 위함입니다.)

기술적으로 function Person(){}는 일반적인 함수 선언문 입니다.

관례로 함수명을 PascalCase로 선언하면, 해당 함수를 생성자로 사용하겠다는 뜻입니다.

var person = Person()은 Person을 생성자가 아닌 함수로 호출합니다.
함수를 생성자로 사용하려는 경우 이와 같이 호출하는 것은 일반적인 실수입니다.
일반적으로 생성자는 아무 것도 반환하지 않습니다.
일반 함수처럼 생성자를 호출하면 undefined 리턴값이 할당되빈다.
 
var person = new Person()은 new 연산자를 사용하여 Person 객체의 인스턴스를 만듭니다.
해당 함수는  Person.prototype를 상속합니다.
Object.create(Person.prototype)와 같이 Object.create를 사용할 수도 있습니다.
function Person(name) {
  this.name = name;
}

var person = Person('John');
console.log(person); // undefined
console.log(person.name); // Uncaught TypeError: Cannot read property 'name' of undefined

var person = new Person('John');
console.log(person); // Person { name: "John" }
console.log(person.name); // "john"

References


14. .call과 .apply의 차이점이 뭔가요?

.call과 .apply는 모두 함수를 호출하는 데 사용됩니다.
둘 다 첫 번째 매개변수는 함수 내에서 this 값으로 사용되지만,
.call은 쉼표로 구분된 인수 목록을 다음 인수로 사용하고 .apply는 인수 배열을 다음 인수로 사용합니다.
a는 Array의 a, c는 Comma-separeted의 C로 암기하세요
function add(a, b) {
  return a + b;
}

console.log(add.call(null, 1, 2)); // 3
console.log(add.apply(null, [1, 2])); // 3

15. Function.prototype.bind에 대해 설명해 주세요

MDN에 의하면..

bind() 메서드는 호출될 때 this 키워드가 인수로 전달된 값으로 사용되며,
콤마로 구분된 일련의 값들을 순서대로 기억해두어, 나중에 필요한 값만 받아 호출하는 새 함수를 리턴합니다.

제 경험상 객체의 메서드를 다른 함수의 인수로 전달할 때, 부분 적용을 구현할 때 좋습니다.

 

References


16. document.write()는 언제 쓰나요?

document.write()는 document.open()을 통해 연 문서 스트림에 텍스트 문자열을 씁니다.
페이지가 로드된 후 document.write()가 실행되면 전체 문서를 지우고(<head> 및 <body>가 제거됨!)
콘텐츠를 파라미터의 값으로 바꾸는 document.open을 호출합니다.
따라서 일반적으로 위험하고 오용되기 쉬운 것으로 간주됩니다.
  • 자바스크립트가 사용 가능할 때에만 스타일을 포함하기 위해 사용합니다.
  • 분석을 위한 코드에서 사용합니다.
  • 스크립트를 병렬로 로드하고 실행 순서를 유지하기 위해 사용합니다.

하지만 모던 브라우저는 document.write 없애 해당 기능을 구현할 수 있으므로, 권장하지 않습니다.

References


17.  feature detection, feature inferenced, UA 문자열 활용은 무슨 차이가 있나요?

Feature Detection(기능 감지)

브라우저가 특정 코드 블록의 지원 여부에 따라 다른 코드를 실행하여

일부 브라우저에서 충돌/오류없는 환경을 제공하기 위한 기능입니다.

if ('geolocation' in navigator) {
  // Can use navigator.geolocation
} else {
  // Handle lack of feature
}
Modernizr라는 좋은 라이브러리가 있습니다.

Feature inference(기능 추론)

기능 감지와 유사하지만, 특정 기능이 존재하면 같이 존재할 기능을 사용하는 것입니다.

if (document.getElementsByTagName) {
  element = document.getElementById(id);
}

이것은 별로 권장되지 않습니다. 기능 감지가 더 확실합니다. 

UA String

네트워크 프로토콜 피어가
요청을 보내는 소프트웨어 사용자 에이전트의 애플리케이션 유형, 운영 체제, 소프트웨어 공급업체
또는 소프트웨어 버전을 식별할 수 있도록 브라우저가 보고하는 문자열입니다.
 
navigator.userAgent를 통해 액세스할 수 있습니다.
그러나 UA 문자열은 구문 분석하기 까다로우며 스푸핑될 수 있습니다.
예를 들어 Chrome은 Chrome과 Safari로 모두 보고합니다.
따라서 Safari를 감지하려면 Safari 문자열과 Chrome 문자열이 없는지 확인해야 합니다.
따라서 기능 감지를 위해 해당 문자열을 사용하는 것은 권장하지 않습니다.

References


18. Ajax에 대해 설명해 주세요

Ajax는 프레젠테이션 계층에서 데이터 교환 계층을 분리
 
Ajax(asynchronous JavaScript and XML는 비동기 웹 응용 프로그램을 만들기 위해
클라이언트 측에서 많은 웹 기술을 사용하는 웹 개발 기술의 집합입니다.
 
Ajax를 사용하면 웹 애플리케이션이 기존 페이지의 시각적 표현 및 동작을 방해하지 않고,
백그라운드에서 비동기적으로 서버에 데이터를 보내고 서버에서 데이터를 검색할 수 있습니다.
 
Ajax는 프레젠테이션 계층에서 데이터 교환 계층을 분리함으로써,
웹 페이지 및 확장 웹 애플리케이션이 전체 페이지를 다시 로드할 필요 없이 동적으로 콘텐츠를 변경할 수 있도록 합니다.

최신 구현에서는 일반적으로 XML 대신 JSON을 사용합니다. JSON이 JavaScript의 네이티브 객체라는 이점 때문입니다. 
최근 비동기 통신에는 XMLHttpRequest API 또는 Fetch API를 자주 사용합니다.

References


19. Ajax 사용의 장단점은 무엇입니까?

장점

  • 더 나은 상호 작용. 전체 페이지를 다시 로드할 필요 없이 서버의 새 콘텐츠를 동적으로 변경할 수 있습니다.
  • 스크립트와 스타일시트는 한 번만 요청하면 되므로 서버 연결을 줄입니다.
  • 페이지에서 상태를 유지할 수 있습니다.
    • 기본 컨테이너 페이지가 다시 로드되지 않기 때문에 JavaScript 변수 및 DOM 상태가 유지됩니다.
  • SPA의 대부분의 장점이 여기에서 나옵니다.

단점

  • 동적 웹페이지는 북마크하기가 더 어렵습니다.
  • 브라우저에서 JavaScript가 비활성화된 경우 동작하지 않습니다.
  • 일부 웹 크롤러는 JavaScript를 실행하지 않으며 JavaScript에 의해 로드된 콘텐츠를 볼 수 없습니다.
  • SPA의 대부분의 단점이 여기에서 나옵니다.
  • DOM을 업데이트하기 위해 가져온 원격 데이터를 클라이언트측 템플릿과 결합해야 할 가능성이 높습니다.
    • 이를 위해서는 JavaScript가 브라우저에서 구문 분석되고 실행되어야 합니다.
    • 저사양 모바일 장치에서는 성능 문제가 발생할 수 있습니다.

20 . JSONP에 대해 설명해 주세요

JSONP(JSON with Padding)는 현재 페이지에서 교차 출처 도메인으로의 Ajax 요청이 허용되지 않기 때문에
웹 브라우저에서 교차 도메인 정책을 우회하기 위해 일반적으로 사용되는 방법입니다.

 

JSONP는 일반적으로 <script>태그를 사용해 교차 출처 요청합니다.

서버에 콜백 쿼리 매개변수(예: https://example.com?callback=printData)를 사용합니다. 

서버는 printData라는 함수 내에서 데이터를 래핑하고 클라이언트에 반환합니다.

<!-- https://mydomain.com -->
<script>
  function printData(data) {
    console.log(`My name is ${data.name}!`);
  }
</script>

<script src="https://example.com?callback=printData"></script>

서버는 qs로 전달된 전역 범위에 존재하는 함수를 호출합니다.

// File loaded from https://example.com?callback=printData
printData({name: 'Yang Shun'});

JSONP는 보안 관련 문제가 있습니다.
JSONP는 실제로 JavaScript이므로 JavaScript가 수행할 수 있는 다른 모든 작업을 수행할 수 있으므로 JSONP 데이터 공급자를 신뢰해야 합니다.

요즘에는 CORS가 권장되는 접근 방식이며 JSONP는 해킹으로 간주됩니다.

References


21. JS 탬플릿 엔진에 대해 설명해 주세요

handlebar,underscore, Lodash, AngularJS 및 JSX 등이 있습니다.
저는 AngularJS에서 템플릿을 사용하는 것을 싫어했습니다. 지시문에서 문자열을 많이 사용하고 오타가 잡히지 않기 때문입니다.
 
JSX는 JavaScript에 더 가깝고 배울 구문이 거의 없기 때문에 제가 가장 좋아하는 것입니다.
타사 코드에 의존하지 않고 템플릿을 만드는 빠른 방법으로 ES2015 템플릿 문자열 리터럴을 사용할 수도 있습니다.
const template = `<div>My name is: ${name}</div>`;
그러나 템플릿 라이브러리와 달리 컨텐츠가 이스케이프되지 않으므로 잠재적인 XSS 이슈를 알고 있어야 합니다.

22. hoisting에 대해 설명해 주세요

호이스팅은 변수 선언의 동작을 설명하는 데 사용되는 용어입니다.
var 키워드로 선언되거나 초기화된 변수는 해당 선언이 모듈/함수 수준 스코프의 맨 위로 "이동"되며 이를 호이스팅이라고 합니다.
그러나 선언만 호이스팅되고 할당(있는 경우)은 그대로 유지됩니다.
 
선언은 코드상에서 실제로 이동하는 것이 아닙니다.
JavaScript 엔진은 컴파일 중에 선언을 구문 분석하고 선언의 존재와 해당 선언의 유효 스코프를 인지합니다.
선언을 해당 스코프위의 맨 위로 끌어올리는 것으로 시각화하면 이 동작을 이해하기가 더 쉽습니다.
console.log(foo); // undefined
var foo = 1;
console.log(foo); // 1​

함수 선언문은 함수 본문(body)가 호이스팅 됩니다.
함수 표현식(변수 선언 형식으로 작성됨)은 변수 선언만 호이스팅 됩니다.

// Function Declaration
console.log(foo); // [Function: foo]
foo(); // 'FOOOOO'
function foo() {
  console.log('FOOOOO');
}
console.log(foo); // [Function: foo]

// Function Expression
console.log(bar); // undefined
bar(); // Uncaught TypeError: bar is not a function
var bar = function () {
  console.log('BARRRR');
};
console.log(bar); // [Function: bar]
let 및 const를 통해 선언된 변수도 호이스팅됩니다.
그러나 var 및 function과 달리 초기화되지 않으며 선언 전에 액세스하면 ReferenceError 예외가 발생합니다.
변수는 블록 시작부터 선언이 처리될 때까지 "temporal dead zone"에 있습니다.
(undeclared variables 참조)
x; // undefined
y; // Reference error: y is not defined

var x = 'local';
let y = 'local';
 
References

23. 이벤트 버블링에 대해 설명해 주세요

이벤트 버블링은 이벤트 위임 메커니즘입니다.

이벤트가 DOM 요소에서 트리거될 때 연결된 리스너가 있는 경우 해당 리스너는 해당 이벤트를 처리하려 시도합니다.

이후 이벤트가 부모에게 버블링되고 해당 부모에게서 동일한 일이 발생합니다.

이 버블링은 document에 이르기까지 요소의 조상에서 순서대로 발생합니다.

 


24. attribute와 property의 차이는 무엇인가요?

attributesms HTML 마크업에서 정의되지만 property은 DOM에서 정의됩니다.
HTML에 <input type="text" value="Hello"> 텍스트 필드가 있다고 가정합니다.

getAttribute는 마크업에 존재하는 바로 그 값을 리턴합니다.

const input = document.querySelector('input');
console.log(input.getAttribute('value')); // Hello
console.log(input.value); // Hello
"World!"를 추가하여 텍스트 필드의 값을 변경해도 attribute는 변하지 않습니다.
console.log(input.getAttribute('value')); // Hello
console.log(input.value); // Hello World!

References


25. JS의 빌트인 객체를 확장하는 것이 왜 좋지 않나요?

내장/네이티브 JavaScript 객체를 확장한다는 것은 프로토타입에 속성/함수를 추가하는 것을 의미합니다.
이것은 처음에는 좋은 생각처럼 보일 수 있지만 실제로는 위험합니다.
동일한 contains 메서드를 추가하여 Array.prototype을 확장하는 몇 가지 라이브러리를 코드에서 같이 사용하면 어떻게 될까요?
구현은 서로 덮어쓰게 되고, 이 두 메서드의 동작이 동일하지 않으면 코드가 중단됩니다.
 
네이티브 객체를 확장하면 좋은 유일한 경우는 폴리필을 사용하는 경우입니다.
폴리필 ;
JavaScript 사양의 일부이지만  이전 브라우저이기 때문에 사용자의 브라우저에 존재하지 않을 수 있는 메서드에 대한
유일한 구현을 제공합니다.
 

26.  document의 load 이벤트와 DOMContentLoaded 이벤트의 차이점은 뭘까요?

DOMContentLoaded 이벤트는 스타일시트, 이미지 및 서브 프레임의 로딩 완료를 기다리지 않습니다.
초기 HTML 문서가 완전히 로드되고 구문 분석되면 시작됩니다.

load  이벤트는 DOM과 모든 종속 리소스 및 에셋이 로드된 후에만 발생합니다.
(리소스 : 동적파일, 에셋 : 정적파일)

27.  === 과 == 의 차이는 뭔가요?

==는 추상 항등 연산자이고 ===는 완전 항등 연산자입니다.
== 연산자는 필요한 타입 변환을 수행한 후 동일한지 비교합니다.
=== 연산자는 타입 변환을 수행하지 않으므로 두 값이 동일한 타입이 아닌 경우 === 는 단순히 false를 반환합니다.
 
1 == '1'; // true
1 == [1]; // true
1 == true; // true
0 == ''; // true
0 == '0'; // true
0 == false; // true​

== 연산자를 사용하지 않는 것이 좋습니다.
단, null 또는 undefined와 비교할 때 a == null은 a가 null이거나 정의되지 않은 경우 true를 반환합니다.

이 경우는 == 도 괜찮습니다.

var a = null;
console.log(a == null); // true
console.log(a == undefined); // true

 

References


28. 동일 출처 정책(same-origin policy)와 JS의 관계를 설명해 주세요

동일 출처 정책은 JavaScript가 도메인 경계를 넘어 요청하는 것을 방지합니다.
출처(origin)은 URI scheme, 호스트 명 및 포트 번호의 조합으로 정의됩니다.
 
이 정책은 악성 스크립트가 해당 페이지의 문서 객체 모델(DOM)을 통해 중요한 데이터에 액세스하지 못하도록 합니다.

29. 배열 복사 구현

duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]
 
function duplicate(arr) {
  return arr.concat(arr);
}
// Or with ES6:

const duplicate = (arr) => [...arr, ...arr];

30. 삼항 연산자의 삼항(Ternary)는 무슨 의미일까요?

"Ternary"는 3을 나타내며 3항 식은 세 개의 피연산자(테스트 조건, "then" 식 및 "else" 식)를 허용합니다.
삼항 표현식은 JavaScript에만 국한되지 않습니다.

 

References


31. "use strict"의 장단점에 대해 설명해 주세요

'use strict'는 전체 스크립트 또는 개별 함수에 대해 엄격 모드를 활성화하는 데 사용되는 문입니다.
Strict 모드는 JavaScript의 제한된 변형을 선택하는 방법입니다.

장점

  • 실수로 전역 변수를 생성하는 것을 불가능하게 합니다.
  • 조용히 실패하는 할당을 오류가 발생하도록 합니다.
  • 삭제할 수 없는 속성을 삭제하려고 시도하면 예외가 발생합니다. (이전에는 시도가 효과가 없었음).
  • 함수 매개변수 이름이 고유해야 합니다.
  • 전역 컨텍스트의 this는 undefined 입니다.
  • 몇 가지 일반적인 코딩 오류를 포착하여 예외를 발생시킵니다.
  • 혼란스럽고 오해하기 쉬운 기능들을 비활성화 합니다.

단점

  • 일부 개발자들이 자연스럽게 사용하는 기능을 못쓰게 됩니다.
  • 더 이상 function.caller 및 function.arguments에 액세스할 수 없습니다.
  • 비엄격 모드로 작성된 스크립트를 연결하면 문제가 발생할 수 있습니다.

전반적으로 단점보다 장점이 더 많다고 생각하며 엄격 모드가 차단하는 기능에 의존할 필요가 없습니다.
엄격 모드를 사용하는 것이 좋습니다.

 

References


32. fizzbuzz 구현

3의 배수에서 "fizz", 5의 배수에서 "buzz", 3과 5의 배수에서 "fizzbuzz"를 출력하면서 최대 100까지 반복되는 for 루프를 만듭니다.
for (let i = 1; i <= 100; i++) {
  let f = i % 3 == 0,
    b = i % 5 == 0;
  console.log(f ? (b ? 'FizzBuzz' : 'Fizz') : b ? 'Buzz' : i);
}
인터뷰 중에 위의 내용을 작성하는 것은 권장하지 않습니다. 길지만 명확한 접근 방식을 고수하십시오.

33. 글로벌 스코프를 사용하면 좋지 않는 이유를 설명해 주세요

모든 스크립트가 전역 범위에 액세스할 수 있고, 모든 사람이 전역 네임스페이스를 사용하여 변수를 정의하면 충돌이 발생할 수 있습니다.
모듈 패턴(IIFE)을 사용하여 로컬 네임스페이스 내에서 변수를 캡슐화합니다.


34. load Event와 대안에 대해 설명해 주세요.

load 이벤트는 문서 로드 프로세스가 끝날 때 발생합니다.
이 시점에서 문서의 모든 객체는 DOM에 있고 모든 이미지, 스크립트, 링크 및 하위 프레임의 로드가 완료된 상태입니다.

DOM 이벤트 DOMContentLoaded는 페이지의 DOM이 생성된 후 실행되지만 다른 리소스가 로드를 완료할 때까지 기다리지 않습니다. 이것은 초기화하기 전에 전체 페이지를 로드할 필요가 없는 경우에 선호됩니다.

References


35. SPA 앱에 대해 설명해 주세요

요새 프론트엔트 개발자의 산출물을 웹 앱이라고 합니다.
웹 사이트와 웹 앱 두 용어 사이에 엄밀한 차이는 없지만
웹 앱은 대화형이고 동적인 경향이 있어,
사용자가 작업을 수행하고 해당 작업에 대한 응답을 받을 수 있습니다.
 
전통적으로 브라우저는 서버에서 HTML을 받아 렌더링합니다.
사용자가 다른 URL로 이동하면 전체 페이지 새로 고침이 필요하고 서버는 새로운 HTML을 새 페이지로 보냅니다.
이를 서버 측 렌더링이라고 합니다.
최신 SPA에서는 클라이언트 측 렌더링이 대신 사용됩니다.
브라우저는 전체 앱에 필요한 스크립트(프레임워크, 라이브러리, 앱 코드) 및 스타일시트와 함께 서버에서 초기 페이지를 로드합니다.
사용자가 다른 페이지로 이동할 때 페이지 새로 고침이 트리거되지 않습니다.
페이지의 URL은 HTML5 History API를 통해 업데이트됩니다.
일반적으로 JSON 포맷의 새 페이지에 필요한 새 데이터는 서버로의 AJAX 요청을 통해 검색합니다.
그런 다음 SPA는 초기 페이지 로드 시 이미 다운로드한 JavaScript를 통해 데이터로 페이지를 동적으로 업데이트합니다.
이 모델은 네이티브 모바일 앱이 작동하는 방식과 유사합니다.

SPA의 장점

  • 앱의 반응 속도가 빨라지고 전체 페이지 새로 고침으로 인한 플래시가 없어집니다.
  • 페이지 로드 시 마다 동일한 자산을 다시 다운로드할 필요가 없으므로 서버에 대한 HTTP 요청이 줄어듭니다.
  • 클라이언트와 서버 간의 문제를 명확하게 분리합니다.
    • 서버 코드를 수정하지 않고도 다양한 플랫폼(예: 모바일, 챗봇, 스마트 워치)을 위한 새 클라이언트를 쉽게 구축할 수 있습니다.
    • API 계약이 깨지지 않는 한 클라이언트와 서버의 기술 스택을 독립적으로 수정할 수도 있습니다.

SPA의 단점

  • 여러 페이지에 필요한 프레임워크, 앱 코드 및 에셋의 로드로 인해 초기 페이지 로드가 더 많습니다.
  • 서버에서 모든 요청을 단일 진입점으로 라우팅하고 거기에서 클라이언트 측 라우팅을 허용하도록 설정이 필요합니다. (rewrite)
  • SPA는 콘텐츠를 렌더링하기 위해 JavaScript에 의존하지만 모든 검색 엔진이 크롤링 중에 JavaScript를 실행하는 것은 아니며 페이지에 빈 콘텐츠가 표시될 수 있습니다.
    • 이로 인해 앱의 검색 엔진 최적화(SEO)가 손상됩니다.
    • 그러나 대부분의 경우 앱을 구축할 때 모든 콘텐츠가 검색 엔진에 의해 인덱싱될 필요가 없기 때문에 SEO가 가장 중요한 요소는 아닙니다.
    • 이를 극복하기 위해 서버 측에서 앱을 렌더링합니다.
    • Prerender와 같은 서비스를 사용하여 "브라우저에서 자바스크립트를 렌더링하고 정적 HTML을 저장한 다음 크롤러에 반환"할 수 있습니다.

References


36. Promise 및/또는 polyfill에 대한 경험이 있나요?

Promise는 미래에 단일 값을 생성할 수 있는 개체입니다.
Promise는 3가지 가능한 상태(resolved, rejected 또는 peiding 중) 중 하나일 수 있습니다.
  • 이행(resolved)된 값
  • 이행되지 않은;거부된(rejected) 이유(예: 네트워크 오류 발생)입니다.
  • 아직 처리되지 않은;보류 중인 약속(Promise)
Promise 사용자는 이행된 값 또는 거부 이유를 처리하기 위해 콜백을 사용할 수 있습니다.
 
일부 일반적인 폴리필은 $.deferred, Q 및 Bluebird이지만 모두 사양을 준수하지는 않습니다.
ES2015는 즉시 사용할 수 있는 Promise를 지원하며 요즘에는 일반적으로 Promise를 위한 polyfill이 필요하지 않습니다.
 

37. Promise와 callback의 장단점을 비교해 주세요

Promise의 장점

  • 가독성을 낮추는 콜백 지옥을 피할 수 있습니다.
  • .then()으로 순차적 비동기 코드를 쉽고 가독성 높게 작성할 수 있습니다.
  • Promise.all()을 사용하여 병렬 비동기 코드를 쉽게 작성할 수 있습니다.
  • Promise를 사용하면 다음 시나리오가 발생하지 않습니다.
    • 콜백을 너무 일찍 호출
    • 콜백을 너무 늦게 호출하거나 호출하지 않음
    • 콜백을 너무 적게 또는 너무 많이 호출합니다.
    • 필요한 환경/매개변수 전달 실패
    • 발생할 수 있는 모든 오류/예외 무시
      • 오류 / 예외가 발생하지 않은 것처럼, 코드 실행을 무효화하고 넘어가기

Promise의 단점

  • 약간 더 복잡한 코드(논쟁의 여지가 있음).
  • ES2015가 지원되지 않는 이전 브라우저에서 사용하려면 polyfill을 로드해야 합니다.

References


38. JavaScript로 컴파일되는 언어로 JavaScript 코드를 작성하는 것의 장점/단점은 무엇입니까?

JavaScript로 컴파일되는 언어의 예로는 CoffeeScript, Elm, ClojureScript, PureScript 및 TypeScript가 있습니다.

장점

  • JavaScript의 오랜 문제 중 일부를 수정하고 JavaScript 안티 패턴을 권장하지 않습니다.
  • 구문 설탕을 제공하여 더 짧은 코드를 작성할 수 있습니다.
    • ES5는 구문 설탕이 부족하다고 생각하지만 ES2015는 이미 훌륭합니다.
  • 정적 타입은 오래 유지 보수해야 하는 대규모 프로젝트에 적합합니다(TypeScript의 경우).
 

단점

  • 브라우저는 JavaScript만 실행하므로 빌드/컴파일 프로세스가 필요하며 브라우저에 제공하기 전에 코드를 JavaScript로 컴파일해야 합니다.
  • 소스 맵이 미리 컴파일된 소스에 제대로 매핑되지 않으면 디버깅이 어려울 수 있습니다.
  • 커뮤니티가 소규모인 기술은 리소스, 자습서, 라이브러리 및 도구를 찾기 어렵습니다.
  • IDE/에디터 지원이 부족할 수 있습니다.
  • 이러한 언어는 항상 최신 JavaScript 표준에 뒤쳐지곤 합니다.
  • 개발자는 자신의 코드가 어떻게 컴파일 되는지 알아야 합니다.

ES2015는 JavaScript를 크게 개선하고 작성하기 훨씬 더 좋게 만들었습니다.
저는 요즘 CoffeeScript의 필요성을 별로 느끼지 못합니다.

 

References


39. JavaScript를 디버강 하는 도구 및 기술을 설명해 주세요

References


40. JS에서 객체 속성과 배열 항목을 어떻게 순회하나요?

객체 :

  • for-in loop - for (var property in obj) { console.log(property); }
    • 그러나 이 방법은 상속된 속성도 순회합니다.
      • 이를 막으러면 obj.hasOwnProperty(property) 검사를 추가합니다.
  • Object.keys() - Object.keys(obj).forEach(function (property) { ... })
    • Object.keys()는 객체의 모든 열거 가능한 속성을 나열하는 정적 메서드입니다.
  • Object.getOwnPropertyNames() - Object.getOwnPropertyNames(obj).forEach(function (property) { ... })
    • Object.getOwnPropertyNames()는 객체의 모든 열거 가능 속성과 열거 불가능 속성을 나열하는 정적 메서드입니다.

배열 :

  • for loop - for (var i = 0; i < arr.length; i++)
    • 일반적인 함정은 var가 블록 범위가 아닌 함수 범위에 있고 대부분의 경우 블록 범위 반복자 변수를 원한다는 것입니다.
    • ES2015는 블록 스코프가 있는 let을 도입했으며 이를 대신 사용할 것을 권장합니다.
      • 따라서 이것은 for (let i = 0; i < arr.length; i++)가 됩니다.
  • forEach - arr.forEach(function (el, index) { ... })
    • 항목만 필요한 경우 인덱스를 사용할 필요가 없기 때문에 이 구성은 때때로 더 편리할 수 있습니다.
    • 반복을 조기에 종료할 수 있는 everysome 메서드도 있습니다.
  • for-of loops - for (let elem of arr) { ... }
    •  ES6는 String, Array, Map, Set 등과 같은 iterable protocol을 준수하는 객체를 반복할 수 있는 새로운 루프인 for-of 루프를 도입했습니다.
    • for loop와 forEach()의 장점을 결합합니다.
      • for loop의 장점은 중단할 수 있다는 것입니다.
      • forEach()의 장점은 카운터 변수가 필요하지 않기 때문에 for 루프보다 간결하다는 것입니다.
    • for-of 루프를 사용하면 루프에서 중단하는 기능과 보다 간결한 구문을 모두 얻을 수 있습니다.
대부분의 경우 .forEach 메서드를 선호합니다.
ES6 이전에는 break를 사용하여 루프를 조기에 종료해야 할 때 for 루프를 사용했습니다만,
이제 ES6에서는 for-of 루프를 사용하여 이를 수행할 수 있습니다.
인덱스를 더 많이 증가시키는 것과 같은, 훨씬 더 많은 유연성이 필요할 때 for 루프를 사용합니다.

또한 for-of 루프를 사용할 때 각 배열 요소의 인덱스와 값에 모두 액세스해야 하는 경우,
ES6 Array entries() 메서드와 구조 분해를 사용할 수 있습니다.

const arr = ['a', 'b', 'c'];

for (let [index, elem] of arr.entries()) {
  console.log(index, ': ', elem);
}

References


41. 불변 객체와 가변 객체에 대해 설명해 주세요.

불변성은 함수형 프로그래밍의 핵심 원칙이며 객체 지향 프로그램에도 많은 것을 제공합니다.
  • 가변 객체는 생성 후 상태를 수정할 수 있는 객체입니다.
  • 불변 객체는 생성된 후에 상태를 수정할 수 없는 객체입니다.

JS의 불변 객체의 예시는 뭔가요?

JavaScript에서 일부 내장 타입(number,string)은 변경할 수 없지만 커스텀 객체는 일반적으로 변경할 수 있습니다.
일부 빌트인 불변 JavaScript 객체는 Math, Date입니다.
다음은 일반 JavaScript 객체에 불변성을 추가/시뮬레이트하는 몇 가지 방법입니다.

객체 상수 속성 (Object Constant Properties)

writable: false 및 configurable: false를 결합하면
다음과 같이 객체 속성으로 상수(변경, 재정의 또는 삭제할 수 없음)를 만들 수 있습니다.

let myObject = {};
Object.defineProperty(myObject, 'number', {
  value: 42,
  writable: false,
  configurable: false,
});
console.log(myObject.number); // 42
myObject.number = 43;
console.log(myObject.number); // 42

확장 방지 (Prevent Extension)

객체에 새 속성이 추가되지 않도록 하고 나머지 객체 속성은 그대로 두려면 Object.preventExtensions(...)를 호출합니다.

var myObject = {
  a: 2,
};

Object.preventExtensions(myObject);

myObject.b = 3;
myObject.b; // undefined

비엄격 모드에서는 b 속성 생성이 조용히(silently) 실패합니다.
엄격 모드에서는 TypeError가 발생합니다.

봉인 (Seal)

Object.seal()은 "봉인된" 객체를 만듭니다.
객체를 대상으로 Object.preventExtensions()를 호출한 것과 유사하지만,
configurable을 false로 만듭니다.
  • 값의 수정은 가능합니다. (writable : true 면)
  • 다른 디스크립터의 속성은 수정 불가능 합니다.
  • 해당 값을 객체에서 삭제할 수 없습니다.
  • 해당 값의 타입을 데이터 속성과 접근자 속성에서 변경할 수 없습니다.

Freeze

Object.freeze()는 고정된 객체를 만듭니다.
객체를 대상으로 Object.seal()을 호출하지만 모든 "데이터 접근자" 속성을 writable : false로 만든 것과 유사합니다.
해당 값을 변경할 수 없도록 합니다.

객체 또는 객체의 직접적인 속성에 대한 변경을 방지하므로

객체 자체에 대해 달성할 수 있는 가장 높은 수준의 불변성입니다.

(참조 대상 객체는 해당 영향을 받지 않음)

var immutable = Object.freeze({});
객체를 고정하면 객체에 새 속성을 추가할 수 없으며 기존 속성을 제거하거나 변경하지 못합니다.
Object.freeze()는 개체의 열거 가능성, 구성 가능성, 쓰기 가능성 및 프로토타입을 보존합니다.
파라미터로 전달된 개체를 반환하고 복사본을 만들지 않습니다.
 

불변성의 장점과 단점

장점

  • 더 쉬운 변경 감지
    • 객체 동등성은 참조 동등성을 통해 쉽게 확인할 수 있습니다.
    • 이는 React와 Redux의 객체 차이점을 비교하는 데 유용합니다.
  • 시간에 따른 객체 변경을 생각할 필요가 없습니다.
    • 겍체 변경을 누적할 필요 없음
  • 방어적 복사본이 필요없습니다
    • 기존 객체가 수정될 가능성이 없기 떄문입니다.
  • 참조를 통한 손쉬운 공유
    • 변경되지 않으면 항상 동일한 참조임이 보장되기에 쉽게 객체를 여러번 재사용할 수 있습니다.
  • thread safe
    • 불변 개체는 동시에 실행 중인 다른 스레드에서 수정될 위험이 없기 때문에 다중 스레드 환경에서 안전하게 사용할 수 있습니다.
  • ImmutableJS와 같은 라이브러리를 사용하면 구조적 공유를 사용해 객체가 수정되어 유사한 구조를 가진 여러 객체를 보유하는 데 필요한 메모리가 더 적습니다.

단점

  • 변경할 수 없는 데이터 구조와 해당 작업을 순진하게 구현하면 매번 새 객체가 생성되기 때문에 성능이 극도로 저하될 수 있습니다. 구조적 공유를 활용하는 효율적인 불변 데이터 구조 및 작업을 위해 라이브러리를 사용하는 것이 좋습니다.
  • 그래프와 같은 순환 데이터 구조의 구현이 어렵습니다.
    • 초기화 후에 수정할 수 없는 두 개의 객체가 서로를 가리키도록 하려면 어떻게 해야 하나요?

References


42. 불변성을 구현하는 방법을 설명해 주세요

  • immutable.jsmori or immer.와 같은 라이브러리를 사용하는 것입니다.
  • 객체를 "변경"하는 경우 확산 연산자 Object.assign, Array.concat() 등을 사용하여 원래 객체를 변경하는 대신 새 객체를 만듭니다.
// Array Example
const arr = [1, 2, 3];
const newArr = [...arr, 4]; // [1, 2, 3, 4]

// Object Example
const human = Object.freeze({race: 'human'});
const john = {...human, name: 'John'}; // {race: "human", name: "John"}
const alienJohn = {...john, race: 'alien'}; // {race: "alien", name: "John"}

43. 동기 함수와 비동기 함수의 차이점을 설명하십시오.

동기 함수에서 명령문은 다음 명령문이 실행되기 전에 완료됩니다.
이 경우 프로그램은 명령문 순서대로 정확하게 평가되며 명령문 중 하나가 매우 오래 걸리면 프로그램 실행이 일시 중지됩니다.
 
비동기 함수는 일반적으로 콜백을 매개변수로 받아, 비동기 함수가 호출된 직후 다음 줄 실행이 계속됩니다.
콜백은 비동기 작업이 완료되고 콜 스택이 비어 있을 때만 호출됩니다.
웹 서버에서 데이터를 로드하거나 데이터베이스를 쿼리하는 것과 같은 무거운 작업은
메인 스레드가 긴 작업이 완료될 때까지 차단하지 않고 다른 작업을 계속 실행할 수 있도록 비동기적으로 수행되어야 합니다.
(브라우저의 경우 UI가 정지됨). .

44. 이벤트 루프가 뭔가요? 콜스택과 태스크 큐는 어떤 차이가 있죠?

이벤트 루프는 콜 스택을 모니터링하고 태스크 큐에서 수행할 작업이 있는지 확인하는 단일 스레드 루프입니다.
콜 스택이 비어 있고 태스크 큐에 콜백 함수가 있는 경우 함수가 큐에서 제거되고 콜 스택으로 푸시되어 실행됩니다.
이벤트 루프에 대한 Philip Robert의 강연을 아직 확인하지 않았다면 꼭 보세요(talk on the Event Loop)

References


45. function foo() {}와 var foo = function() {} 의 foo 사용의 차이점을 설명하세요.

전자는 함수 선언(문)이고 후자는 함수 표현식입니다. 
주요 차이점은 다음과 같습니다.

  • 함수 선언문은 본문이 호이스팅됩니다.
  • 함수 표현식의 본문은 호이스팅 되지 않습니다.
    • (변수와 동일한 호이스팅 동작이 있음).
함수 표현식이 정의(초기화)되기 전에 호출하려고 하면 Uncaught TypeError: XXX is not a function 오류가 발생합니다.

Function Declaration

foo(); // 'FOOOOO'
function foo() {
  console.log('FOOOOO');
}

Function Expression

foo(); // Uncaught TypeError: foo is not a function
var foo = function () {
  console.log('FOOOOO');
};
References

46. 변수 정의 시 var, let, const의 차이점을 설명해 주세요

var 키워드를 사용하여 선언된 변수는 변수가 생성된 함수의 스코프에 속하거나
함수 외부에서 생성된 경우 전역 객체로 스코프가 지정됩니다.
let 및 const는 블록 범위이므로 가장 가까운 중괄호 집합(함수, if-else 블록 또는 for 루프) 내에서만 액세스할 수 있습니다.
function foo() {
  // All variables are accessible within functions.
  var bar = 'bar';
  let baz = 'baz';
  const qux = 'qux';

  console.log(bar); // bar
  console.log(baz); // baz
  console.log(qux); // qux
}

console.log(bar); // ReferenceError: bar is not defined
console.log(baz); // ReferenceError: baz is not defined
console.log(qux); // ReferenceError: qux is not defined​
if (true) {
  var bar = 'bar';
  let baz = 'baz';
  const qux = 'qux';
}

// var declared variables are accessible anywhere in the function scope.
console.log(bar); // bar
// let and const defined variables are not accessible outside of the block they were defined in.
console.log(baz); // ReferenceError: baz is not defined
console.log(qux); // ReferenceError: qux is not defined

var를 사용하면 변수를 호이스팅할 수 있습니다.

즉, 변수를 선언하기 전에 코드에서 참조할 수 있습니다.

 

let 및 const는 이를 허용하지 않고 대신 오류를 발생시킵니다.

console.log(foo); // undefined

var foo = 'foo';

console.log(baz); // ReferenceError: can't access lexical declaration 'baz' before initialization

let baz = 'baz';

console.log(bar); // ReferenceError: can't access lexical declaration 'bar' before initialization

const bar = 'bar';
var로 변수를 재선언하면 오류가 발생하지 않지만 let 및 const는 오류가 발생합니다.
var foo = 'foo';
var foo = 'bar';
console.log(foo); // "bar"

let baz = 'baz';
let baz = 'qux'; // Uncaught SyntaxError: Identifier 'baz' has already been declared
let은 변수의 값을 재할당할 수 있지만 const는 허용하지 않는다는 점에서 다릅니다.
// This is fine.
let foo = 'foo';
foo = 'bar';

// This causes an exception.
const baz = 'baz';
baz = 'qux';

References


47. es6 클래스와 es6 생성자 함수의 차이점은 뭔가요?

먼저 각각의 예를 살펴보겠습니다.
// ES5 Function Constructor
function Person(name) {
  this.name = name;
}

// ES6 Class
class Person {
  constructor(name) {
    this.name = name;
  }
}
간단한 생성자의 경우 매우 비슷해 보입니다.

생성자의 주요 차이점은 상속을 사용할 때 발생합니다.
Person의 하위 클래스인 Student 클래스를 만들고 studentId 필드를 추가하기 위한 작업입니다.

// ES5 Function Constructor
function Student(name, studentId) {
  // Call constructor of superclass to initialize superclass-derived members.
  Person.call(this, name);

  // Initialize subclass's own members.
  this.studentId = studentId;
}

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

// ES6 Class
class Student extends Person {
  constructor(name, studentId) {
    super(name);
    this.studentId = studentId;
  }
}
ES5에서 상속을 사용하는 것이 훨씬 더 장황하며 ES6 버전이 이해하고 기억하기 더 쉽습니다.

References


48. 화살표 함수의 이점과 사용 사례에 대해 설명해 주세요.

화살표 함수의 분명한 이점 중 하나는 function 키워드가 필요 없어 함수를 만드는 데 필요한 구문을 단순화한다는 것입니다.
this 내 화살표 함수는 또한 둘러싸는 스코프에 바인딩되며(enclosing scope)
this가 해당 함수를 호출하는 객체에 의해 결정되는 일반 함수와 다릅니다.
렉시컬 스코프를 따르는 this는 특히 React 컴포넌트에서 콜백을 호출할 때 유용합니다.

49. 메서드의 생성자에 화살표 함수를 사용하면 어떤 이점이 있나요?

화살표 함수를 생성자 내부의 메서드로 사용하는 주요 이점은 this의 값이 함수 생성 시 설정되고 이후에는 변경할 수 없다는 것입니다.
따라서 생성자가 새 객체를 만드는 데 사용되면 this는 항상 해당 객체를 참조합니다.
const Person = function (firstName) {
  this.firstName = firstName;
  this.sayName1 = function () {
    console.log(this.firstName);
  };
  this.sayName2 = () => {
    console.log(this.firstName);
  };
};

const john = new Person('John');
const dave = new Person('Dave');

john.sayName1(); // John
john.sayName2(); // John

// The regular function can have its 'this' value changed, but the arrow function cannot
john.sayName1.call(dave); // Dave (because "this" is now the dave object)
john.sayName2.call(dave); // John

john.sayName1.apply(dave); // Dave (because 'this' is now the dave object)
john.sayName2.apply(dave); // John

john.sayName1.bind(dave)(); // Dave (because 'this' is now the dave object)
john.sayName2.bind(dave)(); // John

var sayNameFromWindow1 = john.sayName1;
sayNameFromWindow1(); // undefined (because 'this' is now the window object)

var sayNameFromWindow2 = john.sayName2;
sayNameFromWindow2(); // John
일반 함수는 this를 변경할 수 있지만 화살표 함수에 대한 컨텍스트는 항상 동일하게 유지된다는 것입니다.
따라서 애플리케이션의 다른 부분에 화살표 함수를 전달하더라도 컨텍스트 변경에 대해 걱정할 필요가 없습니다.
React 클래스 컴포넌트에서 특히 유용할 수 있습니다.
일반 함수를 사용하여 클릭 핸들러와 같은 항목에 대한 클래스 메서드를 정의한 다음
해당 클릭 핸들러를 자식 컴포넌트에 prop으로 전달하는 경우,
this를 부모 컴포넌트의 생성자에 바인딩해야 합니다.
 
대신 화살표 함수를 사용하는 경우 "this"를 바인딩할 필요가 없습니다.
메서드가 둘러싸는 렉시컬 컨텍스트에서 "this" 값을 자동으로 가져오기 때문입니다.

50. 고차 함수가 뭔가요?

고차 함수는 하나 이상의 함수를 인수로 일부 데이터에서 동작하는 데 사용하는 함수이거나,
리턴값으로 함수를 반환하는 함수입니다.
 
고차 함수는 반복적으로 수행되는 일부 작업을 추상화하기 위한 도구입니다.
이에 대한 고전적인 예는 배열과 함수를 인수로 취하는 맵입니다.
map은 이 함수를 사용하여 배열의 각 항목을 변환하고 변환된 데이터가 포함된 새 배열을 반환합니다.
JavaScript의 다른 인기 있는 예는 forEach, filter 및 reduce입니다.
다른 함수에서 함수를 반환하는 많은 사용 사례가 있으므로 고차 함수는 배열을 직접 조작할 필요가 없습니다.
Function.prototype.bind는 그러한 예 중 하나입니다.

map

각 문자열을 대문자로 변환해야 하는 이름 배열이 있다고 가정해 보겠습니다.
const names = ['irish', 'daisy', 'anna'];

명령형 방법은 다음과 같습니다.

const transformNamesToUppercase = function (names) {
  const results = [];
  for (let i = 0; i < names.length; i++) {
    results.push(names[i].toUpperCase());
  }
  return results;
};
transformNamesToUppercase(names); // ['IRISH', 'DAISY', 'ANNA']
.map(transformerFn) 을 사용하면 코드가 더 짧고 선언적입니다.
const transformNamesToUppercase = function (names) {
  return names.map((name) => name.toUpperCase());
};
transformNamesToUppercase(names); // ['IRISH', 'DAISY', 'ANNA']

 

References


51. 객체나 배열을 분해하는 예를 들어주세요

Destructuring(구조분해)는 ES6에서 사용할 수 있는 표현식으로
객체 또는 배열의 값을 추출하여 개별 변수에 배치하는 간결하고 편리한 방법을 제공합니다.

배열 구조분해

// Variable assignment.
const foo = ['one', 'two', 'three'];

const [one, two, three] = foo;
console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"
// Swapping variables
let a = 1;
let b = 3;

[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1

객체 구조분해

// Variable assignment.
const o = {p: 42, q: true};
const {p, q} = o;

console.log(p); // 42
console.log(q); // true

References


52. ES6 템플릿 리터럴은 문자열 생성에 많은 유연성을 제공합니다.  예를 들어주실 수 있나요?

템플릿 리터럴은 문자열 보간을 간단하게 수행하거나 문자열에 변수를 포함하는 데 도움이 됩니다.
ES2015 이전에는 다음과 같은 작업을 수행하는 것이 일반적이었습니다.

var person = {name: 'Tyler', age: 28};
console.log(
  'Hi, my name is ' + person.name + ' and I am ' + person.age + ' years old!',
);
// 'Hi, my name is Tyler and I am 28 years old!'
템플릿 리터럴을 사용하면 이제 다음과 같이 동일한 출력을 생성할 수 있습니다.
const person = {name: 'Tyler', age: 28};
console.log(`Hi, my name is ${person.name} and I am ${person.age} years old!`);
// 'Hi, my name is Tyler and I am 28 years old!'
따옴표가 아닌 백틱을 사용하여 템플릿 리터럴을 사용하고 있으며 ${} 자리 표시자 안에 식을 삽입할 수 있음을 나타냅니다.
두 번째 유용한 사용 사례는 여러 줄 문자열을 만드는 것입니다. ES2015 이전에는 다음과 같이 여러 줄 문자열을 만들 수 있었습니다.
console.log('This is line one.\nThis is line two.');
// This is line one.
// This is line two.
또는 긴 문자열을 읽기 위해 텍스트 편집기에서 오른쪽으로 스크롤할 필요가 없도록 코드에서 여러 줄로 나누고 싶다면 다음과 같이 작성할 수도 있습니다.
console.log('This is line one.\n' + 'This is line two.');
// This is line one.
// This is line two.
그러나 템플릿 리터럴은 추가한 간격을 그대로 유지합니다. 예를 들어 위에서 만든 것과 동일한 여러 줄 출력을 만들려면 다음과 같이 하면 됩니다.
console.log(`This is line one.
This is line two.`);
// This is line one.
// This is line two.
템플릿 리터럴의 또 다른 사용 사례는 간단한 변수 보간을 위한 템플릿 라이브러리 대신 사용하는 것입니다.
const person = {name: 'Tyler', age: 28};
document.body.innerHTML = `
  <div>
    <p>Name: ${person.name}</p>
    <p>Age: ${person.age}</p>
  </div>
`;
.innerHTML을 사용하면 코드가 XSS에 취약할 수 있습니다. 사용자의 데이터인 경우 표시하기 전에 데이터를 정리하십시오!

References


53. 커링 함수 구문의 예시와 이점을 설명해 주세요

Currying은 하나 이상의 매개변수가 있는 함수가 연속적으로 호출될 때
필요한 모든 매개변수를 한 번에 하나씩 축적하는 여러 함수로 분리되는 패턴입니다.
이 기술은 함수형 스타일로 작성된 코드를 더 쉽게 합성할 수 있게 하며, 가독성을 높입니다.
함수를 커리하려면 하나의 함수로 시작한 다음 각각 하나의 매개변수를 받는 일련의 함수로 나누어야 합니다.
function curry(fn) {
  if (fn.length === 0) {
    return fn;
  }

  function _curried(depth, args) {
    return function (newArgument) {
      if (depth - 1 === 0) {
        return fn(...args, newArgument);
      }
      return _curried(depth - 1, [...args, newArgument]);
    };
  }

  return _curried(fn.length, []);
}

function add(a, b) {
  return a + b;
}

var curriedAdd = curry(add);
var addFive = curriedAdd(5);

var result = [0, 1, 2, 3, 4, 5].map(addFive); // [5, 6, 7, 8, 9, 10]​

References


54. 스프레드 구문은 rest 구문과 무엇이 다르죠?

ES6의 스프레드 구문은 Object.create, 슬라이스 또는 라이브러리 함수에 의존하지 않고,
배열이나 객체의 복사본을 쉽게 만들 수 있으므로
함수형 패러다임으로 코딩할 때 매우 유용합니다.
이 언어 기능은 Redux 및 RxJS 프로젝트에서 자주 사용됩니다.
function putDookieInAnyArray(arr) {
  return [...arr, 'dookie'];
}

const result = putDookieInAnyArray(['I', 'really', "don't", 'like']); // ["I", "really", "don't", "like", "dookie"]

const person = {
  name: 'Todd',
  age: 29,
};

const copyOfTodd = {...person};​
 
ES6의 rest 구문은 배열, 객체에 할당할 임의 갯수의 속성, 항목을 나타냅니다.
spread 구문의 반대와 유사하며, 임의 갯수의 데이터를 인수로 받아 배열, 객체를 생성합니다.
함수 인수는 물론 배열, 객체 구조 분해 할당에서도 동작합니다.
function addFiveToABunchOfNumbers(...numbers) {
  return numbers.map((x) => x + 5);
}

const result = addFiveToABunchOfNumbers(4, 5, 6, 7, 8, 9, 10); // [9, 10, 11, 12, 13, 14, 15]

const [a, b, ...rest] = [1, 2, 3, 4]; // a: 1, b: 2, rest: [3, 4]

const {e, f, ...others} = {
  e: 1,
  f: 2,
  g: 3,
  h: 4,
}; // e: 1, f: 2, others: { g: 3, h: 4 }​

References


55. 파일 간에 코드를 어떻게 공유할 수 있나요?

JavaScript 환경에 따라 다릅니다.
클라이언트(브라우저 환경)에서 전역 범위(window)에 변수/함수가 선언되어 있는 한 모든 스크립트에서 참조할 수 있습니다.
또는 보다 모듈화된 접근 방식을 위해 RequireJS를 통해 비동기식 모듈 정의(AMD)를 사용할 수 있습니다.
 
서버(Node.js)에서 일반적인 방법은 CommonJS를 사용하는 것입니다.
각 파일은 모듈로 취급되며 변수와 함수를 module.exports 개체에 연결하여 내보낼 수 있습니다.
ES2015는 AMD와 CommonJS를 모두 대체하는 것을 목표로 하는 모듈 구문을 정의합니다.
이것은 결국 브라우저와 노드 환경 모두에서 지원될 것입니다.

References


56. 정적 클래스 멤버를 사용하는 이유는 뭔가요?

정적 클래스 멤버(속성/메서드)는 클래스의 특정 인스턴스에 연결되지 않으며 참조하는 인스턴스에 관계없이 동일한 값을 갖습니다.
정적 속성은 일반적으로 설정 변수입니다.
정적 메서드는 일반적으로 인스턴스 상태에 의존하지 않는 순수 유틸리티 함수입니다.

References

Other Answers

반응형