JavaScript

이벤트 위임(event delegation)을 설명하세요.

이벤트 위임은 자손 요소에 이벤트 리스너를 추가하는 대신 부모 요소에 이벤트 리스너를 추가하는 기술입니다. 이벤트는 DOM을 따라 이벤트 버블링(bubbling)으로 인해 자손 요소에서 트리거될 때마다 리스너가 실행됩니다. 이 기술의 이점은 다음과 같습니다:

  • 각 자손 요소에 이벤트 핸들러를 연결할 필요 없이 부모 요소에 하나의 핸들러만 필요하므로 메모리 사용량이 줄어듭니다.
  • 제거된 요소에서 핸들러를 언바인딩하고 새 요소에 이벤트를 바인딩할 필요가 없습니다.

JavaScript에서 `this`가 어떻게 작동하는지 설명하세요.

this에 대한 간단한 설명은 없습니다. JavaScript에서 가장 혼란스러운 개념 중 하나입니다. 대략적인 설명은 this의 값이 함수가 호출되는 방식에 따라 달라진다는 것입니다. 온라인에서 this에 대한 많은 설명을 읽어봤지만, [Arnav Aggrawal]의 설명이 가장 명확했습니다. 다음 규칙이 적용됩니다:

  1. 함수를 호출할 때 new 키워드가 사용되면 함수 내부의 this는 완전히 새로운 객체입니다.
  2. 함수를 호출/생성하기 위해 apply, call 또는 bind가 사용되면 함수 내부의 this는 인수로 전달된 객체입니다.
  3. 함수가 obj.method()와 같은 메서드로 호출되면 this는 함수가 속성인 객체입니다.
  4. 함수가 위의 조건 없이 호출되는 자유 함수 호출로 호출되면 this는 전역 객체입니다. 브라우저에서는 window 객체입니다. 엄격 모드('use strict')에서는 this가 전역 객체 대신 undefined가 됩니다.
  5. 위의 규칙 중 여러 개가 적용되면 더 높은 규칙이 우선하여 this 값을 설정합니다.
  6. 함수가 ES2015 화살표 함수이면 위의 모든 규칙을 무시하고 생성될 때 주변 스코프의 this 값을 받습니다.

자세한 설명은 그의 [Medium 기사]를 확인하세요.

ES6에서 this를 사용하는 방식이 변경된 예시를 들어주실 수 있나요?

ES6에서는 [둘러싸는 어휘적 스코프]를 사용하는 [화살표 함수]를 사용할 수 있습니다. 이는 일반적으로 편리하지만, 호출자가 .call 또는 .apply를 통해 컨텍스트를 제어하는 것을 방해합니다. 그 결과 jQuery와 같은 라이브러리가 이벤트 핸들러 함수에서 this를 제대로 바인딩하지 못하게 됩니다. 따라서 대규모 레거시 애플리케이션을 리팩터링할 때 이 점을 염두에 두는 것이 중요합니다.

프로토타입 상속이 어떻게 작동하는지 설명하세요.

모든 JavaScript 객체는 Object.create(null)로 생성된 객체를 제외하고 다른 객체에 대한 참조인 __proto__ 속성을 가지고 있으며, 이 객체를 객체의 '프로토타입'이라고 합니다. 객체에서 속성에 액세스할 때 속성을 찾을 수 없으면 JavaScript 엔진은 객체의 __proto__, 그리고 __proto____proto__ 등을 프로토타입 체인의 끝에 도달하거나 __proto__ 중 하나에서 속성을 찾을 때까지 찾습니다. 이 동작은 클래식 상속을 시뮬레이션하지만 실제로는 [상속이라기보다 위임]에 가깝습니다.

프로토타입 상속 예시

// 부모 객체 생성자. function Animal(name) { this.name = name; } // 부모 객체의 프로토타입에 메서드를 추가합니다. Animal.prototype.makeSound = function () { console.log('The ' + this.constructor.name + ' makes a sound.'); }; // 자식 객체 생성자. function Dog(name) { Animal.call(this, name); // 부모 생성자를 호출합니다. } // 자식 객체의 프로토타입을 부모의 프로토타입으로 설정합니다. Object.setPrototypeOf(Dog.prototype, Animal.prototype); // 자식 객체의 프로토타입에 메서드를 추가합니다. Dog.prototype.bark = function () { console.log('Woof!'); }; // Dog의 새 인스턴스를 생성합니다. const bolt = new Dog('Bolt'); // 자식 객체에서 메서드를 호출합니다. console.log(bolt.name); // 'Bolt' bolt.makeSound(); // 'The Dog makes a sound.' bolt.bark(); // 'Woof!'

주목할 점은 다음과 같습니다:

  • Dog에는 .makeSound가 정의되어 있지 않으므로 엔진은 프로토타입 체인을 거슬러 올라가 상속된 Animal에서 .makeSound를 찾습니다.
  • Object.create를 사용하여 상속 체인을 구축하는 것은 더 이상 권장되지 않습니다. 대신 Object.setPrototypeOf를 사용하십시오.

AMD와 CommonJS에 대해 어떻게 생각하시나요?

둘 다 ES2015가 등장하기 전까지 JavaScript에 기본적으로 존재하지 않던 모듈 시스템을 구현하는 방법입니다. CommonJS는 동기식인 반면 AMD(Asynchronous Module Definition)는 분명히 비동기식입니다. CommonJS는 서버 측 개발을 염두에 두고 설계되었지만, 모듈의 비동기 로딩을 지원하는 AMD는 브라우저를 위한 것입니다.

AMD 구문이 다소 장황하다고 생각하며 CommonJS는 다른 언어에서 import 문을 작성하는 방식과 더 가깝습니다. 대부분의 경우 AMD는 불필요하다고 생각합니다. 왜냐하면 모든 JavaScript를 하나의 연결된 번들 파일로 제공하는 경우 비동기 로딩 속성에서 이점을 얻을 수 없기 때문입니다. 또한 CommonJS 구문은 Node의 모듈 작성 방식과 더 가깝고 클라이언트 측과 서버 측 JavaScript 개발 간에 전환할 때 컨텍스트 전환 오버헤드가 적습니다.

동기식 및 비동기식 로딩을 모두 지원하는 ES2015 모듈 덕분에 마침내 하나의 접근 방식만 고수할 수 있게 되어 기쁩니다. 브라우저와 Node에서 완전히 출시되지는 않았지만 항상 트랜스파일러를 사용하여 코드를 변환할 수 있습니다.

다음이 IIFE로 작동하지 않는 이유를 설명하세요: `function foo(){ }();`. 올바르게 IIFE로 만들려면 무엇을 변경해야 하나요?

IIFE는 즉시 실행 함수 표현식(Immediately Invoked Function Expressions)의 약자입니다. JavaScript 파서는 function foo(){ }();function foo(){ }();로 읽는데, 전자는 _함수 선언_이고 후자(한 쌍의 괄호)는 함수를 호출하려는 시도이지만 이름이 지정되지 않았으므로 Uncaught SyntaxError: Unexpected token ) 오류가 발생합니다.

괄호를 추가하는 두 가지 방법은 다음과 같습니다: (function foo(){ })()(function foo(){ }()). function으로 시작하는 문은 _함수 선언_으로 간주됩니다. 이 함수를 ()로 묶으면 _함수 표현식_이 되고, 이어서 ()로 실행할 수 있습니다. 이 함수들은 전역 스코프에 노출되지 않으며, 본문 내에서 자신을 참조할 필요가 없다면 이름을 생략할 수도 있습니다.

void 연산자를 사용할 수도 있습니다: void function foo(){ }();. 안타깝게도 이러한 접근 방식에는 한 가지 문제가 있습니다. 주어진 표현식의 평가 결과는 항상 undefined이므로, IIFE 함수가 무언가를 반환하더라도 사용할 수 없습니다. 예시:

const foo = void (function bar() { return 'foo'; })(); console.log(foo); // undefined

`null`, `undefined`, 선언되지 않은 변수의 차이점은 무엇인가요? 이러한 상태를 확인하는 방법은 무엇인가요?

선언되지 않은(Undeclared) 변수는 var, let 또는 const를 사용하여 이전에 생성되지 않은 식별자에 값을 할당할 때 생성됩니다. 선언되지 않은 변수는 현재 스코프 외부에서 전역적으로 정의됩니다. 엄격 모드에서는 선언되지 않은 변수에 할당하려고 하면 ReferenceError가 발생합니다. 선언되지 않은 변수는 전역 변수처럼 좋지 않습니다. 어떤 수를 써서라도 피하세요! 이를 확인하려면 사용을 try/catch 블록으로 묶으십시오.

function foo() { x = 1; // 엄격 모드에서 ReferenceError 발생 } foo(); console.log(x); // 1

undefined 변수는 선언되었지만 값이 할당되지 않은 변수입니다. undefined 타입입니다. 함수가 실행 결과로 어떤 값도 반환하지 않고 변수에 할당되면 변수도 undefined 값을 가집니다. 이를 확인하려면 엄격 일치(===) 연산자 또는 'undefined' 문자열을 반환하는 typeof를 사용하여 비교하십시오. 값이 null인 경우에도 true를 반환하므로 추상 일치 연산자를 사용하여 확인해서는 안 된다는 점에 유의하십시오.

var foo; console.log(foo); // undefined console.log(foo === undefined); // true console.log(typeof foo === 'undefined'); // true console.log(foo == null); // true. 틀렸습니다. 이렇게 확인하지 마세요! function bar() {} var baz = bar(); console.log(baz); // undefined

null 변수는 null 값으로 명시적으로 할당됩니다. 값 없음을 나타내며, 명시적으로 할당되었다는 점에서 undefined와 다릅니다. null을 확인하려면 엄격 일치 연산자를 사용하여 비교하기만 하면 됩니다. 위와 마찬가지로, 값이 undefined인 경우에도 true를 반환하므로 추상 일치 연산자(==)를 사용하여 확인해서는 안 된다는 점에 유의하십시오.

var foo = null; console.log(foo === null); // true console.log(typeof foo === 'object'); // true console.log(foo == undefined); // true. 틀렸습니다. 이렇게 확인하지 마세요!

개인적인 습관으로는 변수를 선언하지 않거나 할당하지 않은 상태로 두지 않습니다. 아직 사용하지 않을 예정이라면 선언 후 명시적으로 null을 할당합니다. 워크플로에서 린터를 사용하면 선언되지 않은 변수를 참조하지 않는지도 확인할 수 있습니다.

클로저(closure)란 무엇이며, 어떻게/왜 사용하나요?

클로저는 함수와 그 함수가 선언된 어휘적 환경의 조합입니다. '어휘적'이라는 단어는 어휘적 스코핑이 소스 코드 내에서 변수가 선언된 위치를 사용하여 해당 변수가 어디에서 사용 가능한지 결정한다는 사실을 나타냅니다. 클로저는 외부 함수가 반환된 후에도 외부(둘러싸는) 함수의 변수(스코프 체인)에 액세스할 수 있는 함수입니다.

왜 사용해야 할까요?

  • 클로저를 사용한 데이터 프라이버시 / 비공개 메서드 에뮬레이션. [모듈 패턴]에서 일반적으로 사용됩니다.
  • [부분 적용 또는 커링(currying)].

`.forEach` 루프와 `.map()` 루프의 주요 차이점은 무엇이며, 어떤 경우에 어떤 것을 선택해야 하는지 설명해 주실 수 있나요?

둘 사이의 차이점을 이해하기 위해 각 함수가 수행하는 작업을 살펴보겠습니다.

forEach

  • 배열의 요소를 반복합니다.
  • 각 요소에 대해 콜백을 실행합니다.
  • 값을 반환하지 않습니다.
const a = [1, 2, 3]; const doubled = a.forEach((num, index) => { // num 및/또는 index로 무언가를 수행합니다. }); // doubled = undefined

map

  • 배열의 요소를 반복합니다.
  • 각 요소에 함수를 호출하여 각 요소를 새 요소로 '매핑'하고 그 결과로 새 배열을 생성합니다.
const a = [1, 2, 3]; const doubled = a.map((num) => { return num * 2; }); // doubled = [2, 4, 6]

.forEach.map()의 주요 차이점은 .map()이 새 배열을 반환한다는 것입니다. 결과가 필요하지만 원본 배열을 변경하고 싶지 않다면 .map()이 명확한 선택입니다. 단순히 배열을 반복해야 한다면 forEach가 좋은 선택입니다.

익명 함수의 일반적인 사용 사례는 무엇인가요?

IIFE에서 일부 코드를 로컬 스코프 내에 캡슐화하여 선언된 변수가 전역 스코프로 누출되지 않도록 하는 데 사용할 수 있습니다.

(function () { // 여기에 일부 코드. })();

한 번만 사용되고 다른 곳에서는 사용될 필요가 없는 콜백으로 사용됩니다. 핸들러가 코드를 호출하는 코드 내에 바로 정의될 때 코드가 더 자체 포함되고 읽기 쉽게 보일 것입니다. 함수 본문을 찾기 위해 다른 곳을 검색할 필요가 없습니다.

setTimeout(function () { console.log('Hello world!'); }, 1000);

함수형 프로그래밍 구성 또는 Lodash(콜백과 유사)에 대한 인수.

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

코드를 어떻게 구성하시나요? (모듈 패턴, 클래식 상속?)

과거에는 Backbone을 모델에 사용했는데, 이는 더 OOP적인 접근 방식을 장려하여 Backbone 모델을 만들고 메서드를 첨부했습니다.

모듈 패턴은 여전히 훌륭하지만, 요즘에는 Flux 아키텍처를 기반으로 하는 단방향 데이터 흐름을 활용하는 React/Redux를 사용합니다. 앱의 모델을 일반 객체로 표현하고 이러한 객체를 조작하는 유틸리티 순수 함수를 작성할 것입니다. 다른 Redux 애플리케이션과 마찬가지로 상태는 액션과 리듀서를 사용하여 조작됩니다.

가능한 한 클래식 상속 사용을 피합니다. 사용하는 경우 [이러한 규칙]을 준수합니다.

호스트 객체와 네이티브 객체의 차이점은 무엇인가요?

네이티브 객체는 ECMAScript 사양에 의해 정의된 JavaScript 언어의 일부인 객체로, String, Math, RegExp, Object, Function 등입니다.

호스트 객체는 window, XMLHTTPRequest 등 런타임 환경(브라우저 또는 Node)에서 제공됩니다.

다음의 차이점은 무엇인가요: `function Person(){}`, `var person = Person()`, 그리고 `var person = new Person()`?

이 질문은 상당히 모호합니다. 의도는 JavaScript의 생성자에 대해 묻는 것 같습니다. 엄밀히 말하면 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'

`.call`과 `.apply`의 차이점은 무엇인가요?

.call.apply는 모두 함수를 호출하는 데 사용되며, 첫 번째 매개변수는 함수 내에서 this의 값으로 사용됩니다. 그러나 .call은 다음 인수로 쉼표로 구분된 인수를 받는 반면, .apply는 다음 인수로 인수 배열을 받습니다. 이를 쉽게 기억하는 방법은 call의 C와 쉼표로 구분된(comma-separated), apply의 A와 인수 배열(an array of arguments)입니다.

function add(a, b) { return a + b; } console.log(add.call(null, 1, 2)); // 3 console.log(add.apply(null, [1, 2])); // 3

`Function.prototype.bind`를 설명하세요.

[MDN]에서 단어 그대로 가져온 것입니다:

bind() 메서드는 호출될 때 this 키워드가 제공된 값으로 설정되고, 새 함수가 호출될 때 제공된 인수에 선행하는 주어진 인수의 시퀀스가 있는 새 함수를 생성합니다.

제 경험상, 다른 함수에 전달하려는 클래스 메서드에서 this의 값을 바인딩하는 데 가장 유용합니다. 이는 React 컴포넌트에서 자주 수행됩니다.

`document.write()`는 언제 사용하시겠어요?

document.write()document.open()으로 열린 문서 스트림에 텍스트 문자열을 씁니다. 페이지가 로드된 후 document.write()가 실행되면 document.open이 호출되어 전체 문서(<head><body>가 제거됨!)를 지우고 주어진 매개변수 값으로 내용을 대체합니다. 따라서 일반적으로 위험하고 오용되기 쉽다고 간주됩니다.

온라인에는 document.write()가 분석 코드에 사용되거나 [JavaScript가 활성화된 경우에만 작동해야 하는 스타일을 포함하려는 경우]에 사용된다고 설명하는 답변이 있습니다. HTML5 boilerplate에서도 [스크립트를 병렬로 로드하고 실행 순서를 유지]하는 데 사용됩니다! 하지만 이러한 이유는 구식이 되었을 수 있으며 현대에는 document.write()를 사용하지 않고도 달성할 수 있다고 생각합니다. 만약 제가 틀렸다면 정정해 주십시오.

기능 감지, 기능 추론, UA 문자열 사용의 차이점은 무엇인가요?

기능 감지(Feature Detection)

기능 감지는 브라우저가 특정 코드 블록을 지원하는지 확인하고, 지원 여부에 따라 다른 코드를 실행하여 브라우저가 일부 브라우저에서 충돌/오류 없이 항상 작동하는 경험을 제공할 수 있도록 하는 것입니다. 예를 들어:

if ('geolocation' in navigator) { // navigator.geolocation을 사용할 수 있음 } else { // 기능 부족 처리 }

[Modernizr]는 기능 감지를 처리하는 훌륭한 라이브러리입니다.

기능 추론(Feature Inference)

기능 추론은 기능 감지와 마찬가지로 기능을 확인하지만, 다른 함수도 존재할 것이라고 가정하고 다른 함수를 사용합니다. 예를 들어:

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

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

UA 문자열(UA String)

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

Ajax에 대해 가능한 한 자세히 설명하세요.

Ajax(비동기 JavaScript 및 XML)는 비동기 웹 애플리케이션을 만들기 위해 클라이언트 측에서 많은 웹 기술을 사용하는 웹 개발 기술 집합입니다. Ajax를 사용하면 웹 애플리케이션이 기존 페이지의 표시 및 동작을 방해하지 않고 서버에 데이터를 비동기적으로(백그라운드에서) 보내고 검색할 수 있습니다. 데이터 교환 계층과 프레젠테이션 계층을 분리함으로써 Ajax는 전체 페이지를 다시 로드할 필요 없이 웹 페이지(및 그에 따른 웹 애플리케이션)의 내용을 동적으로 변경할 수 있도록 합니다. 실제로는 JSON이 JavaScript에 기본적으로 내장되어 있다는 이점 때문에 XML 대신 JSON을 사용하는 것이 일반적입니다.

XMLHttpRequest API는 비동기 통신에 자주 사용되거나, 요즘에는 fetch() API가 사용됩니다.

Ajax 사용의 장점과 단점은 무엇인가요?

장점

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

단점

  • 동적 웹 페이지는 북마크하기가 더 어렵습니다.
  • 브라우저에서 JavaScript가 비활성화된 경우 작동하지 않습니다.
  • 일부 웹 크롤러는 JavaScript를 실행하지 않으며 JavaScript가 로드한 콘텐츠를 볼 수 없습니다.
  • 데이터를 가져오기 위해 Ajax를 사용하는 웹 페이지는 가져온 원격 데이터를 클라이언트 측 템플릿과 결합하여 DOM을 업데이트해야 할 것입니다. 이렇게 하려면 브라우저에서 JavaScript를 구문 분석하고 실행해야 하며, 저사양 모바일 장치는 이로 인해 어려움을 겪을 수 있습니다.
  • 기본적으로 SPA의 대부분의 단점과 같습니다.

JSONP가 어떻게 작동하는지 설명하세요(그리고 실제 Ajax가 아닌 이유).

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

JSONP는 <script> 태그를 통해 다른 출처 도메인으로 요청을 보내고 일반적으로 callback 쿼리 매개변수를 함께 보냅니다. 예를 들어: https://example.com?callback=printData입니다. 그러면 서버는 printData라는 함수 내에 데이터를 래핑하여 클라이언트에 반환합니다.

<script> function printData(data) { console.log(`My name is ${data.name}!`); } </script> <script src="[https://example.com?callback=printData](https://example.com?callback=printData)"></script>
printData({ name: 'Yang Shun' });

클라이언트는 전역 스코프에 printData 함수를 가지고 있어야 하며, 다른 출처 도메인에서 응답을 받으면 클라이언트가 함수를 실행합니다.

JSONP는 안전하지 않을 수 있으며 일부 보안상의 문제가 있습니다. JSONP는 실제 JavaScript이므로 JavaScript가 할 수 있는 모든 것을 할 수 있습니다. 따라서 JSONP 데이터를 제공하는 공급자를 신뢰해야 합니다.

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

JavaScript 템플릿을 사용해 본 적이 있나요? 그렇다면 어떤 라이브러리를 사용해 보셨나요?

네. Handlebars, Underscore, Lodash, AngularJS 및 JSX를 사용했습니다. AngularJS의 템플릿은 지시문에서 문자열을 많이 사용하여 오타가 감지되지 않아 마음에 들지 않았습니다. JSX는 JavaScript에 더 가깝고 배울 구문이 거의 없기 때문에 제가 가장 좋아하는 것입니다. 요즘에는 ES2015 템플릿 문자열 리터럴을 사용하여 타사 코드에 의존하지 않고도 템플릿을 빠르게 생성할 수 있습니다.

const template = `<div>My name is: ${name}</div>`;

그러나 템플릿 라이브러리와 달리 콘텐츠가 자동으로 이스케이프되지 않으므로 위의 접근 방식에서 잠재적인 XSS에 유의해야 합니다.

호이스팅(hoisting)을 설명하세요.

호이스팅은 코드에서 변수 선언의 동작을 설명하는 데 사용되는 용어입니다. var 키워드로 선언되거나 초기화된 변수는 선언부가 모듈/함수 수준 스코프의 맨 위로 '이동'하는데, 이를 호이스팅이라고 합니다. 그러나 선언만 호이스팅되고, 할당(있는 경우)은 원래 위치에 유지됩니다.

선언이 실제로 이동하는 것이 아니라는 점에 유의하십시오. JavaScript 엔진은 컴파일 중에 선언을 구문 분석하고 선언 및 해당 스코프를 인식합니다. 단지 선언이 스코프의 맨 위로 호이스팅되는 것으로 시각화하여 이 동작을 이해하는 것이 더 쉽습니다. 몇 가지 예시로 설명해 보겠습니다.

console.log(foo); // undefined var foo = 1; console.log(foo); // 1

함수 선언은 본문이 호이스팅되는 반면, 함수 표현식(변수 선언 형태로 작성됨)은 변수 선언만 호이스팅됩니다.

// 함수 선언 console.log(foo); // [Function: foo] foo(); // 'FOOOOO' function foo() { console.log('FOOOOO'); } console.log(foo); // [Function: foo] // 함수 표현식 console.log(bar); // undefined bar(); // Uncaught TypeError: bar is not a function var bar = function () { console.log('BARRRR'); }; console.log(bar); // [Function: bar]

letconst로 선언된 변수도 호이스팅됩니다. 그러나 varfunction과는 달리 초기화되지 않으며, 선언 전에 접근하면 ReferenceError 예외가 발생합니다. 변수는 블록 시작부터 선언이 처리될 때까지 '임시 사각 지대'에 있습니다.

x; // undefined y; // Reference error: y is not defined var x = 'local'; let y = 'local';

이벤트 버블링(event bubbling)을 설명하세요.

DOM 요소에서 이벤트가 트리거되면, 리스너가 연결되어 있으면 이벤트를 처리하려고 시도한 다음, 이벤트는 부모로 버블링되어 동일한 일이 발생합니다. 이 버블링은 요소의 조상을 거쳐 document까지 계속 발생합니다. 이벤트 버블링은 이벤트 위임 뒤에 있는 메커니즘입니다.

속성(attribute)과 프로퍼티(property)의 차이점은 무엇인가요?

속성은 HTML 마크업에 정의되지만, 프로퍼티는 DOM에 정의됩니다. 차이점을 설명하기 위해 HTML에 다음과 같은 텍스트 필드가 있다고 상상해 보세요: <input type="text" value="Hello">.

const input = document.querySelector('input'); console.log(input.getAttribute('value')); // Hello console.log(input.value); // Hello

하지만 텍스트 필드의 값에 'World!'를 추가하여 변경하면 다음과 같이 됩니다:

console.log(input.getAttribute('value')); // Hello console.log(input.value); // Hello World!

내장 JavaScript 객체를 확장하는 것이 좋지 않은 이유는 무엇인가요?

내장/네이티브 JavaScript 객체를 확장한다는 것은 해당 prototype에 속성/함수를 추가하는 것을 의미합니다. 처음에는 좋은 생각처럼 보일 수 있지만 실제로는 위험합니다. 코드가 Array.prototype를 동일한 contains 메서드를 추가하여 확장하는 두 개의 라이브러리를 사용한다고 상상해 보세요. 구현이 서로 덮어쓰여지고 이 두 메서드의 동작이 동일하지 않으면 코드가 손상될 것입니다.

네이티브 객체를 확장하고 싶은 유일한 경우는 폴리필을 생성하려는 경우입니다. 즉, JavaScript 사양의 일부이지만 오래된 브라우저이기 때문에 사용자 브라우저에 존재하지 않을 수 있는 메서드에 대해 자신만의 구현을 제공하는 것입니다.

문서 `load` 이벤트와 문서 `DOMContentLoaded` 이벤트의 차이점은 무엇인가요?

DOMContentLoaded 이벤트는 초기 HTML 문서가 완전히 로드되고 파싱되었을 때 발생하며, 스타일시트, 이미지 및 서브프레임이 로드될 때까지 기다리지 않습니다.

windowload 이벤트는 DOM과 모든 종속 리소스 및 자산이 로드된 후에만 발생합니다.

`==`와 `===`의 차이점은 무엇인가요?

==는 추상 등가 연산자이고 ===는 엄격 등가 연산자입니다. == 연산자는 필요한 모든 타입 변환을 수행한 후 등가성을 비교합니다. === 연산자는 타입 변환을 수행하지 않으므로 두 값이 같은 타입이 아니면 ===는 단순히 false를 반환합니다. ==를 사용하면 다음과 같은 기묘한 일이 발생할 수 있습니다:

1 == '1'; // true 1 == [1]; // true 1 == true; // true 0 == ''; // true 0 == '0'; // true 0 == false; // true

null 또는 undefined와 비교할 때 편리함을 제외하고는 == 연산자를 절대 사용하지 않는 것이 좋습니다. a == nullanull 또는 undefined이면 true를 반환합니다.

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

JavaScript와 관련하여 동일 출처 정책(same-origin policy)을 설명하세요.

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

이것을 작동하게 만드세요:

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

또는 ES6로:

const duplicate = (arr) => [...arr, ...arr]; duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]

왜 삼항(Ternary) 표현식이라고 불리나요? '삼항'이라는 단어는 무엇을 의미하나요?

'삼항(Ternary)'은 셋을 나타내며, 삼항 표현식은 세 가지 피연산자를 받습니다. 즉, 테스트 조건, 'then' 표현식, 'else' 표현식입니다. 삼항 표현식은 JavaScript에만 국한된 것이 아니므로 이 목록에 있는 이유를 잘 모르겠습니다.

`'use strict';`는 무엇인가요? 이를 사용하는 장단점은 무엇인가요?

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

장점:

  • 실수로 전역 변수를 생성하는 것을 불가능하게 만듭니다.
  • 그렇지 않으면 조용히 실패했을 할당이 예외를 발생시키도록 합니다.
  • 삭제할 수 없는 속성을 삭제하려는 시도가 예외를 발생시키도록 합니다(이전에는 시도해도 아무 효과가 없었음).
  • 함수 매개변수 이름이 고유해야 합니다.
  • 전역 컨텍스트에서 thisundefined입니다.
  • 일부 일반적인 코딩 실수를 잡아내고 예외를 발생시킵니다.
  • 혼란스럽거나 제대로 고려되지 않은 기능을 비활성화합니다.

단점:

  • 일부 개발자가 익숙할 수 있는 많은 기능이 누락되었습니다.
  • 더 이상 function.callerfunction.arguments에 액세스할 수 없습니다.
  • 다른 엄격 모드로 작성된 스크립트의 연결이 문제를 일으킬 수 있습니다.

전반적으로 장점이 단점보다 크다고 생각하며, 엄격 모드가 차단하는 기능에 의존해야 할 필요는 없었습니다. 엄격 모드 사용을 권장합니다.

`3`의 배수에서는 **"fizz"**, `5`의 배수에서는 **"buzz"**, `3`과 `5`의 배수에서는 **"fizzbuzz"**를 출력하면서 `100`까지 반복하는 for 루프를 만드세요.

[Paul Irish]의 FizzBuzz 버전을 확인하세요.

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); }

면접에서는 위와 같이 작성하지 않는 것이 좋습니다. 길지만 명확한 접근 방식을 고수하세요. 더 기발한 FizzBuzz 버전은 아래 참조 링크를 확인하세요.

일반적으로 웹사이트의 전역 스코프를 그대로 두고 절대로 건드리지 않는 것이 좋은 이유는 무엇인가요?

모든 스크립트는 전역 스코프에 액세스할 수 있으며, 모든 사람이 전역 네임스페이스를 사용하여 변수를 정의하면 충돌이 발생할 가능성이 높습니다. 모듈 패턴(IIFE)을 사용하여 변수를 로컬 네임스페이스 내에 캡슐화하십시오.

`load` 이벤트와 같은 것을 사용하는 이유는 무엇인가요? 이 이벤트에 단점이 있나요? 다른 대안을 알고 있다면 왜 그것들을 사용하시겠어요?

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

DOM 이벤트 DOMContentLoaded는 페이지의 DOM이 구성된 후에 발생하지만, 다른 리소스가 로드될 때까지 기다리지 않습니다. 이는 초기화하기 전에 전체 페이지가 로드될 필요가 없는 특정 경우에 선호됩니다.

단일 페이지 앱이란 무엇이며 SEO 친화적으로 만드는 방법은 무엇인가요?

아래 내용은 제가 작성한 멋진 [Grab 프론트 엔드 가이드]에서 발췌한 것입니다!

요즘 웹 개발자들은 자신이 만드는 제품을 웹사이트보다는 웹 앱이라고 부릅니다. 두 용어 사이에 엄격한 차이는 없지만, 웹 앱은 사용자가 작업을 수행하고 해당 작업에 대한 응답을 받을 수 있도록 고도로 상호 작용적이고 동적인 경향이 있습니다. 전통적으로 브라우저는 서버에서 HTML을 받아 렌더링합니다. 사용자가 다른 URL로 이동하면 전체 페이지 새로 고침이 필요하며 서버는 새 페이지에 새로운 HTML을 보냅니다. 이를 서버 측 렌더링이라고 합니다.

그러나 최신 SPA에서는 대신 클라이언트 측 렌더링이 사용됩니다. 브라우저는 서버에서 초기 페이지를 로드하고 전체 앱에 필요한 스크립트(프레임워크, 라이브러리, 앱 코드)와 스타일시트를 로드합니다. 사용자가 다른 페이지로 이동해도 페이지 새로 고침이 트리거되지 않습니다. 페이지의 URL은 [HTML5 History API]를 통해 업데이트됩니다. 새 페이지에 필요한 새 데이터는 일반적으로 JSON 형식으로 [AJAX] 요청을 통해 서버에서 브라우저에 의해 검색됩니다. 그러면 SPA는 초기 페이지 로드 시 이미 다운로드한 JavaScript를 통해 데이터를 사용하여 페이지를 동적으로 업데이트합니다. 이 모델은 기본 모바일 앱이 작동하는 방식과 유사합니다.

장점:

  • 앱이 더 반응성이 뛰어나고 전체 페이지 새로 고침으로 인한 페이지 탐색 간 깜박임이 사용자에게 보이지 않습니다.
  • 각 페이지 로드마다 동일한 자산을 다시 다운로드할 필요가 없으므로 서버에 대한 HTTP 요청이 줄어듭니다.
  • 클라이언트와 서버 간의 관심사가 명확하게 분리되어 있습니다. 서버 코드를 수정할 필요 없이 다른 플랫폼(예: 모바일, 챗봇, 스마트 워치)을 위한 새로운 클라이언트를 쉽게 구축할 수 있습니다. 또한 API 계약이 깨지지 않는 한 클라이언트와 서버의 기술 스택을 독립적으로 수정할 수 있습니다.

단점:

  • 여러 페이지에 필요한 프레임워크, 앱 코드 및 자산 로딩으로 인해 초기 페이지 로드가 더 무겁습니다.
  • 서버에서 추가 단계가 필요합니다. 모든 요청을 단일 진입점으로 라우팅하고 클라이언트 측 라우팅이 거기에서 작동하도록 구성해야 합니다.
  • SPA는 콘텐츠를 렌더링하기 위해 JavaScript에 의존하지만, 모든 검색 엔진이 크롤링 중에 JavaScript를 실행하는 것은 아니며 페이지에서 빈 콘텐츠를 볼 수 있습니다. 이는 의도치 않게 앱의 검색 엔진 최적화(SEO)에 해를 끼칩니다. 그러나 대부분의 경우 앱을 구축할 때 SEO가 가장 중요한 요소는 아닙니다. 모든 콘텐츠가 검색 엔진에서 인덱싱될 필요는 없기 때문입니다. 이를 극복하려면 앱을 서버 측 렌더링하거나 [Prerender]와 같은 서비스를 사용하여 '브라우저에서 자바스크립트를 렌더링하고, 정적 HTML을 저장하고, 이를 크롤러에 반환'할 수 있습니다.

Promise 및/또는 해당 폴리필에 대한 경험은 어느 정도입니까?

작동 방식을 알고 있습니다. Promise는 미래의 어느 시점에 단일 값(성공적인 값 또는 실패 이유(예: 네트워크 오류 발생))을 생성할 수 있는 객체입니다. Promise는 fulfilled, rejected 또는 pending의 세 가지 상태 중 하나에 있을 수 있습니다. Promise 사용자는 콜백을 첨부하여 완료된 값 또는 거부 이유를 처리할 수 있습니다.

일반적인 폴리필로는 $.deferred, Q 및 Bluebird가 있지만 이들 중 모든 것이 사양을 준수하는 것은 아닙니다. ES2015는 Promise를 기본적으로 지원하며 요즘에는 폴리필이 일반적으로 필요하지 않습니다.

콜백 대신 Promise를 사용하는 장단점은 무엇입니까?

장점

  • 읽기 어려울 수 있는 콜백 지옥을 피할 수 있습니다.
  • .then()을 사용하여 읽기 쉬운 순차적 비동기 코드를 쉽게 작성할 수 있습니다.
  • Promise.all()을 사용하여 병렬 비동기 코드를 쉽게 작성할 수 있습니다.
  • Promise를 사용하면 콜백 전용 코딩에서 발생할 수 있는 다음 시나리오가 발생하지 않습니다.
    • 콜백을 너무 일찍 호출
    • 콜백을 너무 늦게(또는 전혀) 호출
    • 콜백을 너무 적게 또는 너무 많이 호출
    • 필요한 환경/매개 변수를 전달하지 못함
    • 발생할 수 있는 오류/예외를 억제

단점

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

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

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

장점:

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

단점:

  • 브라우저는 JavaScript만 실행하므로 코드를 브라우저에 제공하기 전에 JavaScript로 컴파일해야 하므로 빌드/컴파일 프로세스가 필요합니다.
  • 소스 맵이 사전 컴파일된 소스와 잘 매핑되지 않으면 디버깅이 어려울 수 있습니다.
  • 대부분의 개발자는 이러한 언어에 익숙하지 않으므로 학습해야 합니다. 프로젝트에 사용하면 팀의 학습 비용이 발생합니다.
  • 커뮤니티가 작아서(언어에 따라 다름) 리소스, 튜토리얼, 라이브러리 및 도구를 찾기 어려울 수 있습니다.
  • IDE/편집기 지원이 부족할 수 있습니다.
  • 이러한 언어는 항상 최신 JavaScript 표준보다 뒤떨어집니다.
  • 개발자는 코드가 무엇으로 컴파일되는지 인지해야 합니다. 그것이 실제로 실행되는 것이고, 그것이 결국 중요하기 때문입니다.

실용적으로 ES2015는 JavaScript를 크게 개선하여 작성하기 훨씬 편리하게 만들었습니다. 요즘에는 CoffeeScript의 필요성을 느끼지 못합니다.

JavaScript 코드를 디버깅하는 데 어떤 도구와 기술을 사용하십니까?

  • React 및 Redux
    • [React Devtools]
    • [Redux Devtools]
  • Vue
    • [Vue Devtools]
  • JavaScript
    • [Chrome Devtools]
    • debugger
    • 오래된 console.log 디버깅

객체 속성과 배열 항목을 반복하는 데 사용하는 언어 구성은 무엇입니까?

객체의 경우:

  • for-in 루프 - 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 루프 - 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) { ... }). 이 구문은 필요한 것이 배열 요소뿐인 경우 index를 사용할 필요가 없으므로 때때로 더 편리할 수 있습니다. 반복을 일찍 종료할 수 있는 everysome 메서드도 있습니다.
  • for-of 루프 - for (let elem of arr) { ... }. ES6는 String, Array, Map, Set 등과 같은 이터러블 프로토콜을 준수하는 객체를 반복할 수 있는 새로운 루프인 for-of 루프를 도입합니다. 이는 for 루프와 forEach() 메서드의 장점을 결합합니다. for 루프의 장점은 루프를 종료할 수 있다는 것이고, 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); }

변경 가능한 객체와 변경 불가능한 객체의 차이점을 설명하십시오.

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

JavaScript에서 변경 불가능한 객체의 예시는 무엇입니까?

JavaScript에서 일부 내장 타입(숫자, 문자열)은 변경 불가능하지만, 사용자 정의 객체는 일반적으로 변경 가능합니다.

일부 내장 변경 불가능한 JavaScript 객체는 Math, Date입니다.

일반 JavaScript 객체에 불변성을 추가/시뮬레이션하는 몇 가지 방법은 다음과 같습니다.

객체 상수 속성

writable: falseconfigurable: 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

확장 방지

객체에 새 속성이 추가되는 것을 방지하고 싶지만, 객체의 나머지 속성은 그대로 두려면 Object.preventExtensions(...)를 호출합니다:

var myObject = { a: 2, }; Object.preventExtensions(myObject); myObject.b = 3; myObject.b; // undefined

비엄격 모드에서는 b 생성이 자동으로 실패합니다. 엄격 모드에서는 TypeError가 발생합니다.

봉인(Seal)

Object.seal()은 '봉인된' 객체를 생성합니다. 이는 기존 객체를 가져와 본질적으로 Object.preventExtensions()를 호출하지만, 모든 기존 속성을 configurable: false로 표시합니다.

따라서 더 이상 속성을 추가할 수 없을 뿐만 아니라 기존 속성을 재구성하거나 삭제할 수도 없습니다(단, 값은 여전히 수정할 수 있음).

고정(Freeze)

Object.freeze()는 고정된 객체를 생성합니다. 이는 기존 객체를 가져와 본질적으로 Object.seal()을 호출하지만, 모든 '데이터 접근자' 속성을 writable:false로 표시하여 해당 값을 변경할 수 없도록 합니다.

이 접근 방식은 객체 자체에 대해 달성할 수 있는 가장 높은 수준의 불변성으로, 객체 또는 직접적인 속성에 대한 변경을 방지합니다(위에서 언급했듯이 참조된 다른 객체의 내용은 영향을 받지 않음).

var immutable = Object.freeze({});

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

불변성의 장단점은 무엇입니까?

장점

  • 쉬운 변경 감지 - 참조 동일성을 통해 객체 동일성을 성능이 좋고 쉽게 결정할 수 있습니다. 이는 React 및 Redux에서 객체 차이를 비교하는 데 유용합니다.
  • 불변 객체가 있는 프로그램은 객체가 시간이 지남에 따라 어떻게 진화할 수 있는지 걱정할 필요가 없으므로 생각하기에 덜 복잡합니다.
  • 불변 객체가 함수에서 반환되거나 함수에 전달될 때 방어적 복사본이 더 이상 필요하지 않습니다. 불변 객체가 함수에 의해 수정될 가능성이 없기 때문입니다.
  • 참조를 통한 쉬운 공유 - 한 객체의 복사본은 다른 객체만큼 좋으므로 객체를 캐시하거나 동일한 객체를 여러 번 재사용할 수 있습니다.
  • 스레드 안전 - 불변 객체는 다른 동시 실행 스레드에서 수정될 위험이 없으므로 다중 스레드 환경에서 스레드 간에 안전하게 사용할 수 있습니다.
  • ImmutableJS와 같은 라이브러리를 사용하면 객체가 구조 공유를 사용하여 수정되고 유사한 구조를 가진 여러 객체를 갖는 데 필요한 메모리가 줄어듭니다.

단점

  • 불변 데이터 구조 및 해당 작업의 순진한 구현은 새로운 객체가 매번 생성되므로 성능이 매우 저하될 수 있습니다. 구조 공유를 활용하는 효율적인 불변 데이터 구조 및 작업을 위해 라이브러리를 사용하는 것이 좋습니다.
  • 기존 객체를 수정하는 대신 많은 작은 객체를 할당(및 할당 해제)하면 성능에 영향을 줄 수 있습니다. 할당자 또는 가비지 컬렉터의 복잡성은 일반적으로 힙의 객체 수에 따라 달라집니다.
  • 그래프와 같은 순환 데이터 구조는 구축하기 어렵습니다. 초기화 후 수정할 수 없는 두 객체가 있는 경우 어떻게 서로를 가리키게 할 수 있습니까?

자신의 코드에서 불변성을 어떻게 달성할 수 있습니까?

대안은 const 선언을 위에서 언급한 생성 기술과 결합하는 것입니다. 객체를 '변형'하려면 원본 객체를 변형하는 대신 스프레드 연산자, Object.assign, Array.concat() 등을 사용하여 새 객체를 만듭니다.

예시:

// 배열 예시 const arr = [1, 2, 3]; const newArr = [...arr, 4]; // [1, 2, 3, 4] // 객체 예시 const human = Object.freeze({ race: 'human' }); const john = { ...human, name: 'John' }; // {race: "human", name: "John"} const alienJohn = { ...john, race: 'alien' }; // {race: "alien", name: "John"}

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

동기 함수는 차단되는 반면 비동기 함수는 그렇지 않습니다. 동기 함수에서는 다음 문이 실행되기 전에 문이 완료됩니다. 이 경우 프로그램은 정확히 문 순서대로 평가되며, 문 중 하나가 매우 오래 걸리면 프로그램 실행이 일시 중지됩니다.

비동기 함수는 일반적으로 콜백을 매개 변수로 받아들이며, 비동기 함수가 호출된 직후 다음 줄에서 실행이 계속됩니다. 콜백은 비동기 작업이 완료되고 호출 스택이 비어 있을 때만 호출됩니다. 웹 서버에서 데이터를 로드하거나 데이터베이스를 쿼리하는 것과 같은 고용량 작업은 비동기적으로 수행되어야 합니다. 그래야 메인 스레드가 해당 긴 작업이 완료될 때까지 차단하는 대신 다른 작업을 계속 실행할 수 있습니다(브라우저의 경우 UI가 멈출 것입니다).

이벤트 루프란 무엇입니까? 호출 스택과 태스크 큐의 차이점은 무엇입니까?

이벤트 루프는 호출 스택을 모니터링하고 태스크 큐에 수행할 작업이 있는지 확인하는 단일 스레드 루프입니다. 호출 스택이 비어 있고 태스크 큐에 콜백 함수가 있으면 함수가 대기열에서 제거되어 실행을 위해 호출 스택에 푸시됩니다.

Philip Robert의 이벤트 루프에 대한 강연을 아직 보지 않았다면 꼭 봐야 합니다. JavaScript에서 가장 많이 조회된 비디오 중 하나입니다.

`function foo() {}`와 `var foo = function() {}` 사이에 `foo` 사용법의 차이점을 설명하십시오.

전자는 함수 선언이고 후자는 함수 표현식입니다. 주요 차이점은 함수 선언은 본문이 호이스팅되지만 함수 표현식의 본문은 그렇지 않다는 것입니다(변수와 동일한 호이스팅 동작을 가집니다). 호이스팅에 대한 더 자세한 설명은 위의 [호이스팅 설명] 질문을 참조하십시오. 함수 표현식을 정의하기 전에 호출하려고 하면 Uncaught TypeError: XXX is not a function 오류가 발생합니다.

함수 선언

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

함수 표현식

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

`let`, `var`, `const`를 사용하여 생성된 변수의 차이점은 무엇입니까?

var 키워드를 사용하여 선언된 변수는 생성된 함수 범위 내에 있거나, 함수 외부에서 생성된 경우 전역 객체에 속합니다. letconst는 _블록 범위_이므로 가장 가까운 중괄호(함수, if-else 블록 또는 for-루프) 내에서만 접근할 수 있습니다.

function foo() { // 모든 변수는 함수 내에서 접근 가능합니다. 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로 선언된 변수는 함수 범위 내 어디에서든 접근 가능합니다. console.log(bar); // bar // let과 const로 정의된 변수는 정의된 블록 외부에서 접근할 수 없습니다. console.log(baz); // ReferenceError: baz is not defined console.log(qux); // ReferenceError: qux is not defined

var는 변수가 호이스팅되도록 허용합니다. 즉, 변수가 선언되기 전에 코드에서 참조될 수 있습니다. letconst는 이를 허용하지 않고 대신 오류를 발생시킵니다.

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로 변수를 다시 선언해도 오류가 발생하지 않지만, letconst는 오류를 발생시킵니다.

var foo = 'foo'; var foo = 'bar'; console.log(foo); // "bar" let baz = 'baz'; let baz = 'qux'; // Uncaught SyntaxError: Identifier 'baz' has already been declared

letconstlet은 변수의 값을 다시 할당할 수 있는 반면 const는 그렇지 않다는 점에서 다릅니다.

// 이것은 괜찮습니다. let foo = 'foo'; foo = 'bar'; // 이것은 예외를 발생시킵니다. const baz = 'baz'; baz = 'qux';

ES6 클래스와 ES5 함수 생성자의 차이점은 무엇입니까?

먼저 각 예시를 살펴보겠습니다:

// ES5 함수 생성자 function Person(name) { this.name = name; } // ES6 클래스 class Person { constructor(name) { this.name = name; } }

간단한 생성자의 경우, 둘은 매우 유사해 보입니다.

생성자에서의 주요 차이점은 상속을 사용할 때 나타납니다. Person을 서브클래스화하고 studentId 필드를 추가하는 Student 클래스를 생성하려면 위 내용 외에 다음을 수행해야 합니다.

// ES5 함수 생성자 function Student(name, studentId) { // 슈퍼클래스 생성자를 호출하여 슈퍼클래스에서 파생된 멤버를 초기화합니다. Person.call(this, name); // 서브클래스 자체 멤버를 초기화합니다. this.studentId = studentId; } Student.prototype = Object.create(Person.prototype); Student.prototype.constructor = Student; // ES6 클래스 class Student extends Person { constructor(name, studentId) { super(name); this.studentId = studentId; } }

ES5에서 상속을 사용하는 것은 훨씬 더 장황하며 ES6 버전은 이해하고 기억하기가 더 쉽습니다.

새로운 화살표 => 함수 구문의 사용 사례를 제시하고 이 새로운 구문이 다른 함수와 어떻게 다른지 설명할 수 있습니까?

화살표 함수의 분명한 이점 중 하나는 function 키워드 없이 함수를 생성하는 데 필요한 구문을 단순화한다는 것입니다. 화살표 함수 내의 this는 또한 둘러싸는 스코프에 바인딩됩니다. 이는 this가 이를 호출하는 객체에 의해 결정되는 일반 함수와 다릅니다. 어휘적으로 스코프가 지정된 this는 특히 React 구성 요소에서 콜백을 호출할 때 유용합니다.

생성자에서 메서드에 화살표 구문을 사용하는 것의 장점은 무엇입니까?

생성자 내에서 메서드로 화살표 함수를 사용하는 주요 이점은 함수의 생성 시점에 this의 값이 설정되며 그 이후에는 변경될 수 없다는 것입니다. 따라서 생성자가 새 객체를 만드는 데 사용될 때 this는 항상 해당 객체를 참조합니다. 예를 들어, 이름을 인수로 받고 그 이름을 console.log하는 두 가지 메서드를 가진 Person 생성자가 있다고 가정해 봅시다. 하나는 일반 함수이고 다른 하나는 화살표 함수입니다:

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 // 일반 함수는 'this' 값을 변경할 수 있지만 화살표 함수는 변경할 수 없습니다. john.sayName1.call(dave); // Dave (왜냐하면 "this"는 이제 dave 객체이기 때문입니다) john.sayName2.call(dave); // John john.sayName1.apply(dave); // Dave (왜냐하면 'this'는 이제 dave 객체이기 때문입니다) john.sayName2.apply(dave); // John john.sayName1.bind(dave)(); // Dave (왜냐하면 'this'는 이제 dave 객체이기 때문입니다) john.sayName2.bind(dave)(); // John var sayNameFromWindow1 = john.sayName1; sayNameFromWindow1(); // undefined (왜냐하면 'this'는 이제 window 객체이기 때문입니다) var sayNameFromWindow2 = john.sayName2; sayNameFromWindow2(); // John

여기서 핵심은 일반 함수는 this를 변경할 수 있지만, 화살표 함수는 컨텍스트가 항상 동일하게 유지된다는 것입니다. 따라서 화살표 함수를 애플리케이션의 다른 부분으로 전달하더라도 컨텍스트 변경에 대해 걱정할 필요가 없습니다.

이것은 React 클래스 컴포넌트에서 특히 유용할 수 있습니다. 일반 함수를 사용하여 클릭 핸들러와 같은 클래스 메서드를 정의하고, 그 클릭 핸들러를 자식 컴포넌트에 prop으로 전달하는 경우, 부모 컴포넌트의 생성자에서도 this를 바인딩해야 합니다. 대신 화살표 함수를 사용하면 메서드가 자동으로 둘러싸는 어휘 컨텍스트에서 this 값을 가져오므로 "this"를 바인딩할 필요가 없습니다.

고차 함수(higher-order function)의 정의는 무엇입니까?

고차 함수는 하나 이상의 함수를 인수로 받아 일부 데이터를 조작하는 데 사용하거나, 함수를 결과로 반환하는 모든 함수입니다. 고차 함수는 반복적으로 수행되는 일부 작업을 추상화하기 위한 것입니다. 이에 대한 고전적인 예시는 배열과 함수를 인수로 받는 map입니다. map은 이 함수를 사용하여 배열의 각 항목을 변환하고 변환된 데이터가 있는 새 배열을 반환합니다. JavaScript에서 인기 있는 다른 예시는 forEach, filter, reduce입니다. 고차 함수는 배열만 조작할 필요는 없으며, 다른 함수에서 함수를 반환하는 많은 사용 사례가 있습니다. Function.prototype.bind는 JavaScript에서 그러한 예시 중 하나입니다.

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']

객체 또는 배열 구조 분해 할당의 예를 들 수 있습니까?

구조 분해 할당은 ES6에서 사용 가능한 표현식으로, 객체 또는 배열의 값을 추출하여 별도의 변수에 넣는 간결하고 편리한 방법을 제공합니다.

배열 구조 분해 할당

// 변수 할당. const foo = ['one', 'two', 'three']; const [one, two, three] = foo; console.log(one); // "one" console.log(two); // "two" console.log(three); // "three"
// 변수 교환 let a = 1; let b = 3; [a, b] = [b, a]; console.log(a); // 3 console.log(b); // 1

객체 구조 분해 할당

// 변수 할당. const o = { p: 42, q: true }; const { p, q } = o; console.log(p); // 42 console.log(q); // true

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에 취약할 수 있습니다. 사용자로부터 온 데이터라면 표시하기 전에 데이터를 소독하십시오!

커링 함수의 예를 들고 이 구문이 어떤 이점을 제공하는지 설명할 수 있습니까?

커링은 여러 매개변수를 가진 함수를 여러 함수로 분할하여, 순서대로 호출될 때 필요한 모든 매개변수를 한 번에 하나씩 누적하는 패턴입니다. 이 기술은 함수형 스타일로 작성된 코드를 더 읽기 쉽고 구성하기 쉽게 만드는 데 유용할 수 있습니다. 함수가 커링되려면 하나의 함수로 시작한 다음, 각 매개변수를 하나씩 받아들이는 일련의 함수로 분할되어야 한다는 점에 유의하는 것이 중요합니다.

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]

스프레드 문법 사용의 이점은 무엇이며 레스트 문법과 어떻게 다릅니까?

ES6의 스프레드 문법은 함수형 패러다임으로 코딩할 때 매우 유용합니다. Object.create, slice 또는 라이브러리 함수에 의존하지 않고도 배열 또는 객체의 복사본을 쉽게 만들 수 있기 때문입니다. 이 언어 기능은 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의 레스트 문법은 함수에 전달할 임의의 수의 인수를 포함하기 위한 축약형을 제공합니다. 이는 스프레드 문법의 역방향과 같으며, 데이터를 가져와 배열로 채워넣는 대신 데이터 배열을 풀고, 함수 인수뿐만 아니라 배열 및 객체 구조 분해 할당에서도 작동합니다.

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 }

파일 간에 코드를 공유하는 방법은 무엇입니까?

이는 JavaScript 환경에 따라 다릅니다.

클라이언트(브라우저 환경)에서는 변수/함수가 전역 범위(window)에 선언되어 있는 한 모든 스크립트가 참조할 수 있습니다. 또는 RequireJS를 통해 AMD(Asynchronous Module Definition)를 채택하여 더 모듈화된 접근 방식을 사용할 수 있습니다.

서버(Node.js)에서는 CommonJS를 사용하는 것이 일반적이었습니다. 각 파일은 모듈로 취급되며 module.exports 객체에 변수와 함수를 첨부하여 내보낼 수 있습니다.

ES2015는 AMD와 CommonJS를 모두 대체하는 것을 목표로 하는 모듈 구문을 정의합니다. 이는 결국 브라우저와 Node 환경 모두에서 지원될 것입니다.

정적 클래스 멤버를 생성하려는 이유는 무엇입니까?

정적 클래스 멤버(속성/메서드)는 클래스의 특정 인스턴스에 종속되지 않으며 어떤 인스턴스가 참조하든 동일한 값을 갖습니다. 정적 속성은 일반적으로 구성 변수이며 정적 메서드는 일반적으로 순수 함수이거나...