prepare_frontend_interview
JavaScript
프론트엔드 기술 면접을 위한 핸드북 만들기
면접의 인터뷰어 분들이 JS의 수 많은 개념들을 순서대로 질문을 하지는 않습니다.
하지만 자바스크립트의 연관되어 있는 개념들을 순서대로 나열하고 핸드북 형식으로 보다 보면,
모르는 개념을 파악하고 한눈에 보는 것에 있어서 도움이 되지 않을까 싶어 제작하게 되었습니다.
목차는 모던 자바스크립트 deep dive를 기준으로 제작하였고 자세한 정리 내용은
링크 클릭 시에 해당 레포지토리에서 볼 수 있습니다.
질문에 대한 3-5줄 정도의 짧은 길이로 핵심 키워드를 체크하고 헷갈리는 용어들을 반복적으로 보게 됨으로써, 핵심 키워드를 기억할 수 있도록 만드는 것이 목표입니다!
목차
- 프로그래밍이란 뭐라고 생각하나요?
- 컴파일러는 뭐고 인터프리터는 뭔가요? 🔥
- 자바스크립트의 특징은 뭐가 있나요?
- 변수란 무엇인가요?
- 식별자란 무엇인가요? 🔥
- 변수를 선언한다는 것은 어떤 것을 의미하나요?
- var 키워드는 뭔가요?
- 호이스팅이 뭔가요? 🔥🔥
- var 키워드의 문제점은 무엇이 있나요? 🔥
- let 키워드는 var 키워드와 어떤 점이 다른가요? 🔥🔥
- TDZ 🔥🔥
- const 키워드는 어떤 특징이 있나요? 🔥
- 리터럴이 뭔가요?
- 데이터 타입의 종류는 어떤 것들이 있나요? 🔥
- 데이터 타입은 왜 필요할까요? 🔥
- 정적 타이핑이 뭔가요?
- 동적 타이핑이 뭔가요?
- truthy / falsy 한 값이 뭔가요?
- 자바스크립트의 배열은 자료구조의 배열과 같나요?
- 배열의 메서드는 어떤 종류가 있나요?
- 고차 함수에 대해서 아나요?
- forEach 메서드와 map메서드의 차이점에 대해 알고 있나요?
- 자바스크립트에서 객체란 뭘까요?
- 함수와 메서드의 차이점에 대해 알고 계신가요?
- 객체 프로퍼티 접근 시 메모리 동작은 어떻게 되나요? (프로토타입 체인 탐색) 🔥
- 전역 객체에 대해서 아나요?
- 자바스크립트에서 데이터의 타입을 크게 2개로 나누는 이유가 있을까요? 🔥
- 값에 의한 전달이 뭔가요? 🔥
- 참조에 의한 전달이 뭔가요? 🔥
- 함수 선언문과 함수 표현식은 어떤 차이가 있나요?
- 즉시 실행 함수(IIFE)에 대해 알고 있나요? 알고 있다면 아는 내용에 대해 말해보세요
- 스코프가 뭔가요? 🔥🔥
- 스코프에는 어떤 종류가 있죠? 🔥
- 렉시컬 스코프를 아나요? 안다면 렉시컬 스코프는 무엇을 의미하나요? 🔥
- 생성자 함수가 뭔가요?
- 객체 리터럴로 만들 때와는 무슨 차이가 있죠? 왜 생성자 함수를 사용하나요?
- 생성자 함수가 객체(인스턴스)를 생성하는 과정에 대해 간략하게 설명해줄 수 있나요?
- 일급 객체가 뭔가요?
- 자바스크립트에서 함수가 일급 객체라면, 일급 객체로 뭘 할 수 있나요?
- 꼬리 질문) 함수형 프로그래밍이 뭔가요? 🔥
- 꼬리 질문) 순수 함수가 뭔가요? 일반 함수와는 어떤 차이가 있죠? 🔥
- 객체지향 프로그래밍은 무엇을 의미하나요? 🔥
- 객체지향 프로그래밍의 특징에 대해 말해볼 수 있나요? 🔥
- 자바스크립트는 객체지향 프로그래밍 언어인가요?
- 프로토타입이 뭔가요?
- 빌트인 객체가 뭔가요?
- 빌트인 객체의 종류는 어떤게 있죠?
- 래퍼 객체에 대해서 알고 있나요?
- this가 뭔가요? 🔥
- this 바인딩이란? 🔥
- 실행 컨텍스트에 대해 말해보세요 🔥🔥
- 클로저에 대해서 아나요? 🔥🔥
- 클로저를 사용하면 뭐가 좋죠? 🔥
- 클로저를 어떻게 생성하나요? 🔥
- 자바스크립트에서 클래스가 생기기 전에는 어떤 방식으로 객체지향 패턴을 구현했나요?
- 그럼 생성자 함수와 클래스는 어떤 차이가 있나요?
- 클래스 정의
- 클래스의 상속
- 브라우저의 렌더링 과정에 대해 설명해보세요 🔥
- 브라우저의 렌더링 과정에 자바스크립트는 어떻게 동작하나요? 🔥
<script></script>태그를<body></body>태그 밑에 둬야하는 이유가 있을까요?
- DOM이 뭔가요?
- DOM을 구성하는 건 뭐가 있나요?
- 마우스 이벤트 타입에는 뭐가 있나요? click 말고 클릭을 대체할 수 있는 이벤트가 있나요?
- 그 외에 알고 있는 대표적인 이벤트가 있나요?
- 이벤트 전파(propagation)에 대해서 알고 있나요?
- 이벤트 위임(delegation)에 대해서 알고있나요? 🔥
- 호출 스케쥴링이 무엇인가요?
- 타이머 함수에는 어떤 것들이 있나요?
- 이벤트가 과도하게 호출되어 성능에 문제를 일으킬 경우에 할 수 있는 어떤 일을 통해 해결할 수 있나요?
- 디바운스에 대해서 알고 있나요?
- 쓰로틀에 대해서 알고 있나요?
동기와 비동기의 차이점에 대해서 설명해줄 수 있나요? 🔥
- 한줄 요약
이벤트 루프와 태스크 큐에 대해서 알고 있나요? 🔥🔥
마이크로태스크 큐에 대해서 알고 있나요? 🔥
태스크 큐와 마이크로태스크 큐 중 어떤 것이 먼저 실행되나요? 🔥
- REST API가 뭔가요?
- REST API의 구성은 어떤 것이 있나요?
- REST API를 설계하는데 중요한 것이 있을까요?
- HTTP 요청 메서드에 대해서 아는대로 얘기해보세요
- 콜백이란 뭐라고 생각하나요? 🔥
- 프로미스가 뭔가요? 🔥
- 프로미스 생성 방법
- 프로미스의 상태를 나타내는 것은 어떤 것들이 있나요? 🔥
- 제너레이터란 뭔가요? 일반 함수와는 어떤 차이가 있죠?
- 제너레이터의 구조
- async/await 가 뭔가요? 기존의 Promise와는 어떤 차이가 있죠? 🔥
- Promise와 async/await의 차이점 한 줄 요약 🔥
- 에러처리를 왜 해야 하나요?
- 자바스크립트에서 에러를 처리하는 방법에는 뭐가 있을까요?
- 모듈이 뭔가요?
- 자바스크립트의 가비지 컬렉션에 대해 알고 있나요?
프로그래밍
📌 관련 주제: 자바스크립트란
프로그래밍이란 뭐라고 생각하나요?
프로그래밍이란 컴퓨터에게 실행을 요구하는 일종의 커뮤니케이션이다. 해결해야 할 문제(요구사항)를 명확히 이해한 후 적절한 문제 해결 방안을 정의할 필요가 있다.0과 1밖에 알지 못하는 기계가 실행할 수 있을 정도로 정확하고 상세하게 요구를 설명하는 작업이며, 그 결과물이 바로 코드다.
컴파일러는 뭐고 인터프리터는 뭔가요?
- 컴파일러는 소스 코드 전체를 한 번에 기계어로 번역한 후 실행합니다
- 인터프리터는 소스 코드를 한 줄씩 읽어가며 즉시 실행합니다
자바스크립트란
자바스크립트의 특징은 뭐가 있나요?
자바스크립트는 기본적으로 인터프리터(Interpreter) 언어로 분류되지만, 현대적인 엔진(V8 등)에서는 성능 향상을 위해 JIT(Just-In-Time) 컴파일 방식을 혼합하여 사용합니다.
- 컴파일 단계: 실행 직전에 소스 코드를 기계어로 빠르게 컴파일합니다.
- 최적화: 자주 실행되는 코드(Hot code)를 파악하여 실행 중에 더 효율적인 기계어로 다시 최적화합니다.
소스코드 → 파싱 → AST(추상적 구문 트리) → 바이트코드(인터프리터) → 핫 코드 감지 → 기계어(JIT 컴파일)자바스크립트는 단순히 "인터프리터 언어"라고 부르기보다는, JIT 컴파일을 사용하는 동적 언어라고 이해하는 게 더 정확합니다.
변수
📌 관련 주제: 데이터 타입, 스코프, 실행 컨텍스트, 원시 값과 객체 비교
변수란 무엇인가요?
하나의 값을 저장하기 위해 확보한 메모리 공간 자체 또는 그 메모리 공간을 식별하기 위해 붙인 이름.
식별자는 무엇인가요?
변수의 이름을 식별자(identifier)라고도 한다. 메모리 상에 존재하는 어떤 값을 식별할 수 있는 이름. 식별자는 값이 아니라 메모리 주소를 기억하고 있다.
변수를 선언한다는 것은 어떤 것을 의미하나요?
var score;값을 저장하기 위한 메모리 공간을 확보하고 변수 이름과 확보된 메모리 공간의 주소를 연결해서 값을 저장할 수 있게 준비하는 것. 변수를 선언할 때는 var, let, const 키워드를 사용한다.
var 키워드는 뭔가요?
var 키워드는 뒤에 오는 변수 이름을 새로운 변수를 선언할 것을 지시하는 키워드이다. 자바스크립트 엔진은 키워드를 만나면 자신이 수행해야 할 약속된 동작을 수행한다.
변수를 선언한 이후, 아직 변수에 값을 할당하지 않았다. 따라서 변수 선언에 의해 확보된 메모리 공간은 비어 있을 것으로 생각할 수 있으나 확보된 메모리 공간에는 자바스크립트 엔진에 의해 undefined라는 값이 암묵적으로 할당되어 초기화된다. 이것이 자바스크립트의 독특한 특징이다.
호이스팅이 뭔가요?
console.log(score); // undefined;
var score; // 변수 선언문js 엔진은 변수 선언(을 포함한 모든 선언문)이 소스코드의 어디에 있든 상관없이 다른 코드보다 먼저 실행한다. 런타임 이전에 실행 컨텍스트에 의해 소스코드 평가 과정에서 스코프에 등록되고 이를 마치 코드의 제일 위에 있는 것처럼 변수가 어디에 위치하던지와 상관없이 어디서든지 변수를 참조할 수 있는 것처럼 만드는 특징을 변수 호이스팅이라고 합니다.
사실 변수 선언뿐 아니라 var, let, const, function, function*, class 키워드를 사용해서 선언하는 모든 식별자(변수, 함수, 클래스 등)는 호이스팅된다. 모든 선언문은 런타임 이전 단계에서 먼저 실행되기 때문이다.
var 키워드의 문제점은 무엇이 있나요?
var 키워드로 선언된 변수는 다음과 같은 특징이 있다.
- 변수 중복 선언 허용
- 함수 레벨 스코프
- 변수 호이스팅
① 변수 중복 선언 허용
var 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언이 허용되는데, 이는 의도치 않게 변수값이 재할당되어 변경되는 부작용을 발생시킨다.
function foo() {
var x = 1;
// var 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언을 허용한다.
// 아래 변수 선언문은 자바스크립트 엔진에 의해 var 키워드가 없는 것처럼 동작한다.
var x = 2;
console.log(x); // 2
}
foo();② 함수 레벨 스코프
대부분의 프로그래밍 언어는 함수 몸체만이 아니라 모든 코드 블록(if, for, while, try/catch 등)이 지역 스코프를 만든다. 이러한 특성을 블록 레벨 스코프라 한다. 하지만 var 키워드로 선언된 변수는 오로지 함수의 코드 블록(함수 몸체)만을 지역 스코프로 인정한다. 이러한 특성을 함수 레벨 스코프라 한다.
case 1 : var 키워드로 변수 선언
var x = 1;
if (true) {
// var 키워드로 선언된 변수는 함수의 코드 블록(함수 몸체)만을 지역 스코프로 인정한다.
// 함수 밖에서 var 키워드로 선언된 변수는 코드 블록 내에서 선언되었다 할지라도 모두 전역 변수다.
// 따라서 x는 전역 변수다. 이미 선언된 전역 변수 x가 있으므로 x 변수는 중복 선언된다.
// 이는 의도치 않게 변수 값이 변경되는 부작용을 발생시킨다.
var x = 10;
}
console.log(x); // 10
case 2 : var 키워드로 for문 안의 변수 선언
var i = 10;
// for 문에서 선언한 i는 전역 변수다. 이미 선언된 전역 변수 i가 있으므로 중복 선언된다.
for (var i = 0; i < 5; i++) {
console.log(i); // 0 1 2 3 4
}
// 의도치 않게 변수의 값이 변경되었다.
console.log(i); // 5③ 변수 호이스팅
var 키워드로 선언된 변수는 선언과 동시에 undefined로 초기화되며, 런타임 즉 소스코드 평가 단계에서 스코프에 등록되기 때문에 실행 단계에서 실제 값이 할당되지 않더라도 undefined를 가지고있다. 이를 변수 호이스팅이라 한다.
console.log(score); // undefined;
var score; // 변수 선언문let 키워드는 var 키워드와 어떤 점이 다른가요?
let 키워드는 var 키워드의 단점을 보완하기 위해 ES6에서 도입된 새로운 키워드이다.
1. 변수 중복 선언 금지
2. 블록 레벨 스코프
3. 변수 호이스팅
4. 전역 객체와 let1. 변수 중복 선언 금지
let 키워드로 이름이 같은 변수를 중복 선언하면 문법 에러(SyntaxError)가 발생한다.
let bar = 123;
// let이나 const 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언을 허용하지 않는다.
let bar = 456; // SyntaxError: Identifier 'bar' has already been declared2. 블록 레벨 스코프
let 키워드를 통해 선언된 변수는 블록 레벨 스코프를 따른다. 함수 뿐만 아니라 모든 코드 블록 내에 선언된 변수(지역 변수)는 해당 유효 범위(스코프)를 벗어나면 사용할 수 없다.
let foo = 1; // 전역 변수
{
let foo = 2; // 지역 변수
let bar = 3; // 지역 변수
}
console.log(foo); // 1
console.log(bar); // ReferenceError: bar is not defined3. 변수 호이스팅
var 키워드로 선언한 변수와 달리 let 키워드로 선언한 변수는 변수 호이스팅이 발생하지 않는 것처럼 동작한다.
console.log(foo); // Cannot access 'foo' before initialization
let foo;let 키워드로 선언한 변수는 '선언 단계'와 '초기화 단계'가 분리되어 진행된다. 즉, 런타임 이전에 자바스크립트 엔진에 의해 암묵적으로 선언 단계가 먼저 실행되지만 초기화 단계는 변수 선언문에 도달했을 때 실행된다. 만약 초기화 단계가 실행되기 이전에 변수에 접근하려고 하면 참조 에러가 발생한다.
let 키워드로 선언한 변수는 스코프의 시작 지점부터 초기화 단계 시작 지점(변수 선언문)까지 변수를 참조할 수 없다. 스코프의 시작 지점부터 초기화 시작 지점까지 변수를 참조할 수 없는 구간을 일시적 사각지대(TDZ: Temporal Dead Zone) 라 부른다.

// 런타임 이전에 선언 단계가 실행된다. 아직 변수가 초기화되지 않았다.
// 초기화 이전의 일시적 사각 지대에서는 변수를 참조할 수 없다.
console.log(foo); // ReferenceError: foo is not defined
let foo; // 변수 선언문에서 초기화 단계가 실행된다.
console.log(foo); // undefined
foo = 1; // 할당문에서 할당 단계가 실행된다.
console.log(foo); // 14. 전역 객체와 let
let 키워드로 선언한 전역 변수는 전역 객체의 프로퍼티가 아니다. 즉, window.foo와 같이 접근할 수 없다.
let x = 1;
// let, const 키워드로 선언한 전역 변수는 전역 객체 window의 프로퍼티가 아니다.
console.log(window.x); // undefined
console.log(x); // 1const 키워드는 어떤 특징이 있나요?
const 키워드는 상수(constant)를 선언하기 위해 사용하지만, 반드시 상수만을 위해 사용하지는 않는다. const 키워드의 특징은 let과 대부분 동일하므로 let 키워드와 다른 점을 중심으로 살펴볼 필요가 있다.
1. 선언과 초기화
2. 재할당 금지
3. 상수1. 선언과 초기화
const 키워드로 선언한 변수는 반드시 선언과 동시에 초기화해야 한다. 그렇지 않을 경우 문법 에러(SyntaxError)가 발생한다.
const bar = 1;
console.log(bar); >>> 1
const foo;
console.log(foo); >>> // SyntaxError: Missing initializer in const declaration2. 재할당 금지
var 또는 let 키워드로 선언한 변수는 재할당이 자유로우나 const 키워드로 선언한 변수는 재할당이 금지된다.
const foo = 1;
foo = 2; // TypeError: Assignment to constant variable.3. 상수
const 키워드로 선언한 변수에 원시 값을 할당한 경우 변수 값을 변경할 수 없다. 원시 값은 변경 불가능한 값이므로 재할당 없이 값을 변경할 수 있는 방법이 없기 때문이다. 이러한 특징을 이용해 const 키워드를 상수를 표현하는 데 사용하기도 한다.
// 세율을 의미하는 0.1은 변경할 수 없는 상수로서 사용될 값이다.
// 변수 이름을 대문자로 선언해 상수임을 명확히 나타낸다.
const TAX_RATE = 0.1;
// 세전 가격
let preTaxPrice = 100;
// 세후 가격
let afterTaxPrice = preTaxPrice + preTaxPrice * TAX_RATE;
console.log(afterTaxPrice); // 110한 줄 요약
| var 키워드 | let 키워드 | const 키워드 |
|---|---|---|
| 선언 및 초기화 단계(undefined) | 선언 단계 | 선언 + 초기화 + 할당 |
| 할당 단계 | 초기화 단계 | |
| - | 할당 단계 |
리터럴이 뭔가요?
리터럴(literal)은 사람이 이해할 수 있는 문자 또는 약속된 기호를 사용해 값을 생성하는 표기법(=notation) 을 말합니다.
<!-- 숫자 리터럴 3 -->
3위 예제의 3은 단순한 아라비아 숫자가 아니라 숫자 리터럴이다. 사람이 이해할 수 있는 아라비아 숫자를 사용해 숫자 리터럴 3을 코드에 기술하면 자바스크립트 엔진은 이를 평가해 숫자 값 3을 생성한다. 자바스크립트 엔진은 코드가 실행되는 시점인 런타임에 리터럴을 평가해 값을 생성한다.
데이터 타입
📌 관련 주제: 변수, 타입변환과 단축 평가, 원시 값과 객체 비교
데이터 타입의 종류는 어떤 것들이 있나요?
| 구분 | 데이터 타입 | 설명 |
|---|---|---|
| 원시 타입 | 숫자(number)타입 | 숫자, 정수와 실수 구분 없이 하나의 숫자 타입만 존재 |
| 원시 타입 | 문자열(string)타입 | 문자열 |
| 원시 타입 | 불리언(boolean)타입 | 논리적 참(true)과 거짓(false) |
| 원시 타입 | undefined타입 | var 키워드로 선언된 변수에 암묵적으로 할당되는 값 |
| 원시 타입 | null 타입 | 값이 없다는 것을 의도적으로 명시할 때 사용하는 값 |
| 원시 타입 | 심벌(symbol) 타입 | ES6에서 추가된 7번째 타입 |
| 원시 타입 | BigInt 타입 | 길이의 제약 없이 정수를 다룰 수 있게 해주는 숫자형 |
| 객체 타입 | 객체, 함수, 배열 등 |
BigInt 타입 레퍼런스
데이터 타입은 왜 필요할까요?
- 값을 저장할 때 확보해야 하는 메모리 공간의 크기를 결정하기 위해
- 값을 참조할 때 한 번에 읽어 들여야 할 메모리 공간의 크기를 결정하기 위해
- 메모리에서 읽어 들인 2진수를 어떻게 해석할지 결정하기 위해
정적 타이핑이 뭔가요?
C나 자바 같은 정적 타입언어는 변수를 선언할 때 변수에 할당할 수 있는 값의 종류, 즉 데이터 타입을 사전에 선언해야 한다. 이를 명시적 타입 선언이라 한다.
정적 타입 언어는 변수의 타입을 변경할 수 없으며, 변수에 선언한 타입에 맞는 값만 할당할 수 있다. 정적 타입 언어는 컴파일 시점에서 타입 체크를 수행한다. 만약 타입 체크를 통과하지 못했다면 에러를 발생시키고 프로그램의 실행 자체를 막는다.
동적 타이핑이 뭔가요?
자바스크립트는 정적 타입 언어와 다르게 변수를 선언할 때 타입을 선언하지 않는다. 다만 var, let, const 키워들 사용해 변수를 선언할 뿐이다.
var foo;
console.log(typeof foo); // undefined
foo = 3;
console.log(typeof foo); // number
foo = null;
console.log(typeof foo); // object
foo = Symbol(); // 심벌
console.log(typeof foo); // symbol
foo = {}; // 객체
console.log(typeof foo); // object
foo = []; // 배열
console.log(typeof foo); // object
foo = function () {}; // 함수
console.log(typeof foo); // function자바스크립트의 변수는 선언이 아닌 할당에 의해 타입이 결정 (타입 추론) 된다. 그리고 재할당에 의해 변수의 타입은 언제든지 동적으로 변할 수 있다. 이러한 특징을 동적 타이핑이라고 하며, 자바스크립트를 정적 타입 언어와 구별하기 위해 동적 타입 언어라고 한다.
타입변환과 단축 평가
📌 관련 주제: 데이터 타입
truthy / falsy 한 값이 뭔가요?
자바스크립트 엔진은 불리언 타입이 아닌 값을 Truthy 값(참으로 평가되는 값) 또는 Falsy 값(거짓으로 평가되는 값)으로 구분한다. 즉, 제어문의 조건식과 같이 불리언 값으로 평가되어야 할 문맥에서 Truthy값은 true로, Falsy값은 false로 암묵적 타입 변환된다.
아래 값들은 false로 평가되는 Falsy 값이다.
false
undefined
null
0, -0
NaN
' '(빈 문자열)Falsy값에 ! 연산자를 붙이면, 모두 Truthy 값으로 평가되어 실행 가능해진다.
// 아래의 조건문은 모두 코드 블록을 실행한다.
if (!false) console.log(false + ' is falsy value');
if (!undefined) console.log(undefined + ' is falsy value');
if (!null) console.log(null + ' is falsy value');
if (!0) console.log(0 + ' is falsy value');
if (!NaN) console.log(NaN + ' is falsy value');
if (!'') console.log('' + ' is falsy value');배열
📌 관련 주제: 객체 리터럴, 함수와 일급 객체, Map과 Set 그리고 Lookup Table
자바스크립트의 배열은 자료구조의 배열과 같나요?
자료구조에서 말하는 배열은 동일한 크기의 메모리 공간이 빈틈없이 연속적으로 나열된 자료구조를 말한다.
하나의 데이터 타입으로 통일되어 있으며 서로 연속적으로 인접해 있다. 이러한 배열은 밀집 배열(dense array)이라 한다.
자바스크립트의 배열은 자료구조에서 말하는 일반적인 의미의 배열과 다르다.
배열의 요소를 위한 각각의 메모리 공간은 동일한 크기를 갖지 않아도 되며, 연속적으로 이어져 있지 않을 수도 있다.
배열의 요소가 연속적으로 이어져 있지 않는 배열을 희소 배열(sparse array)이라 한다.
이처럼 자바스크립트의 배열(희소 배열)은 엄밀히 말해 일반적인 의미의 배열이 아니다. 자바스크립트의 배열은 일반적인 배열의 동작을 흉내 낸 특수한 객체다.
배열의 메서드는 어떤 종류가 있나요?
배열은 크게 두 종류로 나눌 수 있다
① 원본 배열을 직접 변경하는 메서드② 원본 배열을 직접 변경하지 않고 새로운 배열을 생성하여 반환하는 메서드가 있다.
ES5부터 도입된 배열 메서드는 대부분 원본 배열을 직접 변경하지 않지만 초창기 배열 메서드는 원본 배열을 직접 변경하는 경우가 많다.
원본 배열을 직접 변경하는 메서드는 외부 상태를 직접 변경하는 부수효과가 있으므로 사용할 때 주의해야 한다.
가급적 원본 배열을 직접 변경하지 않는 메서드를 사용하는 편이 좋다.
Array.isArray 🌟
Array.prototype.indexOf 🌟
Array.prototype.push (원본 배열을 변경한다 - 부수효과 o)
Array.prototype.pop (원본 배열을 변경한다 - 부수효과 o)
Array.prototype.unshift (원본 배열을 변경한다 - 부수효과 o)
Array.prototype.shift (원본 배열을 변경한다 - 부수효과 o)
Array.prototype.concat 🌟
Array.prototype.splice 🌟 (원본 배열을 변경한다 - 부수효과 o)
Array.prototype.slice 🌟
Array.prototype.join 🌟 (원본 배열을 변경한다 - 부수효과 o)
Array.prototype.reverse 🌟 (원본 배열을 변경한다 - 부수효과 o)
Array.prototype.fill 🌟 (원본 배열을 변경한다 - 부수효과 o)
Array.prototype.includes 🌟고차 함수에 대해서 아나요?
고차 함수(Higher-Order Function, HOF)는 함수를 인수로 전달받거나 함수를 반환하는 함수를 말한다.
자바스크립트의 함수는 일급 객체이므로 함수를 값처럼 인수로 전달할 수 있으며 반환할 수도 있다.
고차 함수는 외부 상태의 변경이나 가변(mutable) 데이터를 피하고 불변성(immutability)을 지향하는 함수형 프로그래밍에 기반을 두고 있다.
함수형 프로그래밍은 순수 함수와 보조 함수의 조합을 통해 로직 내에 존재하는 조건문과 반복문을 제거하여 복잡성을 해결하고 변수의 사용을 억제하여 상태 변경을 피하려는 프로그래밍 패러다임이다.
함수형 프로그래밍은 순수 함수를 통해 부수 효과를 최대한 억제하여 오류를 피하고 프로그램의 안정성을 높이려는 노력의 일환이라고 할 수 있다.
대부분의 고차 함수들은 파라미터로 콜백 함수를 받아 사용되기 때문에 원본 배열을 바탕으로 하는 새로운 결과값을 창조하는데 사용된다
Array.prototype.sort (원본 배열을 변경한다 - 부수효과 o)
Array.prototype.forEach
Array.prototype.map
Array.prototype.filter
Array.prototype.reduce
Array.prototype.some
Array.prototype.every
Array.prototype.findforEach 메서드와 map메서드의 차이점에 대해 알고 있나요?
forEach()
①forEach 메서드는 언제나 undefined를 반환하고 ②map 메서드는 콜백 함수의 반환값들로 구성된 새로운 배열을 반환한다는 것이다.
즉 forEach 메서드는 단순히 반복문을 대체하기 위한 고차 함수이고 Map 메서드는 요소값을 다른 값으로 매핑한 새로운 배열을 생성하기 위한 고차 함수다.
객체 리터럴
📌 관련 주제: 원시 값과 객체 비교, 생성자 함수에 의한 객체 생성, 프로토타입, 클래스
자바스크립트에서 객체란 뭘까요?
자바스크립트는 객체 기반의 프로그래밍 언어이며, 자바스크립트를 구성하는 거의 '모든 것'이 객체다. 원시 값을 제외한 나머지 값(함수, 배열, 정규 표현식 등)은 모두 객체이다. 원시 타입은 단 하나의 값만 나타내지만 객체 타입은 다양한 타입의 값(원시 값 또는 다른 객체)을 하나의 단위로 구성한 복합적인 자료구조이다. 또한 원시 타입의 값, 즉 원시 값은 변경 불가능한 값이지만 객체 타입의 값, 즉 객체는 변경 가능한 값이다.
객체는 0개 이상의 프로퍼티로 구성된 집합이며, 프로퍼티는 키(key)와 값(value)으로 구성된다.
var person = {
name: 'Lee',
age: 20,
};함수와 메서드의 차이점에 대해 알고 계신가요?
자바스크립트에서 사용할 수 있는 모든 값은 프로퍼티 값이 될 수 있다. 프로퍼티 값이 함수일 경우, 일반 함수와 구분하기 위해 메서드(method)라 부른다. 객체 내부에서 객체의 프로퍼티(상태)를 참조하고 조작할 수 있는 동작을 메서드라 부른다.
즉, 메서드는 객체에 묶여 있는 함수를 의미한다.
var person = {
name: "Lee",
age: 20,
hello: function () {
console.log("hello :" + this.name);
},
};
console.log(person);
>>>
{ name: 'Lee', age: 20, hello: [Function: hello] }객체 프로퍼티 접근 시 메모리 동작은 어떻게 되나요? (프로토타입 체인 탐색) 🔥
person.name에 접근할 때와 person.hobby (없는 프로퍼티)에 접근하려고 할 때는 어떻게 동작하나요?
let person = { name: 'junhee', age: 29 };메모리 구조
let person = { name: 'junhee', age: 29 }가 실행되면:
- 스택 메모리:
person변수가 저장되며, 객체의 힙 메모리 주소(참조값)를 가짐 - 힙 메모리: 실제 객체가 저장됨
name프로퍼티와 값'junhee'age프로퍼티와 값29- 내부적으로 프로토타입 체인 연결 정보
1. 존재하는 프로퍼티 접근 (person.name)
console.log(person.name); // 'junhee'동작 과정:
- 참조 해소: 스택의
person변수에서 힙 메모리 주소를 가져옴 - 프로퍼티 탐색: 해당 주소의 객체에서
name프로퍼티 검색 - 직접 접근: 객체의 프로퍼티 맵(Hidden Class/Shape)에서
name의 오프셋 확인 - 값 반환: 해당 위치의 값
'junhee'반환
V8 엔진의 경우 Hidden Class를 사용해 프로퍼티 위치를 캐싱하므로, 반복 접근 시 빠른 조회가 가능하다.
2. 존재하지 않는 프로퍼티 접근 (person.hobby)
console.log(person.hobby); // undefined동작 과정:
- 참조 해소: 스택의
person변수에서 힙 메모리 주소를 가져옴 - 프로퍼티 탐색: 객체에서
hobby프로퍼티 검색 → 없음 - 프로토타입 체인 탐색:
person.__proto__(Object.prototype)로 이동- Object.prototype에서
hobby검색 → 없음 - Object.prototype의 proto는 null이므로 탐색 종료
- undefined 반환: 프로퍼티를 찾지 못했으므로
undefined반환
메모리 측면의 차이점:
- 추가 메모리 할당 없음:
undefined를 반환하지만, 객체에 새로운 프로퍼티가 생성되지는 않음 - 프로토타입 체인 순회: 존재하는 프로퍼티보다 탐색 비용이 더 큼 (여러 객체를 순회)
- 캐시 미스: Hidden Class에 해당 프로퍼티 정보가 없어 최적화가 어려움
존재하지 않는 프로퍼티에 접근해도 에러가 발생하지 않고 undefined를 반환하는 것이 자바스크립트의 특징이며, 이는 프로토타입 체인 탐색 메커니즘 덕분이다.
전역 객체에 대해서 아나요?
- 전역 객체는 코드가 실행되기(런타임) 이전 단계에 자바스크립트 엔진에 의해 생성되는 특수한 객체다.
- 클라이언트 사이드 환경(브라우저)에서는 window, 서버 사이드 환경(Node.js)에서는 global 객체를 의미한다.
- 전역 객체는 표준 빌트인 객체(Object, String, Number, Function, Array...) 들과 환경에 따른 호스트 객체, 그리고 var 키워드로 선언한 전역 변수와 전역 함수를 프로퍼티로 갖는다.
원시 값과 객체 비교
자바스크립트에서 데이터의 타입을 크게 2개로 나누는 이유가 있을까요?
원시 타입의 값, 즉 원시 값은 변경 불가능한 값(immutable value)이다. 이에 비해 객체(참조)타입의 값, 즉 객체는 변경 가능한 값(mutable value)이다.
원시 값을 변수에 할당하면 변수(확보된 메모리 공간)에는 실제 값(100, 실제로는 2진수)이 저장된다. 이에 비해 객체를 변수에 할당하면 변수(확보된 메모리 공간)에는 참조 값(메모리 주소, 0x00000613)이 저장된다.
원시 값을 갖는 변수를 다른 변수에 할당하면 원본의 원시 값이 복사되어 전달된다. 이를 값에 의한 전달이라 한다. 이에 비해 객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값(메모리 주소, 0x00000613)이 복사되어 전달된다. 이를 참조에 의한 전달이라 한다.
값에 의한 전달이 뭔가요?
변수에 원시 값을 갖는 변수를 할당하면 할당받는 변수(copy)에는 할당하는 변수(score)의 원시 값이 복사되어 전달된다. 이를 '값에 의한 전달' 이라 한다.
var score = 80;
// copy 변수에는 score 변수의 값 80이 복사되어 할당된다.
var copy = score;
console.log(score, copy); // 80 80
console.log(score === copy); // true
// score 변수와 copy 변수의 값은 다른 메모리 공간에 저장된 별개의 값이다.
// 따라서 score 변수의 값을 변경해도 copy 변수의 값에는 어떠한 영향도 주지 않는다.
score = 100;
console.log(score, copy); // 100 80
console.log(score === copy); // falsescore 변수와 copy 변수의 값 80은 다른 메모리 공간에 저장된 별개의 값이라는 것에 주의하기 바란다. 따라서 score 변수의 값을 변경해도 copy 변수의 값에는 어떠한 영향도 주지 않는다.

참조에 의한 전달이 뭔가요?
객체는 프로퍼티의 개수가 정해져 있지 않으며, 동적으로 추가되고 삭제할 수 있다. 또한 프로퍼티의 값에도 제약이 없다. 따라서 객체는 원시 값(문자열 2바이트*문자, 숫자 8바이트)과 같이 확보해야 할 메모리 공간의 크기를 사전에 정해 둘 수 없다. 이러한 객체의 가변성의 성질 때문에 객체를 할당한 변수가 기억하는 메모리 주소를 통해 메모리 공간에 접근하면 참조 값에 접근할 수 있다. 참조 값은 생성된 객체가 저장된 메모리 공간의 주소, 그 자체이다.
var person = {
name: 'Lee',
};
// 참조값을 복사(얕은 복사)
var copy = person;객체를 가리키는 변수(원본, person)를 다른 변수(사본, copy)에 할당하면 원본의 참조 값이 복사되어 전달된다. 이를 '참조에 의한 전달' 이라 한다.

함수
📌 관련 주제: 스코프, 클로저, 실행 컨텍스트, 함수와 일급 객체, this
함수 선언문과 함수 표현식은 어떤 차이가 있나요?
// 함수 참조
console.dir(add); // ƒ add(x, y)
console.dir(sub); // undefined
// 함수 호출
console.log(add(2, 5));
// 7 why? 함수 선언문은 표현식이 아닌 문으로, 런타임 이전에 js 엔진에 의해 실행된다.
console.log(sub(2, 5));
// TypeError: sub is not a function, why? 함수 표현식(표현식인 문)은 런타임에 값을 할당하기 때문에 sub는 현재 undefined로만 초기화된 상태이다.
// ① 함수 선언문
function add(x, y) {
return x + y;
}
// ② 함수 표현식
var sub = function (x, y) {
return x - y;
};코드가 한 줄씩 순차적으로 실행되기 시작하는 런타임에는 이미 함수 객체가 생성되어 있고 함수 이름과 동일한 식별자에 할당까지 완료된 상태이다. 따라서 함수 선언문의 소스코드가 평가되고 실행되기 이전에 함수를 참조할 수 있으며 호출할 수도 있다. 이처럼 함수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트 고유의 특징을 함수 호이스팅이라 한다.
변수 호이스팅에 의해 var 키워드로 선언된 함수 표현식은 undefined로 초기화되고, 함수 선언문을 통해 암묵적으로 생성된 식별자는 함수 객체로 초기화된다. 따라서 var 키워드를 사용한 변수 선언문은 이전에 변수를 참조하면 변수 호이스팅에 의해 undefined로 평가되지만, 함수 선언문으로 정의한 함수를 함수 선언문 이전에 호출하면 함수 호이스팅에 의해 호출이 가능하다.
즉시 실행 함수(IIFE)에 대해 알고 있나요? 알고 있다면 아는 내용에 대해 말해보세요
- 함수 정의와 동시에 즉시 호출되는 함수를 즉시 실행 함수 (IIFE, Immediately Invoked Function Expression) 라고 한다. 즉시 실행 함수는 단 한 번만 호출되며 다시 호출할 수 없다.
//익명 즉시 실행함수
(function () {
var a = 3;
var b = 5;
return a * b;
})();- 즉시 실행 함수는 함수 이름이 없는 익명 함수를 사용하는 것이 일반적이다. 함수 이름이 있는 기명 즉시 실행 함수도 사용할 수 있다. 하지만 즉시 실행 함수를 다시 호출할 수는 없다.
//기명 즉시 실행 함수
(function foo() {
var a = 3;
var b = 5;
return a * b;
})();
foo(); //ReferenceError: foo is not defined- 즉시 실행 함수는 반드시 그룹 연산자 (...)로 감싸야 한다.
function () {
//SyntaxError: Function statements require a function name
...
}함수 정의가 함수 선언문의 형식에 맞지 않기 때문이다. > 그룹 연산자로 함수를 묶은 이유는 먼저 함수 리터럴을 평가해서 함수 객체를 생성하기 위해서다
스코프
📌 관련 주제: 변수, 실행 컨텍스트, 클로저, 함수, 모듈
스코프가 뭔가요?
스코프는 유효 범위라는 뜻으로, 식별자(변수)가 유효한 범위를 말합니다.
자바스크립트 엔진은 스코프를 통해 어떤 변수를 참조해야 할 것인지 결정한다. 따라서 스코프란 자바스크립트 엔진이 식별자를 검색할 때 사용하는 규칙이라고도 할 수 있다.
스코프에는 어떤 종류가 있죠?
스코프는 크게 전역 스코프와 지역 스코프로 구분됩니다.
이는 상대적인 개념이며 전역이란 코드의 가장 바깥 영역을 말합니다. 전역에 변수를 선언하면 전역 스코프를 갖는 전역 변수가 됩니다. 이 전역 변수는 어디서든 참조할 수 있습니다.
지역이란 함수 몸체 내부를 말합니다. 지역은 지역 스코프를 만드는데, 지역에 변수를 선언하면 지역 스코프를 갖는 지역 변수가 됩니다. 지역 변수는 자신의 스코프와 하위 지역 스코프에서 유효합니다.
렉시컬 스코프를 아나요? 안다면 렉시컬 스코프는 무엇을 의미하나요?
함수를 어디서 '호출' 했는지가 아닌 어디서 '정의' 했는지에 따라 함수의 상위 스코프를 결정하는 것이 정적 스코프 즉, 렉시컬 스코프를 의미합니다.
function outer() {
let outerVar = 'I am from outer';
function inner() {
let innerVar = 'I am from inner';
console.log(outerVar); // 접근 가능
console.log(innerVar); // 접근 가능
}
inner();
console.log(outerVar); // 접근 가능
// console.log(innerVar); // 접근 불가, ReferenceError 발생
}
outer();위 코드에서 inner 함수는 outer 함수 내부에 정의되어 있습니다. 자바스크립트는 렉시컬 스코핑을 사용하여 스코프 체인을 구축합니다. 이는 다음과 같은 스코프 체인을 생성합니다:
- Global Scope: 전역 변수와 함수가 정의되는 스코프
- Outer Function Scope: outer 함수 내에서 정의된 변수 outerVar가 있는 스코프
- Inner Function Scope: inner 함수 내에서 정의된 변수 innerVar가 있는 스코프
inner 함수는 자신을 둘러싼 외부 함수 outer의 스코프를 렉시컬하게 기억합니다. 따라서 inner 함수 내부에서는 outer 함수의 변수인 outerVar에 접근할 수 있습니다. 그러나 outer 함수는 inner 함수 내부의 변수인 innerVar에 접근할 수 없습니다.
생성자 함수에 의한 객체 생성
📌 관련 주제: 객체 리터럴, 프로토타입, 클래스, this
생성자 함수가 뭔가요?
생성자 함수란 new 연산자와 함께 호출하여 객체를 생성하는 함수를 말한다. 생성자 함수에 의해 생성된 객체를 인스턴스라고 한다.
자바스크립트는 Object 생성자 함수 이외에도 String, Number, Boolean, Function, Array, Date, RegExp, Promise 등의 빌트인 생성자 함수를 제공한다.
생성자 함수는 객체를 생성하기 위해 사용되므로, 반드시 Object 생성자 함수를 사용해 객체를 생성해야 하는 것은 아니다. (객체 리터럴을 통해서도 만들 수 있기 때문)
객체 리터럴로 만들 때와는 무슨 차이가 있죠? 왜 생성자 함수를 사용하나요?
객체 리터럴에 의한 객체 생성 방식은 단 하나의 객체만 생성하기 때문에 같은 프로퍼티를 갖는 객체를 여러 개 생성해야 하는 경우 비효율적이다.
생성자 함수를 통해 객체를 생성한다면 마치 객체를 생성하기 위한 템플릿처럼 생성자 함수를 사용하여 프로퍼티 구조가 동일한 객체 여러 개를 간편하게 생성할 수 있다.
// 생성자 함수 (일반 함수와 동일한 방식으로 만들어지는 선언문이다)
function Circle(radius) {
// 생성자 함수 내부의 this는 생성자 함수가 생성할 인스턴스를 가리킨다.
this.radius = radius; // ex) circle1의 radius에 radius(5)를 할당해줘
this.getDiameter = function () {
return 2 * this.radius;
};
}
// 인스턴스의 생성
const circle1 = new Circle(5); // 반지름이 5인 Circle 객체를 생성
const circle2 = new Circle(10); // 반지름이 10인 Circle 객체를 생성
console.log(circle1.getDiameter()); // 10
console.log(circle2.getDiameter()); // 20생성자 함수가 객체(인스턴스)를 생성하는 과정에 대해 간략하게 설명해줄 수 있나요?
- 생성자 함수 선언
- 인스턴스 생성
- 인스턴스 초기화
- 인스턴스 반환
// 1. 생성자 함수 선언
function Circle(radius) {
// 3. 인스턴스 초기화
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
// 4. 인스턴스 생성시에 생성자 함수를 호출할 때 넣은 인수를 this 바인딩을 통해 프로퍼티에 할당한 뒤, 인스턴스를 반환한다
}
// 2. 인스턴스 생성
const circle1 = new Circle(5); // 반지름이 5인 Circle 객체를 생성함수와 일급 객체
일급 객체가 뭔가요?
다음과 같은 조건을 만족하는 객체 를 일급 객체 라 한다.
- 무명의 리터럴로 생성할 수 있다. (함수 이름 없이)
- 변수나 자료구조(객체, 배열 등)에 저장할 수 있다.
- 함수의 매개변수에 전달할 수 있다.
- 함수의 반환 값으로 사용할 수 있다.
// 1. 함수는 무명의 리터럴로 생성할 수 있다.
// 2. 함수는 변수에 저장할 수 있다.
const increase = function (num) {
return ++num;
};
const decrease = function (num) {
return --num;
};
// 2. 함수는 객체에 저장할 수 있다.
const predicates = { increase, decrease };
console.log(predicates); // predicates: { increase: [Function: increase], decrease: [Function: decrease] }
// 3. 함수의 매개변수에게 전달할 수 있다.
// 4. 함수의 반환값으로 사용할 수 있다.
function makeCounter(predicate) {
let num = 0;
return function () {
num = predicate(num);
return num;
};
}
// 3. 함수는 매개변수에게 함수를 전달할 수 있다.
const increaser = makeCounter(predicates.increase);
console.log(increaser()); // 1
console.log(increaser()); // 2
// 3. 함수는 매개변수에게 함수를 전달할 수 있다.
const decreaser = makeCounter(predicates.decrease);
console.log(decreaser()); // -1
console.log(decreaser()); // -2자바스크립트에서 함수가 일급 객체라면, 일급 객체로 뭘 할 수 있나요?
일급 객체로서 함수가 가지는 가장 큰 특징은 일반 객체와 같이 함수의 매개변수에 전달할 수 있으며, 함수의 반환값으로 사용할 수도 있다는 것이다. 이는 함수형 프로그래밍을 가능케 하는 자바스크립트의 장점 중 하나이다.
꼬리 질문) 함수형 프로그래밍이 뭔가요?
외부 상태를 변경하지 않고 외부 상태에 의존하지도 않는 함수를 순수 함수 라 한다. 순수 함수를 통해 부수효과를 최대한 억제하여 오류를 피하고 프로그램의 안전성을 높이려는 프로그래밍 패러다임 을 함수형 프로그래밍 이라 한다.
꼬리 질문) 순수 함수가 뭔가요? 일반 함수와는 어떤 차이가 있죠?
- 순수 함수: 어떤 외부 상태에 의존하지도 않고 변경하지도 않는, 즉 부수 효과가 없는 함수를 순수 함수라 한다.
- 비순수 함수: 외부 상태에 의존하거나 외부 상태를 변경하는, 즉 부수 효과가 있는 함수를 비순수 함수라고 한다.
Map과 Set 그리고 Lookup Table
맵
맵(Map)은 키가 있는 데이터를 저장한다는 점에서 객체와 유사합니다. 다만, 맵은 키에 다양한 자료형을 허용한다는 점에서 차이가 있습니다.
맵에는 다음과 같은 주요 메서드와 프로퍼티가 있습니다.
- new Map(): 맵을 만듭니다.
- map.set(key, value): key를 이용해 value를 저장합니다.
- map.get(key): key에 해당하는 값을 반환합니다. key가 존재하지 않으면 undefined를 반환합니다.
- map.has(key): key가 존재하면 true, 존재하지 않으면 false를 반환합니다.
- map.delete(key): key에 해당하는 값을 삭제합니다.
- map.clear(): 맵 안의 모든 요소를 제거합니다.
- map.size: 요소의 개수를 반환합니다. 예시:
let map = new Map();
map.set('1', 'str1'); // 문자형 키
map.set(1, 'num1'); // 숫자형 키
map.set(true, 'bool1'); // 불린형 키
// 객체는 키를 문자형으로 변환한다는 걸 기억하고 계신가요?
// 맵은 키의 타입을 변환시키지 않고 그대로 유지합니다. 따라서 아래의 코드는 출력되는 값이 다릅니다.
alert(map.get(1)); // 'num1'
alert(map.get('1')); // 'str1'
alert(map.size); // 3
// 맵은 객체와 달리 키를 문자형으로 변환하지 않습니다. 키엔 자료형 제약이 없습니다.셋
셋(Set)은 중복을 허용하지 않는 값을 모아놓은 특별한 컬렉션입니다. 셋에 키가 없는 값이 저장됩니다.
주요 메서드는 다음과 같습니다.
- new Set(iterable): 셋을 만듭니다. 이터러블 객체를 전달받으면(대개 배열을 전달받음) 그 안의 값을 복사해 셋에 넣어줍니다.
- set.add(value): 값을 추가하고 셋 자신을 반환합니다.
- set.delete(value): 값을 제거합니다. 호출 시점에 셋 내에 값이 있어서 제거에 성공하면 true, 아니면 false를 반환합니다.
- set.has(value): 셋 내에 값이 존재하면 true, 아니면 false를 반환합니다.
- set.clear(): 셋을 비웁니다.
- set.size: 셋에 몇 개의 값이 있는지 세줍니다.
셋 내에 동일한 값(value)이 있다면 set.add(value)을 아무리 많이 호출하더라도 아무런 반응이 없을 겁니다. 셋 내의 값에 중복이 없는 이유가 바로 이 때문이죠.
방문자 방명록을 만든다고 가정해 봅시다. 한 방문자가 여러 번 방문해도 방문자를 중복해서 기록하지 않겠다고 결정 내린 상황입니다. 즉, 한 방문자는 '단 한 번만 기록’되어야 합니다.
이때 적합한 자료구조가 바로 셋입니다.
let set = new Set();
let john = { name: 'John' };
let pete = { name: 'Pete' };
let mary = { name: 'Mary' };
// 어떤 고객(john, mary)은 여러 번 방문할 수 있습니다.
set.add(john);
set.add(pete);
set.add(mary);
set.add(john);
set.add(mary);
// 셋에는 유일무이한 값만 저장됩니다.
alert(set.size); // 3
for (let user of set) {
alert(user.name); // // John, Pete, Mary 순으로 출력됩니다.
}룩업 테이블
룩업 테이블(Lookup Table) 구조를 사용하는 것도 객체의 중복 키 값을 제거하는 좋은 방법입니다. 이 방법은 특히 대량의 데이터에서 중복 제거를 수행할 때 효율적입니다. 룩업 테이블은 키-값 쌍을 저장하는 자료구조로, 검색 속도가 매우 빠른 것이 장점입니다. 객체에서 중복 키 값 제거를 위해 룩업 테이블을 사용하는 방법은 다음과 같습니다:
날짜 별로 메시지 데이터를 나눌 때, 룩업 테이블을 사용합니다.
interface Data {
date: string; // 'YYYY-MM-DD' 형식의 날짜 문자열
value: any; // 데이터 값
}
const groupByDate = (data: Data[]): Record<string, Data[]> => {
const lookupTable: Record<string, Data[]> = {};
for (const item of data) {
const { date, value } = item;
if (!lookupTable[date]) {
lookupTable[date] = [];
}
lookupTable[date].push({ date, value });
}
return lookupTable;
};
// 사용 예시
const data: Data[] = [
{ date: '2023-06-01', value: 10 },
{ date: '2023-06-02', value: 20 },
{ date: '2023-06-01', value: 30 },
{ date: '2023-06-03', value: 40 },
];
const lookupTable = groupByDate(data);
/*
lookupTable: {
'2023-06-01': [
{ date: '2023-06-01', value: 10 },
{ date: '2023-06-01', value: 30 }
],
'2023-06-02': [
{ date: '2023-06-02', value: 20 }
],
'2023-06-03': [
{ date: '2023-06-03', value: 40 }
]
}
*/- 빈 객체를 룩업 테이블로 사용합니다.
- 원본 객체의 모든 키-값 쌍을 룩업 테이블에 추가합니다. 이때 중복 키가 있다면 기존 값이 새로운 값으로 덮어씌워집니다.
- 룩업 테이블을 다시 객체로 변환하면 중복 키 값이 제거된 새로운 객체를 얻을 수 있습니다.
이 방식의 장점은 대량의 데이터에서도 빠른 검색 속도를 유지할 수 있다는 점입니다. 다만, 최종적으로 중복 키에 대한 마지막 값만 남게 되므로, 중복 키에 대한 처리 로직이 별도로 필요할 수 있습니다. 또한, 룩업 테이블 방식은 메모리 사용량이 다소 늘어날 수 있습니다. 왜냐하면 임시로 키-값 쌍을 모두 저장해야 하기 때문입니다. 따라서 메모리 제약이 있는 환경에서는 주의가 필요합니다.
언제 룩업 테이블과 셋을 골라서 써야 할까?
룩업 테이블과 Set 모두 객체에서 중복 제거를 할 수 있는 효과적인 방법입니다. 각각의 장단점을 고려하여 상황에 맞게 적절히 선택하는 것이 중요합니다.
Set을 사용하는 경우:
- 데이터 크기가 작고, 메모리 효율성이 크게 중요하지 않은 경우
- 중복 제거 외에 다른 로직이 필요 없는 경우
- 코드 가독성과 간결함이 중요한 경우
- 순서가 중요하지 않은 경우 (Set은 값의 순서를 보장하지 않음)
룩업 테이블을 사용하는 경우:
- 대량의 데이터에서 중복 제거를 수행해야 하는 경우 (검색 속도가 빠름)
- 순서가 중요한 경우 (룩업 테이블은 객체이므로 삽입 순서 유지)
- 중복 키에 대한 추가 처리 로직이 필요한 경우
- 메모리 효율성보다는 속도 효율성이 더 중요한 경우
일반적으로 데이터 크기가 크지 않고, 단순한 중복 제거만 필요하다면 Set을 사용하는 것이 더 간편합니다. 하지만 대량의 데이터나 추가 로직이 필요한 경우에는 룩업 테이블이 더 적합할 수 있습니다. 결론적으로 프로젝트의 요구사항, 데이터 크기, 성능 요구 수준, 메모리 제약 등을 종합적으로 고려하여 Set과 룩업 테이블 중 적절한 방법을 선택하는 것이 좋습니다.
프로토타입
📌 관련 주제: 객체 리터럴, 생성자 함수에 의한 객체 생성, 클래스, 빌트인 객체
객체지향 프로그래밍은 무엇을 의미하나요?
객체지향 프로그래밍은 프로그램을 명령어 또는 함수의 목록으로 보는 전통적인 명령형 프로그래밍의 절차지향적 관점에서 벗어나
여러 개의 독립적 단위, 즉 객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임을 말한다.
객체지향 프로그래밍의 특징에 대해 말해볼 수 있나요?
- 추상화 (Abstraciton): 객체를 구성할 수 있는 다양한 속성(프로퍼티) 중에서 프로그램에 필요한 속성만 간추려 내어 표현하는 것
- 캡슐화 (Encapsulation): 데이터 구조와 데이터를 다루는 방법들을 목적에 따라 결합 시켜 묶는 것
- 상속 (Inheritance): 상위 개념의 특징을 하위 개념이 물려받는 것
- 다형성 (Polymorphism): 부모에 선언된 메서드를 자식에서 오바리이딩하여 사용하는 것
자바스크립트는 객체지향 프로그래밍 언어인가요?
자바스크립트는 객체지향 프로그래밍 뿐만 아니라 명령형, 함수형 프로그래밍을 지원하는 멀티 패러다임 프로그래밍 언어입니다.
클래스 기반 객체지향 프로그래밍 언어와 달리 프로토타입 기반의 객체지향 프로그래밍입니다.
프로토타입이 뭔가요?
프로토타입은 상속을 구현하기 위해 사용됩니다. 쉬운 예시로 붕어빵을 만들기 위한 붕어빵 틀이라고 생각하면 좋습니다.
기본적인 틀(프로토타입)에 우리가 원하는 앙금(인스턴스를 만들 때 생성자 함수에 들어갈 인수)을 넣어 기존에 만들어 놓은 프로퍼티에 바인딩하여 인스턴스(붕어빵)을 반환합니다.
JavaScript에서 기본 데이터 타입을 제외한 모든 것은 객체입니다. 객체가 만들어지기 위해서는 자신을 만드는 데 사용된 원형인 프로토타입 객체를 이용하여 객체를 만듭니다.
이때 만들어진 객체 안에 __proto__ 속성이 자신을 만들어낸 원형을 의미하는 프로토타입 객체를 참조하는 숨겨진 링크가 있습니다. 이 숨겨진 링크를 프로토타입이라고 합니다.
빌트인 객체
빌트인 객체가 뭔가요?
개발자가 모든 기능을 구현하지 않고, 편하게 개발할 수 있도록 자바스크립트에서 기본적으로 제공하는 객체이다.
Object, String, Number, Boolean, Symbol, Date, Math, RegExp, Array, Map/Set, WeakMap/WeakSet, Function, Promise, Reflect, Proxy, JSON, Error 등 40여개 표준 빌트인 객체가 있다.
빌트인 객체의 종류는 어떤게 있죠?
빌트인 객체는 크게 생성자 함수 객체와 그 외 객체로 구분할 수 있다.
- Math, Reflect, JSON을 제외한 표준 빌트인 객체는 모두 생성자 함수 객체이다.
- 생성자 함수 객체는 프로토타입 메서드와 정적 메서드, 그 외 객체는 정적 메서드만 제공한다.
(*) 자바스크립트 객체 분류
자바스크립트에서 객체는 크게 3개의 객체로 분류할 수 있다.
- 표준 빌트인 객체
- 호스트 객체
- 사용자 정의 객체
① 표준 빌트인 객체
- 앞서 설명한 자바스크립트에서 기본적으로 제공하는 객체 중 ECMAScript 사양에 정의된 표준 객체. 애플리케이션 전역의 공통 기능을 제공합니다
- 표준 빌트인 객체는 전역 객체의 프로퍼티로서 제공됩니다. 따라서 별도의 선언 없이 전역 변수처럼 언제나 참조할 수 있다
- new 연산자를 사용하여 표준 빌트인 객체와 결합하여 선언하면, 생성된 인스턴스로 하여금 해당 함수에 내장된(빌트인 된) 프로토타입 메서드들을 이용할 수 있다.
② 호스트 객체
- ECMAScript 사양에 정의되어 있지 않지만 자바스크립트 실행 환경(브라우저 또는 Node.js 환경)에서 추가로 제공하는 객체를 말한다
- 브라우저 환경에서는 DOM, BOM, CANVAS, XMLHttpRequest, fetch, Web Storage, Web Component와 같은 클라이언트 사이드 Web API를 호스트 객체로 제공합니다
③ 사용자 정의 객체
- 사용자 정의 객체는 표준 빌트인 객체와 호스트 객체처럼 기본 제공되는 객체가 아닌 사용자가 직접 정의한 객체를 말한다.
래퍼 객체에 대해서 알고 있나요?
레퍼(wrapper)객체는 원시 타입을 마치 객체 타입처럼 사용하는 과정 속에서 생기는 임시 객체이다. 원시 타입인 String, Number, Boolean으로 특정된다.
이는 원시값인 문자열, 숫자, 불리언 값의 경우 이들 원시값에 대해 마치 객체처럼 마침표 표기법 (.) 으로 접근하면 자바스크립트 엔진이 일시적으로 원시값을 연관된 객체로 변환해 주기 때문이다.
즉, 원시값을 객체처럼 사용하면 자바스크립트 엔진은 암묵적으로 연관된 객체를 생성하여 생성된 객체로 프로퍼티에 접근하거나 메서드를 호출하고 다시 원시값으로 되돌린다.
this
📌 관련 주제: 함수, 실행 컨텍스트, 생성자 함수에 의한 객체 생성, 클래스
this가 뭔가요?
this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수이다.
this를 통해 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메서드를 참조할 수 있다.
this는 자바스크립트 엔진에 의해 암묵적으로 생성되며, 코드 어디서든 참조할 수 있다.
단 this가 가리키는 값, 즉 this 바인딩은 함수 호출 방식에 의해 동적으로 결정된다.
this 바인딩이란?
바인딩이란 식별자(변수)와 값(원시 값 또는 객체)을 연결하는 과정을 의미한다.
예를 들어, 변수 선언은 변수 이름(식별자)과 확보된 메모리 공간의 주소를 바인딩하는 것이다.
this 바인딩은 this(키워드로 분류되지만 식별자 역할을 한다)와 this가 가리킬 객체를 바인딩하는 것이다.
실행 컨텍스트
📌 관련 주제: 스코프, 클로저, 변수, 함수, this
실행 컨텍스트에 대해 말해보세요
실행 컨텍스트는 소스코드를 실행하는 데 필요한 환경을 제공하고 코드의 실행 결과를 실제로 관리하는 영역이다.
식별자(변수, 함수, 클래스 등의 이름)를 등록하고 관리하는 스코프와 코드 실행 순서 관리를 구현한 내부 메커니즘으로, 모든 코드는 실행 컨텍스트를 통해 실행되고 관리된다.
실행 컨텍스트는 ① 실행 컨텍스트 스택과 ② 렉시컬 환경으로 구성되어 있다.
- ① 실행 컨텍스트 스택은 코드의 실행 순서를 관리하는 자료구조로, L.I.F.O(Last In First Out) 구조로 들어오는 코드를 관리한다.
- ② 렉시컬 환경은 모든 식별자와 바인딩된 값, 스코프를 기록 및 관리하는 자료구조이다.
② 렉시컬 환경은 키와 값을 갖는 객체 형태의 스코프(전역,함수,블록 스코프)를 생성하여 식별자를 키로 등록하고 식별자에 바인딩된 값을 관리한다.
즉, ② 렉시컬 환경은 스코프를 구분하여 식별자를 등록하고 관리하는 저장소 역할을 하는 렉시컬 스코프의 실체다.
실행 컨텍스트 구조 한눈에 보기

전역 실행 컨텍스트에 바인딩된 전역 렉시컬 환경

함수 실행 컨텍스트에 바인딩된 함수 렉시컬 환경

클로저
📌 관련 주제: 스코프, 실행 컨텍스트, 함수, 가비지 컬렉션
클로저에 대해서 아나요?
클로저는 자신이 선언될 당시의 환경을 기억하는 함수이다
클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다.
해당 함수의 생명 주기가 종료되더라도 함수의 반환된 값이 변수에 의해 아직 참조되고 있다면 생명 주기가 종료되더라도 렉시컬 환경에 남아 참조가 가능하다.
클로저를 사용하면 뭐가 좋죠?
클로저는 상태(state)를 안전하게 변경하고 유지하기 위해 사용한다.
다시 말해, 상태가 의도치 않게 변경되지 않도록 상태를 안전하게 은닉(information hiding)하고 특정 함수에게만 상태변경을 허용하기 위해 사용한다.
클로저를 어떻게 생성하나요?
- 내부(중첩) 함수가 익명 함수로 되어 외부 함수의 반환값으로 사용될 때
- 내부(중첩) 함수가 외부 함수의 스코프에서 실행될 때
- 내부 함수에서 사용되는 변수가 외부 함수의 변수 스코프에 포함되어 있을 때
var name = `Global`;
function outer() {
var name = `closure`;
return function inner() {
console.log(name);
};
}
var callFunc = outer();
callFunc();위 코드에서 callFunc를 클로저라고 한다. callFunc 호출에 의해 name이라는 값이 console 에 찍히는데, 찍히는 값은 Global이 아니라 closure라는 값이다. 즉, outer 함수의 context 에 속해있는 변수를 참조하는 것이다. 여기서 outer 함수의 지역변수로 존재하는 name변수를 free variable(자유변수) 라고 한다.
이처럼 외부 함수 호출이 종료되더라도 외부 함수의 지역 변수 및 변수 스코프 객체의 체인 관계를 유지할 수 있는 구조를 클로저 라고 한다.
클래스
📌 관련 주제: 생성자 함수에 의한 객체 생성, 프로토타입, this, 객체 리터럴
자바스크립트에서 클래스가 생기기 전에는 어떤 방식으로 객체지향 패턴을 구현했나요?
자바스크립트는 프로토타입 기반 객체지향 언어로서, 클래스가 필요 없는 객체지향 프로그래밍 언어이다.
생성자 함수와 프로토타입을 통해 객체지향 언어의 상속을 구현할 수 있었다.
// ES5 생성자 함수
function Person(name) {
this.name = name;
}
// 프로토타입 메서드
Person.prototype.sayHi = function () {
console.log('Hi! My name is ' + this.name);
};
// 인스턴스 생성
var me = new Person('Lee');
me.sayHi(); // Hi! My name is Lee하지만 클래스 기반 언어에 익숙한 프로그래머들은 프로토타입 기반의 프로그래밍 방식에 혼란을 느낄 수 있으며, 자바스크립트를 어렵게 느끼게 하는 하나의 장벽처럼 인식되었다.
ES6에서 도입된 클래스 는 ①기존 프로토타입 기반 객체지향 프로그래밍보다 ②자바나 C#과 같은 클래스 기반 객체지향 프로그래밍에 익숙한 프로그래머가 더욱 빠르게 학습할 수 있도록 클래스 기반 객체지향 프로그래밍 언어와 매우 흡사한 새로운 객체 생성 메커니즘을 제시한다.
자바스크립트에서는 프로토타입 기반의 객체지향 프로그래밍을 기반으로 설계되었지만,
이에 어려움을 느끼는 객체지향 프로그래밍에 익숙한 프로그래머들을 위해 ES6부터 클래스 개념을 도입하였다.그럼 생성자 함수와 클래스는 어떤 차이가 있나요?
클래스를 new 연산자 없이 호출하면 에러가 발생한다. 하지만, 생성자 함수는 일반 함수로 호출된다.
클래스는 상속을 지원하는 extends와 super 키워드를 제공한다. 생성자 함수는 해당 키워드를 제공하지 않는다.
클래스는 호이스팅이 발생하지 않는 것처럼 동작한다. 하지만 함수 선언문으로 작성된 클래스는 함수 호이스팅이, 함수 표현식으로 정의한 생성자 함수는 변수 호이스팅이 발생한다.
클래스 내의 모든 코드에는 암묵적으로 strict mode가 저장되어 실행되며 strict mode를 해제할 수 없다. <-> 생성자 함수는 암묵적으로 strict mode가 지정되지 않는다.
클래스의 constructor, 프로토타입 메서드, 정적 메서드는 모두 프로퍼티 어트리뷰트 [[Enumerable]] 값이 false인 열거가 되지 않는 값이다.
클래스 정의
// 클래스 선언문
class Person {}
// 함수 선언문으로 작성시, 함수 호이스팅이
// 함수 표현식으로 작성시, 변수 호이스팅이 발생한다.익명 함수와 기명 함수로 클래스 정의
// 익명 클래스 표현식
const Person = class {};
// 기명 클래스 표현식
const Person = class MyClass {};클래스 몸체에 정의할 수 있는 메서드
- ① constructor(생성자)
- ② 프로토타입 메서드
- ③ 정적 메서드
class Person {
// constructor: 생성자
constructor(name) {
// 인스턴스 생성 및 초기화
this.name = name; // name 프로퍼티는 public하다.
}
// 프로토타입 메서드
sayHi() {
console.log(`Hi! My name is ${this.name}`);
}
// 정적 메서드 (static을 붙여 정의한다)
static sayHello() {
console.log('Hello!');
}
}
// 인스턴스 생성
const me = new Person('Lee');
// ① 인스턴스의 프로퍼티 참조
console.log(me.name); // Lee
// ② 프로토타입 메서드 호출
me.sayHi(); // Hi! My name is Lee
// ③ 정적 메서드 호출 (호출 시에 인스턴스가 아닌 클래스의 메서드로 동작한다.)
Person.sayHello(); // Hello!정적 메서드와 프로토타입 메서드의 차이
- 정적 메서드와 프로토타입 메서드는 자신이 속해 있는 프로토타입 체인이 다르다. 🌟
- 정적 메서드는 클래스로 호출하고 프로토타입 메서드는 인스턴스로 호출한다. 🌟
- 정적 메서드는 인스턴스를 프로퍼티로 참조할 수 없지만 프로토타입 메서드는 인스턴스를 프로퍼티로 참조할 수 있다.
클래스의 상속
① 상속에 의한 클래스 확장은 ②프로토타입 기반 상속과는 다른 개념이다.
②는 프로토타입 체인을 통해 다른 객체의 자산을 상속받는 개념이지만 ①은 기존 클래스를 상속받아 새로운 클래스를 확장(extends)하여 정의하는 것이다.
extends 키워드
// 수퍼(베이스/부모)클래스
class Base {}
// 서브(파생/자식)클래스
class Derived extends Base {}클래스 확장
extends 키워드를 사용해 기존 클래스를 상속받아 새로운 클래스를 확장할 수 있다. super 키워드로 수퍼클래스의 constructor와 메서드를 호출하며, 메서드 오버라이딩을 통해 상속받은 메서드를 재정의할 수 있다. 이를 통해 코드 재사용성과 계층적 관계를 표현할 수 있다.
상세 설명 및 예제 코드 보기
기본 클래스 상속 예제
class Animal {
constructor(age, weight) {
this.age = age;
this.weight = weight;
}
eat() {
return 'eat';
}
move() {
return 'move';
}
}
// 상속을 통해 Animal 클래스를 확장한 Bird 클래스
class Bird extends Animal {
fly() {
return 'fly';
}
}
const bird = new Bird(1, 5);
console.log(bird); // Bird {age: 1, weight: 5}
console.log(bird instanceof Bird); // true
console.log(bird instanceof Animal); // true (프로토타입 체인으로 얽혀있기 때문에)
console.log(bird instanceof Object); // true (스코프의 최 상위에는 Object가 있다)
console.log(bird.eat()); // eat
console.log(bird.move()); // move
console.log(bird.fly()); // flysuper 키워드
super 키워드는 함수처럼 호출할 수도 있고 this와 같이 식별자처럼 참조할 수 있는 특수한 키워드다.
1. super를 호출하면 수퍼클래스의 constructor(super-constructor)를 호출한다.
2. super를 참조하면 수퍼클래스의 메서드를 호출할 수 있다.super 호출
// 수퍼클래스
class Base {
constructor(a, b) {
this.a = a;
this.b = b;
}
}
// 서브클래스
class Derived extends Base {
constructor(a, b, c) {
super(a, b); // 수퍼클래스에 정의한 프로퍼티(a,b)를 그대로 사용하겠다는 의미
this.c = c;
}
}
const derived = new Derived(1, 2, 3);
console.log(derived); // Derived {a: 1, b: 2, c: 3}super 참조
// 수퍼클래스
class Base {
constructor(name) {
this.name = name;
}
sayHi() {
return `Hi! ${this.name}`;
}
}
// 서브클래스
class Derived extends Base {
sayHi() {
// super.sayHi는 수퍼클래스의 프로토타입 메서드를 가리킨다.
return `${super.sayHi()}. how are you doing?`;
}
}
const derived = new Derived('Lee');
console.log(derived.sayHi()); // Hi! Lee. how are you doing?추상화
추상화는 다양한 속성 중에서 프로그램에 필요한 속성만 간추려 내어 표현하는 것이다
// 수퍼클래스
class Rectangle {
constructor(width, height) {
// constructor
this.width = width;
this.height = height;
}
// 프로토타입 메서드
getArea() {
return this.width * this.height;
}
toString() {
return `width = ${this.width}, height = ${this.height}`;
}
}
// 서브클래스
class ColorRectangle extends Rectangle {
// extends 키워드를 통해 수퍼클래스를 상속받음
constructor(width, height, color) {
super(width, height);
this.color = color;
}
// 메서드 오버라이딩
toString() {
return super.toString() + `, color = ${this.color}`;
}
}
const colorRectangle = new ColorRectangle(2, 4, 'red');
console.log(colorRectangle); // ColorRectangle {width: 2, height: 4, color: "red"}
// 상속을 통해 getArea 메서드를 호출
console.log(colorRectangle.getArea()); // 8
// 오버라이딩된 toString 메서드를 호출
console.log(colorRectangle.toString()); // width = 2, height = 4, color = red위 코드의 흐름은 다음과 같다
- 서브클래스의 super 호출
- 수퍼클래스의 인스턴스 생성과 this 바인딩
- 수퍼클래스의 인스턴스 초기화
- 서브클래스 constructor로의 복귀와 this 바인딩
- 서브클래스의 인스턴스 초기화
- 인스턴스 반환
오버라이딩
상위(super) 클래스가 가지고 있는 메서드를 하위(sub) 클래스가 재정의하여 사용하는 방식오버로딩
함수의 이름은 동일하지만 매개변수의 타입 또는 개수가 다른 메서드를 구현하고 매개변수에 의해 메서드를 구별하여 호출하는 방식이다.
자바스크립트는 오버로딩을 지원하지 않지만 arguments 객체를 사용하여 구현할 수는 있다.브라우저 렌더링 과정
브라우저의 렌더링 과정에 대해 설명해보세요
- 클라이언트에서 불러오고 싶은 파일을 서버에 요청한다
- 주소창에 직접 입력하거나, 클릭을 통해 해당 웹 페이지에 접근한다
- 클라이언트에서 요청한 URI를 DNS를 통해 IP 주소로 변환하고, 해당 IP를 가진 서버에 GET 요청을 보내게 된다
- 서버에서 응답으로 받은 HTML 데이터를 파싱한다 (바이트 > 문자 > 토큰 > 노드 > DOM)
- 서버에 존재하던 HTML 파일이 브라우저의 요청에 의해 응답된다
- 이때 서버는 브라우저가 요청한 HTML 파일을 읽어 들여 메모리에 저장한 다음 메모리에 저장된 바이트(2진수)를 인터넷을 경유하여 응답한다
- 브라우저는 서버가 응답한 HTML 문서를 바이트(2진수) 형태로 응답받는다
- 따라서 응답된 바이트 형태의 HTML 문서를 meta 태그의 charset 어트리뷰트에 의해 지정된 인코딩 방식을 기준으로 문자열로 변환한다
- 문자열로 변환된 HTML 문서를 읽어 들여 문법적 의미를 갖는 코드의 최소 단위인 토큰(token)들로 분해한다.
- 각 토큰들을 객체로 변환하여 노드들을 생성한다 토큰의 내용에 따라 ⑴ 문서 노드 ⑵ 요소 노드 ⑶ 어트리뷰트 노드 ⑷ 텍스트 노드가 생성된다.
- HTML 마크업을 바탕으로 DOM 트리를 생성한다
- HTML 문서는 HTML 요소들의 집합으로 이루어지며 HTML 요소는 중첩 관계를 갖는다
- 이때 HTML 요소 간에는 중첩 관계에 의해 부자(부모자식)관계가 형성된다
- 이러한 HTML 요소 간의 부자 관계를 반영하여 모든 노드들을 트리 자료구조로 구성한다.
- 이 노드들로 구성된 트리 자료구조를 DOM이라 부른다.
- CSS 마크업을 바탕으로 CSSOM 트리를 생성한다 (바이트 > 문자 >토큰 > 노드 > CSSOM)
- HTML 데이터와 마찬가지로 파싱하여 CSSOM 트리 구조로 나타낸다
- DOM트리와 CSSOM트리를 결합하여 렌더 트리를 형성한다
- 렌더링 엔진에 의해 문서의 처음부터 끝까지 해석이 완료되어 DOM트리와 CSSOM 트리가 완성된다면, 이 둘을 바탕으로 렌더 트리를 생성한다
- 렌더 트리에서 레이아웃을 실행한다
- 렌더 트리를 기반으로 HTML 요소의 레이아웃(위치와 크기)를 계산합니다.
- 개별 노드를 화면에 페인트한다
- 이후 레이아웃을 바탕으로 브라우저 화면에 픽셀을 렌더링하는 페인팅(painting)처리에 입력되면 렌더링이 완료되게 됩니다
- 레이어를 합성(Composite)한다
- 브라우저는 페인트 단계에서 여러 레이어로 나눠서 그린 후, GPU를 통해 레이어들을 합성한다
transform,opacity속성이 성능이 좋은 이유는 Layout과 Paint를 건너뛰고 Composite만 발생하기 때문이다
브라우저의 렌더링 과정에 자바스크립트는 어떻게 동작하나요?
HTML/CSS 파싱 과정과 마찬가지로
렌더링 엔진은 HTML을 한 줄씩 순차적으로 파싱하며 DOM을 생성해 나가다가 자바스크립트 파일을 로드하는<script>태그나 자바스크립트 코드를 콘텐츠로 담은<script>태그를 만나면 DOM 생성을 일시 중단한다.그리고
<script>태그의 src 어트리뷰트에 정의된 자바스크립트 파일을 서버에 요청하여 로드한 자바스크립트 파일이나<script>태그 내의 자바스크립트 코드를 파싱하기 위해② 자바스크립트 엔진에 제어권을 넘긴다. 이후 자바스크립트 파싱과 실행이 종료되면① 렌더링 엔진으로 다시 제어권을 넘겨 HTML 파싱이 중단된 지점부터 다시 HTML 파싱을 시작하여 DOM 생성을 재개한다.자바스크립트 파싱과 실행은
① 브라우저 렌더링 엔진이 아닌② 자바스크립트 엔진이 처리한다.② 자바스크립트 엔진은 자바스크립트 코드를 파싱하여 CPU가 이해할 수 있는 저수준 언어(low-level language)로 변환하고 실행하는 역할을 한다.② 자바스크립트 엔진은 자바스크립트를 해석하여AST(Abstract Syntax Tree: 추상적 구문 트리)를 생성한다. 그리고 AST를 기반으로 인터프리터가 실행할 수 있는 중간 코드(intermediate code)인 바이트코드를 생성하여 실행한다.리플로우와 리페인트
- 만약 자바스크립트 코드에 DOM이나 CSSOM을 변경하는 DOM API가 사용된 경우 DOM이나 CSSOM이 변경된다.
- 이때 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합되고 변경된 렌더 트리를 기반으로 레이아웃과 페인트 과정을 거쳐 브라우저의 화면에 다시 렌더링한다. 이를 리플로우(reflow), 리페인트(repaint)라 한다.
리플로우와 리페인트의 차이점은 무엇인가요?
리플로우(Reflow): 레이아웃(위치, 크기)이 변경될 때 발생한다.width,height,margin,padding등의 속성 변경 시 발생하며, 비용이 높다.리페인트(Repaint): 시각적 속성(색상, 그림자 등)만 변경될 때 발생한다.color,background-color,visibility등의 속성 변경 시 발생하며, 리플로우보다 비용이 낮다.- 리플로우가 발생하면 리페인트도 함께 발생하지만, 리페인트만 단독으로 발생할 수도 있다.
script 태그의 async와 defer 속성에 대해 설명해보세요
기본 동작: HTML 파싱 중<script>태그를 만나면 파싱을 중단하고 스크립트를 다운로드 및 실행한다.async: 스크립트 다운로드는 HTML 파싱과 병렬로 진행되며, 다운로드가 완료되면 즉시 실행한다. 실행 순서가 보장되지 않아 독립적인 스크립트(광고, 분석 도구)에 적합하다.defer: 스크립트 다운로드는 HTML 파싱과 병렬로 진행되며, HTML 파싱이 완료된 후 실행한다. 실행 순서가 보장되어 DOM에 의존하는 스크립트에 적합하다.
DOM
📌 관련 주제: 브라우저 렌더링 과정, 이벤트
DOM이 뭔가요?
DOM은 HTML 문서의 계층적 구조와 정보를 표현하며 이를 제어할 수 있는 API, 즉 프로퍼티와 메서드를 제공하는 트리 자료구조다.
DOM을 구성하는 건 뭐가 있나요?
① HTML 요소는 렌더링 엔진에 의해 파싱되어 ② DOM을 구성하는 요소 노드 객체로 변환된다.
이때 HTML 요소 어트리뷰트는 어트리뷰트 노드로, HTML 요소의 텍스트 콘텐츠는 텍스트 노드로 변환된다.
DOM은 노드 객체의 계층적인 구조로 구성된다. 노드 객체는 종류가 있고 상속 구조를 갖는다.
노드 객체는 총 12개의 종류(노드 타입)가 있다. 이 중에서 중요한 노드 타입은 다음과 같이 4가지다.
- 문서 노드
- 요소 노드
- 어트리뷰트 노드
- 텍스트 노드
① 문서 노드
<!DOCTYPE>문서 노드는 DOM 트리의 최상위에 존재하는 루트 노드로서 document 객체를 가리킨다.
document 객체는 브라우저가 렌더링한 HTML 문서 전체를 가리키는 객체로서 전역 객체 window의 document 프로퍼티에 바인딩되어 있다.
따라서 문서 노드는 window.documnet 또는 document로 참조할 수 있다.
브라우저 환경의 모든 자바스크립트 코드는 script 태그에 의해 분리되어 있어도 하나의 전역 객체 window를 공유한다.
따라서 window의 document 프로퍼티에 바인딩되어 있는 하나의 document 객체를 바라본다. 즉, HTML 문서당 document 객체는 유일하다.
② 요소 노드
<html> <head> <meta> <link> <body> <ul> <li> <script>요소 노드는 HTML 요소를 가리키는 객체다. 요소 노드는 HTML 요소 간의 중첩에 의해 부자 관계를 가지며, 이 부자 관계를 통해 정보를 구조화한다.
따라서 요소 노드는 문서의 구조를 표현한다고 할 수 있다.
③ 어트리뷰트 노드
charset="UTF"
rel="stylesheet"
...
id="apple"어트리뷰트 노드는 HTML 요소의 어트리뷰트를 가리키는 객체다. 어트리뷰트 노드는 어트리뷰트가 지정된 HTML 요소의 요소 노드와 연결되어 있다.
④ 텍스트 노드
APPLE
BANANA
ORANGE텍스트 노드는 HTML 요소와 텍스트를 가리키는 객체다. 요소 노드가 문서의 구조를 표현한다면 텍스트 노드는 문서의 정보를 표현한다고 할 수 있다.
이벤트
📌 관련 주제: DOM, 브라우저 렌더링 과정, 비동기 프로그래밍
마우스 이벤트 타입에는 뭐가 있나요? click 말고 클릭을 대체할 수 있는 이벤트가 있나요?
mouseup
| 이벤트 타입 | 이벤트 발생 시점 |
|---|---|
| click | 마우스 버튼을 클릭했을 때 |
| dbclick | 마우스 버튼을 더블 클릭했을 때 |
| mousedown | 마우스 버튼을 누르고 있을 때 |
| mouseup | 누르고 있던 마우스 버튼을 뗄 때 |
| mousemove | 마우스 커서를 움직일 때 |
| mouseenter | 마우스 커서를 HTML 요소 안으로 이동했을 때 (버블링 x) |
| mouseover | 마우스 커서를 HTML 요소 안으로 이동했을 때 (버블링 o) |
| mouseleave | 마우스 커서를 HTML 요소 밖으로 이동했을 때(버블링x) |
| mouseout | 마우스 커서를 HTML 요소 밖으로 이동했을 때(버블링o) |
그 외에 알고 있는 대표적인 이벤트가 있나요?
키보드 이벤트
| 이벤트 타입 | 이벤트 발생 시점 |
|---|---|
| keydown | 키를 누르고 있을 때 |
| keypress | 키를 누르고 뗏을 때 (폐지되었으므로 사용 x) |
| keyup | 누르고 있던 키를 뗄 때 |
포커스 이벤트
| 이벤트 타입 | 이벤트 발생 시점 |
|---|---|
| focus | 요소가 포커스를 얻었을 때 (버블링 x) |
| blur | 요소가 포커스를 잃었을 때 (버블링 x) |
| focusin | 요소가 포커스를 얻었을 때 (버블링 o) |
| foucusout | 요소가 포커스를 잃었을 때 (버블링 o) |
폼 이벤트
| 이벤트 타입 | 이벤트 발생 시점 |
|---|---|
| submit | form을 submit할 때 (버튼 또는 키) |
| reset | reset 버튼을 클릭할 때 (최근에는 사용 안함) |
값 변경 이벤트
| 이벤트 타입 | 이벤트 발생 시점 |
|---|---|
| input | input 또는 textarea 요소의 값이 변경되었을 때 |
| change | select box, checkbox, radio button의 상태가 변경되었을 때 |
DOM 뮤테이션 이벤트
| 이벤트 타입 | 이벤트 발생 시점 |
|---|---|
| DOMContentLoaded | HTML 문서의 로드와 파싱이 완료되어 DOM 생성이 완료되었을 때 |
뷰 이벤트
| 이벤트 타입 | 이벤트 발생 시점 |
|---|---|
| resize | 브라우저 윈도우의 크기를 리사이즈할 때 연속적으로 발생 |
| scroll | 웹피이지(document) 또는 HTML 요소를 스코롤할 때 연속적으로 발생 |
리소스 이벤트
| 이벤트 타입 | 이벤트 발생 시점 |
|---|---|
| load | DOMContentLoaded 이후, 모든 리소스의 로딩이 완료되었을 때 |
| unload | 리소스가 언로드 될 때 (주로 새로운 웹페이지를 요청한 경우) |
| abort | 리소스 로딩이 중단되었을 때 |
| error | 리소스 로딩이 실패했을 때 |
이벤트 전파(propagation)에 대해서 알고 있나요?
DOM 트리상에 존재하는 모든 DOM 요소 노드에서 발생한 이벤트는 DOM 트리를 통해 전파됩니다. 이를 이벤트 전파라고 합니다.
사용자의 다양한 입력을 통해 동적으로 생성되는 이벤트 객체는 이벤트를 발생시킨 타깃(target)을 중심으로 DOM 트리를 통해 전파됩니다.
전파되는 방향에 따라 3단계로 구분할 수 있습니다.
- 캡처링 단계 : 이벤트가 상위 요소에서 하위 요소 방향으로 전파
- 타깃 단계 : 이벤트가 이벤트 타깃에 도달
- 버블링 단계: 이벤트가 하위 요소에서 상위 요소 방향으로 전파
3단계 이미지 보기

브라우저는 기본적으로 이벤트 버블링 단계에서 이벤트를 캐치합니다.
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<div>Click me</div>
</body>
<script>
const html = document.querySelector('html');
const body = document.querySelector('body');
const div = document.querySelector('div');
html.addEventListener('click', () => console.log('HTML'));
body.addEventListener('click', () => console.log('BODY'));
div.addEventListener('click', () => console.log('DIV'));
</script>
</html>>>>
DIV
BODY
HTML
이벤트 캡처링 단계라면 HTML > BODY > DIV 순으로 ① 상위 노드에서 ② 하위 노드로 내려오며 이벤트를 캐치할 것입니다.
하지만 브라우저는 기본적으로 이벤트 버블링 단계인 우리가 클릭하고자 한 이벤트 객체의 타깃인 <div> 에 도달한 후 다시 해당 ② 하위 노드에서 ① 상위 노드로 돌아가는 과정 에서 이벤트를 캐치하기 때문입니다.
물론 addEventListener 메서드의 세번째 인수(argument)로 옵션인 [, useCaputure] 자리에 boolean 값인 true를 넣어준다면, 캡처링 단계에서도 이벤트 객체를 캐치할 수 있습니다. (기본값으로는 false로, 버블링 단계에서 이벤트 객체를 캐치합니다)

html.addEventListener("click", () => console.log("HTML"), true);
body.addEventListener("click", () => console.log("BODY"), true);
div.addEventListener("click", () => console.log("DIV"));
>>>
HTML
BODY
DIV이벤트 위임(delegation)에 대해서 알고있나요?
연속되는 태그에 대해서 공통적으로 이벤트를 줘야할 때 우리가 이벤트 핸들러를 바인딩할 해당 요소의 부모 요소에게 이를 위임하여 이벤트를 진행하는 것 을 이벤트 위임 (event delegation) 이라 합니다.
예제 코드 보기
<!DOCTYPE html>
<html>
<head>
<title>eventDelegation</title>
<meta charset="UTF-8" />
<style>
body {
font-family: sans-serif;
}
.btn-number {
background-color: yellowgreen;
}
</style>
</head>
<body>
<div class="container">
<button class="btn-number">1</button>
<button class="btn-number">2</button>
<button class="btn-number">3</button>
<button class="btn-number">4</button>
<button class="btn-number">5</button>
</div>
<script>
const div = document.querySelector('div');
div.addEventListener('click', (e) => {
console.log(e.target.innerHTML);
});
</script>
</body>
</html>1만약 공통되는 button 태그에 대해서 이벤트를 준다면, 버튼별 이벤트 ① onclick 또는 ② addEventListener 메서드를 사용하여 각 버튼에 해당되는 로직을 바인딩해줘야 할 것입니다.
하지만 이벤트 위임(event delegation) 을 통해 부모 요소에 이 작업을 위임하여 현재 클릭하는 타깃 (e.target)에 대한 값을 출력할 수 있습니다.
타이머
호출 스케쥴링이 무엇인가요?
타이머 함수를 사용하여 명시적으로 호출하지 않고 일정 시간이 경기된 이후에 호출되도록 함수 호출을 예약하는 것
타이머 함수에는 어떤 것들이 있나요?
자바스크립트는 setTimeout/clearTimeout(일정 시간 후 단 한 번 실행)과 setInterval/clearInterval(일정 시간마다 반복 실행) 타이머 함수를 제공한다. 타이머 함수는 고유한 타이머 id를 반환하며, clear 함수에 id를 전달하여 타이머를 취소할 수 있다.
타이머 함수 상세 설명 및 예제 보기
① setTimeout/ clearTimeout
setTimeout 함수로 생성한 타이머는 한 번 동작합니다.
const timeoutdId = setTimeout(func|code[, delay, param1, param2, ...]);| 매개변수 | 설명 |
|---|---|
| func | 타이머가 만료된 뒤 호출될 콜백 함수 콜백 함수 대신 코드를 문자열로 전달할 수 있다. 이때 코드 문자열은 타이머가 만료된 뒤 해석되고 실행된다. |
| delay | 타이머 만료 시간(밀리초(ms) 단위), setTimeout 함수는 delay 시간으로 단 한 번 동작하는 타이머를 생성한다. 인수 전달을 생략한 경우 기본 값 0이 지정된다. |
| param1, param2, ... | 호출 스케줄링된 콜백 함수에 전달해야 할 인수가 존재하는 경우 세 번째 이후의 인수로 전달할 수 있다. |
setTimeout 함수는 생성된 타이머를 식별할 수 있는 고유한 타이머를 식별할 수 있는 고유한 id를 반환한다.
setTimeout 함수가 반환한 타이머 id는 ① 브라우저 환경일 경우 숫자이며 ② Node.js 환경인 경우 객체다
// 1초(1000ms) 후 타이머가 만료되면 콜백 함수가 호출된다.
setTimeout(() => console.log('Hi!'), 1000);
// 세 번째 인수로 문자열 'Lee' 전달
setTimeout((name) => console.log(`Hi! ${name}.`), 1000, 'Lee');
// 두 번째 인수(delay)를 생략하면 기본값 0이 지정된다.
setTimeout(() => console.log('Hello!'));setTimeout 함수가 반환한 타이머 id를 clearTimeout 함수의 인수로 전달하여 타이머를 취소할 수 있다.
const timerId = setTimeout(() => console.log('Hi!'), 1000);
console.log(timeId);
clearTimeout(timerId);② setInterval/ clearInterval
setInterval 함수는 두 번째 인수로 전달받은 시간(ms, 1/1000초)으로 반복 동작하는 타이머를 생성한다.
const timeoutdId = setInterval(func|code[, delay, param1, param2, ...]);setInterval의 첫 번째 인수인 콜백 함수는 두 번째 인수로 전달받은 시간이 경과할 때마다 반복 실행되도록 호출 스케줄링된다.
let count = 1;
const timeoutId = setInterval(() => {
console.log(count); // 1 2 3 4 5
if (count++ === 5) clearInterval(timeoutId);
}, 1000);
console.log('timeoutId: ', timeoutId);이벤트가 과도하게 호출되어 성능에 문제를 일으킬 경우에 할 수 있는 어떤 일을 통해 해결할 수 있나요?
scroll, resize, mousemove 같은 이벤트는 짧은 시간 간격으로 연속해서 발생한다.
이러한 이벤트에 바인딩한 이벤트 핸들러는 과도하게 호출되어 성능에 문제를 일으킬 수 있다.
① 디바운스와 ② 쓰로틀은 짧은 시간 간격으로 연속해서 발생하는 이벤트를 그룹화해서 과도한 이벤트 핸들러의 호출을 방지하는 프로그래밍 기법이다.
이렇게 ① 디바운스와 ② 쓰로틀을 사용하여 이벤트가 과도하게 호출되는 것을 막거나, 조절할 수 있다.
디바운스에 대해서 알고 있나요?
디바운스(debounce)는 짧은 시간 간격으로 이벤트가 연속해서 발생하면 이벤트 핸들러를 호출(call)하지 않다가 일정 시간이 경과된 이후에 이벤트 핸들러가 한 번만 호출되도록 한다.
즉 디바운스는 짧은 시간 간격으로 발생하는 이벤트를 그룹화해서 마지막에 한 번만 이벤트 핸들러가 호출되도록 한다.
텍스트 입력 필드에서 input 이벤트가 짧은 시간 간격으로 연속해서 발생하는 경우, 디바운스를 통해 Ajax와 같은 요청의 빈도를 줄여 서버에 부하를 줄일 수 있다.
<!DOCTYPE html>
<html>
<body>
<input type="text" />
<div class="msg"></div>
<script>
const $input = document.querySelector('input');
const $msg = document.querySelector('.msg');
const debounce = (callback, delay) => {
let timerId;
// debounce 함수는 timerId를 기억하는 클로저를 반환한다.
return (event) => {
// delay가 경과하기 이전에 이벤트가 발생하면 이전 타이머를 취소하고
// 새로운 타이머를 재설정한다.
// 따라서 delay보다 짧은 간격으로 이벤트가 발생하면 callback은 호출되지 않는다.
if (timerId) clearTimeout(timerId);
timerId = setTimeout(callback, delay, event);
};
};
// debounce 함수가 반환하는 클로저가 이벤트 핸들러로 등록된다.
// 300ms보다 짧은 간격으로 input 이벤트가 발생하면 debounce 함수의 콜백 함수는
// 호출되지 않다가 300ms 동안 input 이벤트가 더 이상 발생하면 한 번만 호출된다.
$input.oninput = debounce((e) => {
$msg.textContent = e.target.value;
}, 300);
</script>
</body>
</html>result 💻

검색 창에서 매 클릭 이벤트 (e.target.value) 마다 ajax 요청을 보내는 것보다 디바운스를 통해 일정 기간을 바탕으로 마지막 이벤트에 대한 ajax 요청을 보내는 것이 서버의 부하를 줄이는 데 더욱 효율적일 것이다.
쓰로틀에 대해서 알고 있나요?
**쓰로틀(throttle)**은 짧은 시간 간격으로 이벤트가 연속 발생하더라도 일정 시간 간격으로 최대 한 번만 이벤트 핸들러가 호출되도록 제한한다. scroll, resize, mousemove 등 연속 발생 이벤트의 호출 주기를 제어하여 성능을 최적화한다. 무한 스크롤 UI 구현 등에 유용하다.
쓰로틀 구현 예제 코드 보기
<!DOCTYPE html>
<html>
<head>
<style>
.container {
width: 300px;
height: 300px;
background-color: rebeccapurple;
overflow: scroll;
}
.content {
width: 300px;
height: 1000vh;
}
</style>
</head>
<body>
<div class="container">
<div class="content"></div>
</div>
<div>
일반 이벤트 핸들러가 scroll 이벤트를 처리한 횟수:
<span class="normal-count">0</span>
</div>
<div>
쓰로틀 이벤트 핸들러가 scroll 이벤트를 처리한 횟수:
<span class="throttle-count">0</span>
</div>
<script>
const $container = document.querySelector('.container');
const $normalCount = document.querySelector('.normal-count');
const $throttleCount = document.querySelector('.throttle-count');
const throttle = (callback, delay) => {
let timerId;
// throttle 함수는 timerId를 기억하는 클로저를 반환한다.
return (event) => {
// delay가 경과하기 이전에 이벤트가 발생하면 아무것도 하지 않다가
// delay가 경과했을 때 이벤트가 발생하면 새로운 타이머를 재설정한다.
// 따라서 delay 간격으로 callback이 호출된다.
if (timerId) return;
timerId = setTimeout(
() => {
callback(event);
timerId = null;
},
delay,
event
);
};
};
let normalCount = 0;
$container.addEventListener('scroll', () => {
$normalCount.textContent = ++normalCount;
});
let throttleCount = 0;
// throttle 함수가 반환하는 클로저가 이벤트 핸들러로 등록된다.
$container.addEventListener(
'scroll',
throttle(() => {
$throttleCount.textContent = ++throttleCount;
}, 1000)
);
</script>
</body>
</html>result 💻

비동기 프로그래밍
📌 관련 주제: Promise, 제너레이터와 async await, 타이머, 이벤트
동기와 비동기의 차이점에 대해서 설명해줄 수 있나요?
- 현재 실행 중인 태스크가 종료될 때까지 다음에 실행될 태스크가 대기하는 방식 을 동기(synchronous) 처리 방식이라고 하며
- 현재 실행 중인 태스크가 종료되지 않은 상태라 해도 다음 태스크를 곧바로 실행하는 방식 을 비동기(asynchronous) 처리라고 한다.
- 대표적으로 타이머 함수인 ① setTimeout/ setInterval ② HTTP 요청 ③ 이벤트 핸들러 는 비동기 처리 방식으로 동작한다.
자바스크립트 엔진은 기본적으로 함수를 호출하면 함수 코드가 평가되어 함수 실행 컨텍스트가 생성된다.
이때 생성된 함수 실행 컨텍스트는 실행 컨텍스트 스택(콜 스택이라고도 부름)에 푸시되고 함수 코드가 실행된다.
함수 코드의 실행이 종료되면 함수 실행 컨텍스트는 실행 컨텍스트 스택에서 팝되어 제거된다.
const x = 1;
function foo() {
const y = 2;
function bar() {
const z = 3;
console.log(x + y + z);
}
bar();
}
foo(); // 6
함수가 실행되려면 ① 함수 코드 평가 과정에서 생성된 함수 실행 컨텍스트가 실행 컨텍스트 스택에 푸시되어야 한다.
다시 말해, 실행 컨텍스트 스택에 함수 실행 컨텍스트가 푸시되는 것은 바로 함수 실행의 시작을 의미한다.
함수가 호출된 순서대로 순차적으로 실행되는 이유는 함수가 호출된 순서대로 함수 실행 컨텍스트가 실행 컨텍스트 스택에 푸시되기 때문이다.
이처럼 함수의 실행 순서는 실행 컨텍스트 스택이 관리한다.
스택구조

자바스크립트 엔진은 단 하나의 실행 컨텍스트 스택 구조를 갖는다.
이는 함수를 실행할 수 있는 창구가 단 하나이며, 동시에 2개 이상의 함수를 실행할 수 없다는 것을 의미한다.
실행 컨텍스트의 최상위 요소인 '실행 중인 실행 컨텍스트'를 제외한 모든 실행 컨텍스트는 모두 실행 대기 중인 태스크들이다.
대기 중인 태스크들은 현재 실행 중인 실행 컨텍스트가 팝(pop)되어 실행 컨텍스트 스택에서 제거되면, 비로소 실행되기 시작한다.
태스크

자바스크립트 엔진은 한 번에 하나의 태스크(작업, 일)만 실행할 수 있는 싱글 스레드 방식으로 동작한다.
싱글 스레드 방식은 한 번에 하나의 태스크만 실행할 수 있기 때문에 처리에 시간이 걸리는 태스크를 실행하는 경우 블로킹(작업중단)이 발생한다.
동기 코드 예시
function sleep(func, delay) {
const delayUntil = Date.now() + delay;
// 현재 시간(Date.now())에 delay를 더한 delayUntil이 현재 시간보다 작으면 계속 반복한다.
while (Date.now() < delayUntil);
// 일정 시간(delay)이 경과한 이후에 콜백 함수(func)를 호출한다.
func();
}
function foo() {
console.log('foo');
}
function bar() {
console.log('bar');
}
sleep(foo, 3000);
bar();
이처럼 현재 실행 중인 태스크가 종료될 때까지 다음에 실행될 태스크가 대기하는 방식을 동기(synchronous) 처리 방식이라고 한다.
동기 처리 방식은 태스크를 순서대로 하나씩 처리하므로 실행 순서가 보장된다는 장점이 있지만, 앞선 태스크가 종료할 때까지 이후 태스크들이 블로킹되는 단점이 있다.

비동기 코드 예시
function foo() {
console.log('foo');
}
function bar() {
console.log('bar');
}
// 타이머 함수 setTimeout은 일정 시간이 경과한 이후에 콜백 함수 foo를 호출한다.
// 타이머 함수 setTimeout은 bar 함수를 블로킹하지 않는다.
setTimeout(foo, 3 * 1000);
bar();
setTimeout 함수는 앞서 살펴본 sleep 함수와 유사하게 일정 시간이 경과한 이후에 콜백 함수를 호출하지만 setTimeout 함수 이후의 태스크(여기서는 bar)를 블로킹하지 않고 곧바로 실행한다.
이처럼 현재 실행 중인 태스크가 종료되지 않은 상태라 해도 다음 태스크를 곧바로 실행하는 방식을 비동기(asynchronous) 처리라고 한다.

비동기 처리 방식은 현재 실행 중인 태스크가 종료되지 않은 상태라 해도 다음 태스크를 곧바로 실행하므로 블로킹이 발생하지 않는다는 장점이 있다.
하지만 동시에 태스크의 실행 순서가 보장되지 않는 단점이 있다.
이벤트 루프와 태스크 큐에 대해서 알고 있나요?
자바스크립트는 싱글 스레드로 동작하기 때문에 한 번에 하나의 태스크만 처리할 수 있다 하지만 브라우저가 동작하는 것을 살펴보면 많은 태스크가 동시에 처리되는 것처럼 느껴진다
예를 들어, HTML 요소가 애니메이션 효과를 통해 움직이면서 이벤트를 처리하기도 하고, HTTP 요청을 통해 서버로부터 데이터를 가지고 오면서 렌더링하기도 한다. 이처럼 자바스크립트의 동시성을 지원하는 것이 바로 이벤트 루프(event loop)다.
이벤트 루프는 브라우저에 내장되어 있는 기능 중 하나다. 브라우저 환경을 그림으로 표현하면 다음과 같다.

구글의 v8 자바스크립트 엔진을 비롯한 대부분의 자바스크립트 엔진은 크게 2개의 영역으로 구분할 수 있다.
① 콜 스택 (call stack)
소스코드(전역 코드 및 함수 코드 등) 평가 과정에서 생성된 실행 컨텍스트가 추가되고 제거되는 스택 자료구조인 실행 컨텍스트 스택이 바로 콜 스택이다.
함수를 호출하면 함수 실행 컨텍스트가 순차적으로 콜 스택에 푸시되어 순차적으로 실행된다. 자바스크립트 엔진은 단 하나의 콜 스택을 사용하기 때문에 최상위 실행 컨텍스트(실행 중인 실행 컨텍스트)가 종료되어 콜 스택에서 제거되기 전까지는 다른 어떤 태스크도 실행되지 않는다.
②힙 (heap)
힙은 객체가 저장되는 메모리 공간이다. 콜 스택의 요소인 실행 컨텍스트는 힙에 저장된 객체를 참조한다.
메모리에 값을 저장하려면 먼저 값을 저장할 메모리 공간의 크기를 결정해야 한다. 객체는 원시 값과는 달리 크기가 정해져 있지 않으므로 할당해야 할 메모리 공간의 크기를 런타임에 결정(동적 할당)해야 한다. 따라서 객체가 저장되는 메모리 공간인 힙은 구조화되어 있지 않다는 특징이 있다.
이처럼 콜 스택과 힙으로 구성되어 있는 자바스크립트 엔진은 단순히 태스크가 요청되면 콜 스택을 통해 요청된 작업을 순차적으로 실행할 뿐이다. 비동기 처리에서 ① 소스코드의 평가와 ② 실행을 제외한 모든 처리는 자바스크립트 엔진을 구동하는 환경인 브라우저 또는 Node.js가 담당한다.
예를 들어,
① 비동기 방식으로 동작하는 setTimeout의 콜백 함수의 평가와 실행은 자바스크립트 엔진이 담당하지만
② 호출 스케줄링을 위한 타이머 설정과 콜백 함수의 등록은 브라우저 또는 Node.js가 담당한다
이를 위해 브라우저 환경은 태스크 큐와 이벤트 루프를 제공한다
③ 태스크 큐 (task queue/event queue/callback queue)
- setTimeout이나 setInterval과 같은 비동기 함수의 콜백 함수 또는 이벤트 핸들러가 일시적으로 보관되는 영역이다
- 태스크 큐와는 별도로 프로미스 후속 처리 메서드의 콜백 함수가 일시적으로 보관되는 마이크로태스크 큐도 존재한다
큐 (queue)
- 스택 자료구조와 달리 선입선출(먼저 들어온 것이 먼저 나가는) 형식을 띈다
- F.I.F.O(First In First Out) 라고도 부른다

④ 이벤트 루프 (event loop)
- 이벤트 루프는 콜 스택에 현재 실행 중인 실행 컨텍스트가 있는지, 그리고 태스크 큐에 대기 중인 함수(콜백 함수, 이벤트 핸들러 등)가 있는지 반복해서 확인한다
- 만약 콜 스택이 비어 있고 태스크 큐에 대기 중인 함수가 있다면 이벤트 루프는 순차적(FIFO)으로 태스크 큐에 대기 중인 함수를 콜 스택으로 이동시킨다
- 이때 콜 스택으로 이동한 함수는 실행된다. 즉, 태스크 큐에 일시 보관된 함수들을 비동기 처리 방식으로 동작한다
마이크로태스크 큐에 대해서 알고 있나요?
마이크로태스크 큐 는 태스크큐 와 별도의 큐로 후속 처리 메서드의 콜백 함수가 일시 저장된다.
그 외의 비동기 함수 ① setTimeout/ setInterval ② HTTP 요청 ③ 이벤트 핸들러 의 콜백 함수나 이벤트 핸들러는 태스크 큐에 일시 저장된다.
콜백 함수나 이벤트 핸들러를 일시 저장한다는 점에서 태스크 큐와 동일하지만 마이크로태스크 큐는 태스크 큐보다 우선순위가 높다.
setTimeout(() => console.log(1), 0);
Promise.resolve()
.then(() => console.log(2))
.then(() => console.log(3));프로미스의 후속 처리 메서드(then, catch, finally)도 비동기적으로 동작하므로 1 > 2 > 3 의 순으로 출력될 것처럼 보이지만 2 > 3 > 1 순으로 출력된다.
프로미스의 후속 처리 메서드의 콜백 함수는 태스크 큐 가 아니라 마이크로태스크 큐 에 저장되기 때문이다.
태스크 큐와 마이크로태스크 큐 중 어떤 것이 먼저 실행되나요?
마이크로태스크 큐는 태스크 큐보다 우선순위가 높다.
따라서 이벤트 루프에서 마이크로태스크 큐에 쌓인 태스크를 먼저 콜 스택에 올려준 뒤, 태스크 큐에 잔여 태스크를 콜 스택에 올린다
REST API
📌 관련 주제: Promise
REST API가 뭔가요?
REST는 HTTP의 장점을 최대한 활용할 수 있는 아키텍처로서, HTTP 프로토콜을 의도에 맞게 디자인하도록 유도하고 있다.
즉, REST는 HTTP를 기반으로 클라이언트가 서버의 리소스에 접근하는 방식을 규정한 아키텍처고, REST API는 REST를 기반으로 서비스 API를 구현한 것을 의미한다.
REST의 기본 원칙을 성실히 지킨 서비스 디자인을 'RESTful'이라고 표현한다.
REST API의 구성은 어떤 것이 있나요?
REST API는 ① 자원(resource), ② 행위 (verb), ③ 표현 (representations) 의 3가지 요소로 구성된다.
| 구성 요소 | 내용 | 표현 방법 |
|---|---|---|
| 자원(resource) | 자원 | URI(엔드 포인트) |
| 행위(verb) | 자원에 대한 행위 | HTTP 요청 메서드 |
| 표현(representations) | 자원에 대한 행위의 구체적 내용 | 페이로드 |
REST API를 설계하는데 중요한 것이 있을까요?
REST에서 가장 중요한 기본적인 원칙은 두 가지다.
① URI는 리소스를 표현하는데 집중해야 한다
② 행위에 대한 정의는 HTTP 요청 메서드를 통해 해야 한다
위 두 규칙이 RESTful API를 설계하는 중심 규칙이다.
URI는 리소스를 표현해야 한다 URI는 리소스를 표현하는 데 중점을 두어야 한다. 리소스를 식별할 수 있는 이름은 ① 동사보다는 ② 명사를 사용한다.
따라서 리소스 이름에 get 같은 행위에 대한 표현이 들어가서는 안 된다.
# bad
GET /getTodos/1
GET /todos/show/1
# good
GET /todos/1HTTP 요청 메서드에 대해서 아는대로 얘기해보세요
| HTTP 요청 메서드 | 종류 | 목적 | 페이로드 |
|---|---|---|---|
| GET | index/retrieve | 모든/특정 리소스 취득 | x |
| POST | create | 리소스 생성 | o |
| PUT | replace | 리소스의 전체 교체 | o |
| PATCH | modify | 리소스 일부 수정 | o |
| DELETE | delete | 모든/특정 리소스 삭제 | x |
Promise
📌 관련 주제: 비동기 프로그래밍, 제너레이터와 async await, 에러
콜백이란 뭐라고 생각하나요?
자바스크립트에서 콜백 함수는 다른 함수의 매개변수로 함수를 전달하고, 어떠한 이벤트가 발생한 후 매개변수로 전달한 함수가 다시 호출되는 것을 의미합니다.
어떤 일을 다른 객체에게 시키고, 그일이 끝나는 것을 기다리지 않고 끝나고 부를 때까지 다른 일을 하는 것을 말합니다.
이 때문에 동기가 아닌 비동기적으로 처리되는 비동기 방식의 함수라고 할 수 있습니다.
프로미스가 뭔가요?
자바스크립트에서는 비동기 처리를 위한 패턴중 하나로 콜백 함수를 사용합니다.
전통적인 콜백 패턴은 일명 '콜백 헬'로 인해 가독성이 나쁘고 비동기 처리 중 발생한 에러의 처리가 곤란하며 여러 개의 비동기 처리를 한 번에 처리하는데 한계를 느꼈다.
프로미스는 ES6에서 도입된, 콜백 함수의 문제점인 비동기 처리를 해결하기 위한 또 하나의 패턴입니다.
// case 1 : 콜백 HELL 🔥
get('/step1', (a) => {
get(`/step2/${a}`, (b) => {
get(`/step3/${b}`, (c) => {
get(`/step4/${c}`, (d) => {
console.log(d);
});
});
});
});프로미스 생성 방법
Promise 생성자 함수를 new 연산자와 함께 호출하면 프로미스(Promise 객체)를 생성한다.
ES6에서 도입된 Promise는 호스트 객체가 아닌 ECMAScript 사양에 정의된 표준 빌트인 객체다.
Promise 생성자 함수는 비동기 처리를 수행할 콜백 함수를 인수로 전달받는데 이 콜백 함수는 resolve와 reject 함수를 인수로 전달받는다.
const promise = new Promise((resolve, reject) => {
if (/* 비동기 처리 성공 */) {
resolve('result');
} else { /* 비동기 처리 실패 */
reject('failure reason');
}
});Promise 생성자 함수가 인수로 전달받은 콜백 함수 내부에서 비동기 처리를 수행한다. 이때 비동기 처리가 성공하면 resolve를, 실패하면 reject를 호출한다.
프로미스의 상태를 나타내는 것은 어떤 것들이 있나요?
프로미스는 다음과 같이 현재 비동기 처리가 어떻게 진행되고 있는지를 나타내는 상태(state) 정보를 갖는다.
| 프로미스의 상태 정보 | 의미 | 상태 변경 조건 |
|---|---|---|
| pending | 비동기 처리가 아직 수행되지 않은 상태 | 프로미스가 생성된 직후 기본 상태 |
| fulfilled | 비동기 처리가 수행된 상태(성공) | resolve 함수 호출 |
| reject | 비동기 처리가 수행된 상태(실패) | reject 함수 호출 |
생성된 직후의 프로미스는 기본적으로 pending 상태다. 이후 비동기 처리가 수행되면 비동기 처리 결과에 따라 다음과 같이 프로미스의 상태가 변경된다

① fulfilled 또는 ② rejected인 상태를 settled 상태라고 한다. settled 상태는 fulfilled 또는 rejected 상태와 상관없이 pending이 아닌 상태로 비동기 처리가 수행된 상태를 말한다.
프로미스는 🔥 pending 상태에서 fulfilled 또는 rejected 상태, 즉 🔥 settled 상태로 변화할 수 있다.
하지만 일단 settled 상태가 되면 더는 다른 상태로 변화할 수 없다.
제너레이터와 async await
📌 관련 주제: Promise, 비동기 프로그래밍, 함수
제너레이터란 뭔가요? 일반 함수와는 어떤 차이가 있죠?
ES6에서 도입된 제너레이터(generator)는 코드 블록의 실행을 일시 중지 (블로킹) 했다가 필요한 시점에 재개할 수 있는 특수한 함수다.
제너레이터와 일반 함수의 차이는 다음과 같다.
① 제너레이터 함수는 함수 호출자에게 함수 실행의 제어권을 양도할 수 있다.
- 제너레이터는 함수 호출자가 함수 실행을 일시 중지시키거나 재개시킬 수 있다.
- 이는 함수의 제어권을 함수가 독점하는 것이 아니라 함수 호출자에게 양도(yield)할 수 있다는 것을 의미한다.
② 제너레이터 함수는 함수 호출자와 함수의 상태를 주고받을 수 있다.
- 제너레이터 함수는 함수 호출자에게 상태를 전달할 수 있고 함수 호출자로부터 상태를 전달받을 수도 있다.
③ 제너레이터 함수를 호출하면 제너레이터 객체를 반환한다.
- 제너레이터 함수를 호출하면 함수 코드를 실행하는 것이 아니라 이터러블이면서 동시에 이터레이터인 제너레이터 객체를 반환한다.
제너레이터의 구조
제너레이터는 ① yield 키워드와 ② next 메서드를 통해 실행을 일시 중지했다가 필요한 시점에 다시 재개할 수 있다.
일반 함수는 호출 이후 제어권을 해당 함수가 독점하지만, 제너레이터는 함수 호출자에게 제어권을 양도(yield)하여 필요한 시점에 함수 실행을 재개할 수 있다.
next 메서드를 통해 제너레이터를 실행할 경우, 코드 블록 내에 yield 키워드 뒤에 오는 표현식의 평가 결과를 제너레이터 함수 호출자에게 리절트 객체형식으로 반환한다.
{ value : , done : }코드 보기 📌
// 제너레이터 함수
function* genFunc() {
yield 1;
yield 2;
yield 3;
}
// 제너레이터 함수를 호출하면 제너레이터 객체를 반환한다.
// 이터러블이면서 동시에 이터레이터인 제너레이터 객체는 next 메서드를 갖는다.
const generator = genFunc();
console.log(generator.next()); // {value: 1, done: false}
console.log(generator.next()); // {value: 2, done: false}
console.log(generator.next()); // {value: 3, done: false}
console.log(generator.next()); // {value: undefined, done: true}async/await 가 뭔가요? 기존의 Promise와는 어떤 차이가 있죠?
async/await는 ES8에서 도입된 문법으로, 프로미스를 기반으로 동작하지만 then/catch 체이닝 없이 동기 코드처럼 비동기 처리를 작성할 수 있다. 에러 핸들링은 try/catch를 사용하며, Promise.all과 함께 사용하면 병렬 처리도 가능하다. 코드 가독성이 뛰어나 현대적인 비동기 처리의 표준으로 자리잡았다.
상세 설명 및 예제 코드 보기
async 함수
async 함수는 async 키워드를 사용해 정의하며 언제나 프로미스를 반환한다.
async 함수가 명시적으로 프로미스를 반환하지 않더라도 async 함수는 암묵적으로 반환 값을 resolve하는 프로미스를 반환한다.
await 키워드
await 키워드는 프로미스가 settled 상태(비동기 처리가 수행된 상태) 가 될 때까지 대기하다가 settled 상태가 되면 프로미스가 resolve한 처리 결과를 반환한다.
await 키워드는 반드시 프로미스 앞에서 사용해야 한다.
<body>
<pre></pre>
<script>
// async 사용!
async function fetchTodo() {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
const response = await fetch(url);
const todo = await response.json();
console.log(todo);
const result = JSON.stringify(todo, null, 2);
document.querySelector('pre').innerHTML = result;
// {userId: 1, id: 1, title: 'delectus aut autem', completed: false}
}
fetchTodo();
</script>
</body>async await로 구현할 경우 제너레이터의 성질을 갖기 때문에 항상 프로미스가 settled(이행된) 상태가 될 때까지 대기한다.
따라서 모든 코드에 async await를 남발하는 것은 도움이 되지 않는다
async await 키워드를 사용하는 경우
async function foo() {
const a = await new Promise((resolve) => setTimeout(() => resolve(1), 3000));
const b = await new Promise((resolve) => setTimeout(() => resolve(2), 2000));
const c = await new Promise((resolve) => setTimeout(() => resolve(3), 1000));
console.log([a, b, c]); // [1, 2, 3]
}
foo(); // 약 6초 소요된다.프로미스의 정적 메서드 Promise.all을 사용하는 경우
3개의 비동기 처리는 서로 연관이 없이 개별적으로 수행되는 비동기처리 이므로 앞선 비동기 처리가 완료될 때까지 대기해서 순차적으로 처리할 필요가 없다.
async function foo() {
const res = await Promise.all([
new Promise((resolve) => setTimeout(() => resolve(1), 3000)),
new Promise((resolve) => setTimeout(() => resolve(2), 2000)),
new Promise((resolve) => setTimeout(() => resolve(3), 1000)),
]);
console.log(res); // [1, 2, 3]
}
foo(); // 약 3초 소요된다.Promise와 async/await의 차이점 한 줄 요약
① 에러 핸들링
- Promise 를 활용할 시에는 .catch() 문을 통해 에러 핸들링을 해야 하지만,
- async/await 은 try / catch를 통해 에러를 처리할 수 있다
② 코드 가독성
- Promise의 후속 처리 메서드인 .then() 의 hell의 가능성
- async/await 은 프로미스의 후속 처리 메서드 없이 마치 동기 처리처럼 프로미스가 처리 결과를 반환하도록 구현할 수 있기 때문에 코드 흐름을 이해 하기 쉽다.
에러
📌 관련 주제: Promise, 제너레이터와 async await
에러처리를 왜 해야 하나요?
에러가 발생하지 않는 코드를 작성하는 것은 불가능하다고 볼 수 있다.
발생한 에러에 대해 대처하지 않고 방치한다면 프로그램을 강제 종료될 것이다.
따라서 try catch 문 등을 사용해 발생한 에러를 적절하게 대응하면 프로그래밍이 강제 종료되지 않고 계속해서 코드를 실행시킬 수 있다.
에러 캐치
에러를 처리를 하지 않을 경우
console.log('[Start]');
foo(); // ReferenceError: foo is not defined
console.log('[End]');// 터미널 💻
[Start]
/Users/leejunhee/junheeDB/CaptainP/upgrade_javascript/DEEPDIVE/error.js:3
foo(); // ReferenceError: foo is not defined
^
ReferenceError: foo is not defined
[End] 는 빛을 보지 못하고 종료되었다...에러 처리를 할 경우
console.log('[Start]');
try {
foo();
} catch (error) {
console.error('[에러 발생]', error);
// [에러 발생] ReferenceError: foo is not defined
}
// 발생한 에러에 적절한 대응을 하면 프로그램이 강제 종료되지 않는다.
console.log('[End]');[Start]
[에러 발생] ReferenceError: foo is not defined
at Object.<anonymous> (/...파일이 실행된 상대 경로)
at Module._compile (internal/modules/cjs/loader.js:1063:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
at Module.load (internal/modules/cjs/loader.js:928:32)
at Function.Module._load (internal/modules/cjs/loader.js:769:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
at internal/main/run_main_module.js:17:47
[End]자바스크립트에서 에러를 처리하는 방법에는 뭐가 있을까요?
- try catch finally
- Error 객체
- throw 문
Error 객체
Error 생성자 함수는 에러 객체를 생성한다.
Error 생성자 함수에는 에러를 상세히 설명하는 에러 메시지를 인수로 전달할 수 있다.
const error = new Error('invalid');자바스크립트는 Error 생성자 함수를 포함해 7가지의 에러 객체를 생성할 수 있는 Error 생성자 함수를 제공한다.
해당 함수가 생성한 에러 객체의 프로토타입은 모두 Error.prototype을 상속받는다.
| 생성자 함수 | 인스턴스 |
|---|---|
| Error | 일반적 에러 객체 |
| SyntaxError | 자바스크립트 문법에 맞지 않는 문을 해석할 때 발생하는 에러 객체 |
| ReferenceError | 참조할 수 없는 식별자를 참조했을 때 발생하는 에러 객체 |
| TypeError | 피연산자 또는 인수의 데이터 타입이 유효하지 않을 때 발생하는 에러 객체 |
| RangeError | 숫자값의 허용 범위를 벗어낫을 때 발생하는 에러 객체 |
| URIError | encodeURI 또는 decodeURI 함수에 부적절한 인수를 전달했을 때 발생하는 에러 객체 |
| EvalError | eval 함수에서 발생하는 에러 객체 |
throw 문
Error 생성자 함수로 에러 객체를 생성한다고 에러가 발생하는 것은 아니다.
즉, 에러 객체 생성과 에러 발생은 의미가 다르다.
try {
// 에러 객체를 생성한다고 에러가 발생하는 것은 아니다.
new Error('something wrong');
} catch (error) {
console.log(error);
}에러를 발생시키려면 try 코드 블록에서 throw 문으로 에러 객체를 던져야 한다
throw 표현식;try {
// 에러 객체를 던지면 catch 코드 블록이 실행되기 시작한다.
throw new Error('something wrong');
} catch (error) {
console.log(error);
}모듈
모듈이 뭔가요?
모듈(module)이란 애플리케이션을 구성하는 개별적 요소로서 재사용 가능한 코드 조각을 말한다.
일반적으로 모듈은 기능을 기준으로 파일 단위로 분리한다. 이때 모듈이 성립하려면 모듈은 자신만의 파일 스코프(모듈 스코프)를 가질 수 있어야 한다.
자신만의 파일 스코프를 갖는 모듈의 자산(모듈에 포함되어 있는 변수, 함수, 객체 등)은 기본적으로 비공개 상태다. 즉, 모듈은 개별적 존재로서 애플리케이션과 분리되어 존재한다.
하지만 애플리캐이션과 완전히 분리되어 개별적으로 존재하는 모듈은 재사용이 불가능하므로 존재의 의미가 없다.
따라서 모듈은 공개가 필요한 자산에 한정하여 명시적으로 선택적 공개가 가능하다. 이를 export 라 한다.
공개(export)의 자산은 다른 모듈에서 재사용할 수 있다. (의존성을 갖게 된다)
이때 공개된 모듈의 자산을 사용하는 모듈을 모듈 사용자(module consumer) 라 한다. 모듈 사용자는 모듈이 공개(export)한 자산 중 일부 또는 전체를 선택해 자신의 스코프 내로 불러들여 재사용할 수 있다. 이를 import 라 한다.

자바스크립트는 기본적으로 모듈이 성립하기 위해 필요한 파일 스코프와 import, export를 지원하지 않는다.
따라서 자바스크립트를 클라이언트 사이드, 즉 브라우저 환경에 국한하지 않고 범용적으로 사용하려는 움직임이 생기면서 이러한 상황에 제안된 것이 CommonJS 와 AMD(asynchronous module definition) 다.
가비지 컬렉션
자바스크립트의 가비지 컬렉션에 대해 알고 있나요?
자바스크립트 가비지 컬렉션(Garbage Collection, GC)은 메모리 관리를 자동화하여 개발자가 명시적으로 메모리를 할당하거나 해제할 필요 없이 메모리를 효율적으로 사용하는 기능입니다. 자바스크립트의 가비지 컬렉션은 주로 '참조 카운팅(Reference Counting)'과 '마크 앤 스위프(Mark-and-Sweep)' 알고리즘을 사용합니다.
1. 참조 카운팅(Reference Counting)
참조 카운팅 방식은 객체에 대한 참조(reference)를 추적합니다. 객체가 더 이상 참조되지 않을 때, 즉 참조 카운트가 0이 될 때 그 객체는 가비지 컬렉션의 대상이 됩니다.
장점:
- 간단한 구현
- 즉시 수거 가능
단점:
- 순환 참조(circular reference) 문제를 해결하지 못함. 예를 들어, 두 객체가 서로를 참조할 경우, 참조 카운트가 0이 되지 않아서 메모리 누수가 발생할 수 있습니다.
2. 마크 앤 스위프(Mark-and-Sweep)
현대 자바스크립트 엔진(V8, SpiderMonkey 등)은 주로 '마크 앤 스위프' 방식을 사용합니다. 이 방식은 두 단계로 작동합니다:
1. 마크 단계(Mark Phase):
- GC 루트(GC roots)에서 시작하여 도달 가능한 모든 객체를 '마크'합니다.
- GC 루트는 전역 객체, 현재 함수의 스코프 체인, 함수의 인수 및 지역 변수 등입니다.
- 도달 가능한 객체는 '활성'으로 표시됩니다.
2. 스위프 단계(Sweep Phase):
- 메모리를 순회하면서 마크되지 않은 객체를 수거(해제)합니다.
- 즉, 도달 불가능한 객체는 메모리에서 제거됩니다.
장점:
- 순환 참조 문제를 해결할 수 있습니다.
- 대부분의 경우 효율적으로 작동합니다.
단점:
- 메모리 사용량이 많아질 때까지 수집을 지연시킬 수 있음.
- 정지 시간을 유발할 수 있음 (즉, 가비지 컬렉션이 실행되는 동안 애플리케이션이 일시적으로 멈출 수 있음).
최적화 기법
현대 자바스크립트 엔진은 가비지 컬렉션을 최적화하기 위해 여러 기법을 사용합니다:
1. 세대별 수집(Generational Collection):
- 객체를 두 가지 '세대'로 분류합니다: 젊은 세대와 오래된 세대.
- 대부분의 객체는 짧은 생명 주기를 가지므로, 젊은 세대 객체를 자주 수집하고 오래된 세대 객체는 덜 자주 수집합니다.
2. 증분 수집(Incremental Collection):
- 긴 정지 시간을 피하기 위해, 가비지 컬렉션 작업을 여러 작은 단위로 나누어 수행합니다.
- 따라서 애플리케이션이 긴 시간 동안 멈추지 않습니다.
3. 동시 수집(Concurrent Collection):
- 애플리케이션 스레드와 별도로 가비지 컬렉션을 수행하여, 가비지 컬렉션 동안 애플리케이션이 계속 실행될 수 있게 합니다.
이러한 기법들은 자바스크립트 엔진이 메모리를 효율적으로 관리하면서도 성능에 미치는 영향을 최소화하는 데 도움을 줍니다.




