시작하기에 앞서...
F-Lab 멘토링 과정에서 기술 면접을 준비하며 적어둔 정보를 여기에 다시 작성해두려 한다. 프론트엔드 기술 면접에서 범용적으로 질문하는 것들에 대하여 적어두었으며, 범위가 상당하지만 모두 유기적으로 연결되어있는 내용이기도 하다. 자주 물어보는 내용이기도 하지만, 꼭 기술 면접이 아니더라도 본인이 이론적인 부분에 대하여 취약하다고 판단한다면 한번 공부를 해보는 것도 좋을 것 같다.
자바스크립트란?
자바스크립트는 클라이언트 사이드에서 동작하는 싱글스레드 프로그래밍 언어이다.
인터프리터 방식으로 동작하는 특징을 가지고 있다.
인터프리터 vs 컴파일러
인터프리터
코드가 순차적으로 런타임(코드가 실행되는 시점)에 실행되는 프로그램 실행 접근 방식 중 하나이다.
- 초기에 코드를 스캔하는 속도가 빠르다.
- 전체적인 실행 속도는 컴파일러보다 느리다는 단점을 가진다.
- 대표적으로는 파이썬, 자바스크립트, 루비가 있다.
컴파일러
- 컴파일 언어는 코드를 작성한 후 컴파일을 하면 컴파일러에 의해 기계어로 변환된 파일이 생성되고, 컴퓨터는 그 파일을 실행한다.
- 이미 기계어로 번역된 파일을 컴퓨터가 실행하기 때문에 스크립트 언어에 비해 빠르고 소스코드에 문법적 오류가 있으면 컴파일 에러를 발생시켜 디버깅이 수월하다.
- 대표적으로 자바가 이에 해당한다.
싱글 스레드와 멀티 스레드
자바스크립트는 기본적으로 싱글 스레드 프로그래밍 언어이다. 한 번에 하나의 작업만 수행이 가능하다.
하지만 오래 걸리고 반복적인 작업들은 브라우저 내부의 멀티 스레드인 Web APIs에서 비동기+논블로킹으로 처리할 수 있다.
비동기+논블로킹 : 메인 스레드가 작업을 다른 곳에 요청하여 대신 실행하고, 그 작업이 완료되면 이벤트나 콜백 함수를 받아 결과를 실행하는 방식을 말한다.
비동기로 동작하는 핵심 요소들은 자바스크립트가 아니라 브라우저 소프트웨어가 가지고 있다.
이벤트 루프
이벤트 루프는 브라우저의 동작을 제어하는 관리자 역할을 수행한다.
브라우저 내부의 call stack, callback queue, Web APIs등의 요소를 모니터링하며 비동기적으로 실행된는 작업들을 관리하고, 이를 순서대로 처리하여 프로그램의 실행 흐름을 제어한다.
- call Stack : 자바스크립트 엔진이 코드 실행을 위해 사용하는 메모리 구조
- heap:동적으로 생성된 자바스크립트 객체가 저장되는 공간
- Web APIs: 브라우저에서 제공하는 API 모음으로 비동기적으로 실행되는 작업들을 전담하여 처리한다. (Ajax 호출, 타이머 함수, DOM 조작)
- callback queue : 비동기적 작업이 완료되면 실행되는 함수들이 대기하는 공간
- Event Loop: 비동기 함수들을 적잘한 시점에 실행시키는 관리자
- Event Table: 특정 이벤트 (timeout click mous 등)가 발생했을 떄 어떤 callback 함수가 호출되어야 하는지를 알고있는 자료구조
Web APIs
- DOM : HTML 문서의 구조와 내용을 표현하고 조작할 수 있는 객체 (비동기)
- XML HttpRequest : 서버와 비동기적으로 데이터를 교환할 수 있는 객체 (비동기)
- Timer API: 일정한 시간 간격으로 함수를 실행하거나 지연시키는 메소드 제공 (비동기)
- Console API: 개발자 도구에서 콘솔 기능을 제공 (동기적)
- Canvas API: <canvas> 요ㅛ소를 통해 그래픽을 그리거나 애니메이션을 만들 수 있는 메서드 제공
- Geolocation API:웹 브라우저에 사용자의 현재 위치 정보를 얻을 수 있는 메서드를 제공
Callback Queue
- macrotask queue - setTimeout, setInterval, fetch, addEventlistner
- microtask queue - promise.then, process.nextTick, MutationObserver와 같이 우선적으로 비동기로 처리되는 함수들의 콜백 함수가 들어가는 큐
Callback Queue의 종류에 따라 이벤트 루프가 콜 스택으로 옮기는 순서가 달라진다.
예) microtask queue → macrotask queue 처리
이때 같은 queue에 적재된 콜백이라도 어떤 비동기 작업이냐에 따라 우선순위가 다를수 있다.
예) MutationObserver → promise.then
AnimationFrame Queue
브라우저 애니메이션 작업에 대한 처리를 담당하는 AnimationFrame Queue도 있다. 자바스크립트 애니메이션 동작을 제어하는 RequestAnimationFrame 메소드를 통해 콜백으로 등록하면 이 큐에 적재되어 브라우저가 Repaint 직전에 AnimationFrame Queue에 있는 작업들을 전부 처리한다.
스타일 관련 코드를 여기서 비동기처리하도록 구성하면 애니메이션 타이밍을 관리하고 적절한 프레임 속도를 유지하고 다른 탭이나 창에 있을 때 애니메이션을 중지하여 브라우저의 애니메이션 동작의 성능과 품질을 향상시킬 수 있다.
콜백 함수
콜백 함수란, 함수가 인자를 넘기면서 제어권도 함께 넘긴 함수를 말한다.
콜백 함수의 제어줜을 넘겨받은 함수는 해당 콜백 함수의 실행 시점을 결정한다.
eventHandler, setTimeout, SetInterval등이 있다.
비동기
promise
- 대기(pending) : 비동기 처리 로직이 완료되지 않은 상태를 말한다. promise 객체가 생성되고 비동기 작업이 진행 중인 상태
- 이행(fulfilled): 비동기 처리가 성공적으로 완료되어 promise가 결과 값을 반환한 상태. then() 메서드를 통해 처리 결과값 받을 수 있다.
- 거부(rejected) : 비동기 처리가 실패하거나 오류를 발생시킨 상태. catch를 통해 사유를 받을 수 있다.
const promise = new Promise((resolve, reject) => {
if (condition) {
resolve('resolved');
} else {
reject('rejected');
}
});
promise
.then((res) => {
console.log(res);
})
.catch((error) => {
console.error(error);
});
async function asyncFunc() {
try {
const result = await promise;
console.log(result);
} catch (err) {
console.error(err);
}
}
asyncFunc();
프로미스는 .catch를 통해 에러 핸들링이 가능하지만 async await은 try catch문을 사용해야 한다.
코드 길이가 길어질수록 async await이 가독성이 좋다.
promise.all(), promise.race()
promise.all() : 주어진 프로미스가 모두 이행되거나 하나의 프로미스가 거부될 때 까지 대기하는 동작 수행. 하나라도 거부되면 promise.all또한 즉시 거부된다.
모든 작업이 완료되어야만 하는 경우 사용
promise.race() : 주어진 프로미스가 하나라도 이행될 때 까지 대기하는 동작을 수행한다. 주어진 프로미스들 중 가장 먼저 완료된 것을 토대로 이행하거나 거부한다. 먼저 이행되거나 먼저 거부된 결과에 따라 promise.race 결과를 결정한다.
가장 빠른 API 응답을 사용하고자 할 때 사용
async, await
콜백 함수를 사용할 경우, 콜백 지옥에 빠지기 쉽다.
들여쓰기로 구분하지 못할 만큼 코드가 길어질 경우, promise나 async await과 같은 비동기 제어를 통해 가독성 있는 코드를 작성할 수 있다.
async function f() {
return 1;
}
f().then(alert); // 1
function 앞에 async를 붙이면 해당 함수는 항상 프라미스를 반환합니다. 프라미스가 아닌 값을 반환하더라도 이행 상태의 프라미스(resolved promise)로 값을 감싸 이행된 프라미스가 반환되도록 합니다.
thenable(댄너블)이란?
모든 promise 객체는 thenable이지만 역은 성립하지 않는다.
then 메소드를 가지는 객체는 모두 thenable이라고 뜻한다.
- then이라는 메서드가 반드시 존재
- 해당 결과에 대한 처리 완료와 처리 거부를 각각 처리할 두 개의 콜백 함수를 인자로 받아야 함
- then 메서드는 새로운 Promise를 반환해야 한다. 이 promise는 then메서드가 호출된 시점에서 항상 대기 상태이다.
let myThenable = {
then: function(onFulfilled, onRejected) {
onFulfilled('Hello, I am a thenable!');
}
};
Promise.resolve(myThenable)
.then(value => console.log(value)); // 'Hello, I am a thenable!'
어떤 작업을 수행하고 그 작업이 완료되면 어떤 일을 하겠다는 것을 표현할 때 thenable을 사용
비동기 병렬 처리
- promise.All()을 활용한 비동기 병렬 처리
Promise.all([requestData1(), requestData2()])
.then([data1, data2] => { ... });
- 하나라도 거부되면 rejected됨
- async await을 활용한 비동기 병렬 처리
async function getData() {
const p1 = asyncFunc1();// promise 객체 안 코드 바로 실행
const p2 = asyncFunc2();
const data1 = await p1();// 각각의 프로미스가 완료될 때 까지 기다림
const data2 = await p2();
//...
};
- 프로미스는 생성과 동시에 비동기 코드가 실행되기 때문에 두 개의 프로미스를 먼저 생성하고 await을 통해 나중에 실행할 코드를 병렬로 처리
이때 프로미스란 비동기 작업이 미래에 완료되거나 실패할 것이라는 약속을 나타냄
위의 코드를 더 간단히 줄일 수 있다.
aync function getData(){
const[data1,data2] = await Promise.all([asyncFunc1(),asyncFunc2()]);
}
타입
기본형 참조형
숫자 | object |
문자 | Date |
undefined, null | map, set |
symbol | WeakMap, WeakSet |
객체
자바스크립트에서 객체란 key와 value로 이루어진 property의 집합이다.
객체가 생성되는 과정
- 식별자를 저장하는 변수 메모리 공간과 객체 변수를 저장할 메모리 공간에 객체의 프로퍼티 주소를 담은 뒤, 프로퍼티의 값에 대한 주소를 객체 변수 공간에 매핑하고 객체 변수를 저장한 메모리 공간의 주소를 식별자와 매핑하는 과정을 통해 객체가 생성됨
object.create
- 기존의 객체를 생성할 경우 자동으로 프로토 타입 내부에 내장 함수까지 모두 담겨 생성이 되는데, object.create를 활용하면 내장함수가 모두 없는 상태로 생성이 된다.
- 기본적인 함수 지원이 불가하지만 메모리 측면에서 가벼운 객체를 만들수 있다는 특징이 있다.
불변성
객체가 생성된 이후, 그 상태를 변경할 수 없는 것을 말한다.
데이터 영역 메모리 주소가 변경되지 않는 것을 말한다.
실질적으로 참조형 객체에서 객체가 바뀔 경우 불변성을 확보했다고 볼 수 있지만, 참조 객체 내부의 프로퍼티가 변경될 경우에는 가변적이다.
얕은 복사, 깊은 복사
얕은 복사는 데이터의 최상위 레벨만 복제한다. 때문에 참조 객체의 내부 프로퍼티 데이터가 변경될 경우, 복사된 사본에도 영향을 미친다.
깊은 복사는 데이터의 구조 모든 레벨을 복사하여 완전히 새로운 사본을 만든다. 때문에 원본 데이터가 변경되더라도 사본에 영향을 주지 않는다.
깊은 복사 방법
- JSON 객체와 같은 경우, 문자형으로 바꿨다가 다시 JSON 객체로 만들어주는 방법을 사용할 수 있다.
- ES6에서는 spread operator를 사용해 복사를 할 수 있지만 깊이가 1인 객체에서만 깊은 복사가 일어난다.
- 리액트에서 자주 사용되는 복사 방법이다. 이를 통해 불변성을 유지하고 재랜더링을 감지한다.
null, undefined
- 둘 다 실질적으로 자바스크립트에서 ‘없음’을 나타내는 것은 동일하다.
- undefined는 자바스크립트 엔진에서 직접 반환해주는 값이라는 차이가 존재한다.
- 핸들링 가능한 오류 상황일 경우 null을 사용함으로써 이외의 경우에 undefined가 호출될 수 있도록 구분하는 것에 사용 가능하다.
대표적으로 undefined는 아직 데이터가 할당되지 않은 식별자를 호출하려 할 때 발생한다.
Symbol
ES6에서 추가된 기본형 중 하나이다. 심볼은 생성될 때 마다 고유한값을 가진다는 특징을 가진다.
- 변경 불가능한 원시값 → 키값을 생성하는데 자주 사용됨
- 어디서든 접근 가능한 전역 레지스트리를 생성하기 위해
- 고유한 속성 키주로 객체의 프로퍼티키 충돌을 방지하기 위해 사용된다.
- let id = Symbol("id"); let user = { name: "John", [id]: 123 // "id"는 "123"이라는 고유한 값을 가집니다. };
- 전역 심볼 레지스트리어디서든 접근 가능하며 유일무이한 상수를 만들고자 할 때 적합
- let id = Symbol.for("id"); // 심볼이 존재하지 않으면 새로운 심볼을 생성합니다. let idAgain = Symbol.for("id"); // 동일한 이름으로 심볼을 다시 읽는다. console.log(id === idAgain); // true, 동일한 심볼이다.
- 잘 알려진 심볼
특별한 내장 심볼이다. 자바스크립트의 핵심 동작을 나타내고 심볼을 통해 사용자 정의를 할 수 있다.
잘 알려진 심볼은 자바스크립트 내장 작업에 대한 일종의 프로토콜로 작동 → 사용자가 언어 동작을 변경하거나 정의할 수 있다.
let myIterable = {
[Symbol.iterator]: function* () {
yield 1;
yield 2;
yield 3;
}
};
for (let value of myIterable) {
console.log(value); // 출력: 1, 2, 3
}
BigInt
자바스크립트에서는 0.1+0.2=0.3이 성립하지 않는다. 이는 0.1을 2진수로 계산하고 덧셈을 수행할 경우, 아주 미세한 오차가 발생하기 때문이다. 자바스크립트에는 float나 integar같은 선언자 타입이 없다
0.1+0.2가 0.3이 되게 하려면
- toFixed()를 활용하여 소숫점 자리를 결정해주는 방법
- 소숫점 자리만큼 10을 곱하고 계산한 뒤 곱한 10의 제곱만큼 나눠주는 방법
이렇게 메모리에 모든 수를 저장할 수 없을 경우가 생긴다. BigInt는 2^53-1보다 큰 정수를 표현할 때 사용되는 내장 객체이다. 표현하고자 하는 숫자 뒤에 n을 붙이거나 BigInt()를 호출하여 사용 가능하다.
- 소수점 이하는 저장하지 않는다.
- number타입과 연산 불가하다
- math와 같은 내장 메서드는 사용 불가하지만 기본 연산은 가능하다.
아주 큰 수는 어떻게 담아야 하는 걸까?
문자로 변환하면 된다.
WeakMap, WeakSet
먼저, 자바스크립트는 기본적으로 추후 사용될 가능성이 있는 데이터의 메모리를 유지시킨다는 특징이 있다.
기존의 map이나 set은 해당 객체를 참조하는 식별자가 없더라도 메모리에서 삭제되지 않는다. (활용 가능성이 있을 경우 가비지 콜렉터의 대상이 되지 않는다)
→ 효율적인 메모리 활용을 위해 weakmap을 사용하면 해당 객체를 참조하는 식별자가 없을 경우 곧바로 메모리에서 지워주는 역할을 수행한다.
map
- 객체이다.
- key: value 쌍의 순서가 기억된다.
- key는 중복될 수 없다. ("하나의 key에 하나의 value")
- key로 사용 가능한 data type: any value(string, number, symbol, object, function, boolean, etc).
- 요소의 삽입 순서대로 원소를 순회한다.
- for ... of 반복문은 각 순회에서 [key, value]로 이루어진 배열을 반환한다.
weakmap
- key는 반드시 object여야 한다. (다른 primitive data 사용 불가)
- Iteration, keys(), values(), entries() method를 지원하지 않기 때문에 key나 value 전체를 얻는 것이 불가능하다.
- addtional data를 저장할 때 유용하다.
- key로 사용된 object를 참조하는 것이 아무것도 없다면, 해당 object는 메모리와 WeakMap에서 자동으로 삭제된다.
- chaching 작업에 유용하다.
process(obj)를 여러 번 호출하면 최초 호출할 때만 연산이 수행되고, 그 이후엔 연산 결과를 cache에서 가져옵니다. 그런데 맵을 사용하고 있어서 객체가 필요 없어져도 cache를 수동으로 청소해 줘야 합니다.
맵을 위크맵으로 교체하면 이런 문제를 예방할 수 있습니다. 객체가 메모리에서 삭제되면, 캐시에 저장된 결과(함수 연산 결과) 역시 메모리에서 자동으로 삭제되기 때문입니다.
set
- 배열과 비슷하다.
- 저장되는 data type은 object이다.
- 값이 중복될 수 없다.
- 삽입 순서대로 요소를 순회할 수 있다.
weakset
- WeakSet 객체는 object 를 저장하는 집합입니다.
- WeakSet 내부의 객체는 한번만 생성될 수 있습니다.
- WeakSet 의 집합 안에서 고유하며(중복된 객체가 없다),
- 순회할 수 없다.
실행 컨텍스트와 this
실행 컨텍스트
함수가 실행될 때의 맥락이라고 할 수 있으며 호출하고 있는 함수의 소유자이다.
코드를 실행하는데 필요한 환경정보를 모아둔 객체이다.
- 전역 코드 실행
- 함수 호출
- eval 함수 실행
스코프란 무엇인가?
클로저에 스코프의 개념이 포함됨 → 클로저를 설명할 때 스코프의 개념을 함께 설명하는 것도 좋음
- 자바스크립트 스코프는 변수의 유효 범위를 의미한다.
- 함수 단위의 유효범위 - 함수 코드 블럭 내에서 선언된 변수는 함수 코드 블럭에서만 유효
- 변수명 중복 허용 - 글로벌 영역에 변수를 선언하면 이 변수는 어느 곳에서든지 참조할 수 있는 global scope를 가진 전역 변수가 된다.
- 암묵적 선언 - 변수 앞에 var을 붙여주지 않으면 암묵적으로 전역변수가 된다.
- Lexical scoping(Static Scoping) - 함수가 선언된 시점에서 생성되는 변수 유효 범위
- 스코프는 전역 스코프와 지역 스코프로 나뉘며, 전역 스코프는 코드 어디에서든 접근 가능한 스코프이고, 지역 스코프는 함수 내부에서 선언된 변수가 해당 함수 내부에서만 접근 가능한 스코프이다.(ES6부터 let을 사용하면 block 단위 스코프 사용이 가능해졌다.)
Lexical Scope
함수가 영향을 미치는 범위, 혹은 공간
자바스크립트에서 변수와 함수를 정의할 때 해당 변수와 함수가 속한 스코프의 정보를 저장하는 객체이다.
실행 컨텍스트 생성 → 렉시컬 스코프 생성
구성
- 환경 레코드(Environment Record) - 스코프 내의 변수와 함수 정보를 저장
- 외부 렉시컬 환경 (Outer Lexical Environment) - 햔재 렉시컬 스코프보다 더 상위의 렉시컬 스코프 참조하는 링크
Lexical Scoping
함수를 어디에 선언하였는지에 따라 상위 스코프가 결정되는 방식을 말한다.
- 함수를 선언할 때 함수 내부의 변수가 자기 스코프로부터 가장 가까운 곳에 있는 변수를 계속 참조하게 됨. (호출이 아닌 선언에 따라 결정)
Lexical Scope
- 자신이 속한 환경
- 스코프 체인이 바인딩한 객체
스코프체인(scope chain)
스코프체인은 자바스크립트에서 변수를 찾을 떄 해당 변수가 선언된 스코프에서부터 시작하여 상위 스코프로 이동하여 변수를 찾는 과정 → 전역 스코프와 중첩된 함수의 스코프의 레퍼런스를 차례로 저장하는 리스트
- 함수 내부에서 선언된 변수는 해당 함수 안에서만 사용이 가능하다. → 지역 스코프
- 외부에서 선언된 변수는 전역 스코프
- 스코프 체인은 이러한 지역 스코프와 전역 스코프를 연결하는 역할 수행
- outer 참조 값이 상위 스코프의 lexical Environment를 가리키고 있어서 체인처럼 연결됨
스코프 체인 검색 과정
- 현재 실행 컨텍스트의 Lexical Environment의 EnvironmentRecord에서 식별자 검색
- 없으면 outer 참조값으로 상위 스코프의 environmentRecord에서 식별자 검색
- outer 참조값이 null일 때 까지 계속 찾다가 없으면 에러 발생
클로저
함수가 선언된 환경의 lexical Scope를 기억하여 함수가 스코프 밖에서 실행될 때에도 이 스코프에 접근할 수 있게 한다.
회부함수의 실행이 끝나 외부함수가 소멸된 이후에도 내부 함수가 외부 함수에 접근이 가능하다.(데이터 지속성)
클로저를 왜 쓰는가?
- 함수가 선언된 후 종료된 이후에도 외부에서 함수가 호출될 때 해당 함수 내부에 있는 변수들을 선언 당시의 환경 그대로 사용하기 위해서(데이터 은닉)
- 클로저는 내부에서 외부 스코프의 참조가 가능하지만, 외부에서는 내부를 참조하는 것이 불가능하기 때문에 접근 제어를 할 수 있다
내부함수가 종료된 이후에도 외부 함수에서 내부 함수에 접근하여 해당 내부 함수가 선언되었을 당시의 환경 정보를 사용하기 위해서
접근자 제어 - 클로저는 외부에서 내부 변수를 직접 참조하는 것이 불가능하다.
클로저를 사용한 모듈
디바운스(Debounce)와 스로틀(Throttle ) 그리고 차이점
디바운스
짧은 시간 동안 동일한 이벤트가 많이 발생할 경우에 이를 전부 처리하지 않고 마지막 혹은 가장 처음 발생 이벤트 한 번만 처리하는 것 → 프론트엔드 성능 최적화에 도움을 줌
예) 버튼이 더블클릭 될 때 모두 API를 호출하는 것이 아닌, 마지막 클릭 한 번만 API를 호출할 수 있도록 처리
function debounce(func, delay) {
let timeoutId;//외부 변수
return function(...args) {
if (timeoutId) {//내부 함수에서 외부 변수 참조
clearTimeout(timeoutId);//메모리 관리
}
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
스로틀
이벤트를 일정 주기마다 발생시키는 기술. 마지막 함수가 호출된 후 일정 시간이 지나기 전까지 다시 호출하지 않도록 한다.
대표적으로 무한 스크롤 이벤트가 있다.
function throttle(func, delay) {
let timeoutId;// 외부 변수
return function(...args) {
if (!timeoutId) {// 내부 함수에서 외부 변수 참조
timeoutId = setTimeout(() => {
func.apply(this, args);
timeoutId = null;//메모리 관리
}, delay);
}
};
}
사용자가 빠르게 스크롤을 하더라도 계속해서 API를 호출하는 것이 아닌, 1초에 한 번만 API가 호출되도록 제약을 걸 수 있다.
두 가지 모두 이벤트의 호출 빈도를 줄여 성능 개선을 할 수 있게 함
호이스팅
변수 식별자와 함수 선언을 코드의 가장 위로 끌어올리는 것이다.
데이터할당은 끌고오지 않기 때문에 끌어올려진 변수명과 함수 선언부를 실행시키고 순차적으로 실행된다.
왜 일어나는가? - 자바스크립트의 동작 과정
- 소스코드 평가 - 선언문으로 작성된 변수와 함수를 실행하여 식별자를 키로 하여 실행 컨텍스트가 관리하는 환경에 등록
- 소스코드 실행 - 평가 이후 소스코드 순차적 실행 (런타임 시작) → 어떤 변수나 환경을 만나면 실행 컨텍스트가 관리하는 환경에서 검색하여 참조
이로 인해 호이스팅 발생(실행 컨텍스트가 관리하는 환경에 저장된 정보를 참조)
호이스팅과 실행 컨텍스트의 연관관계
실행 컨텍스트 - 자바스크립트 엔진이 코드를 실행할 때 변수와 함수의 실행 정보를 저장하는 객체
호이스팅 - 위의 저장된 변수와 함수 식별자 정보가 참조됨에 따라 발생하는 변수명과 함수 선언이 코드의 가장 위로 끌어올려지는 현상
실행 컨텍스트의 환경 레코드로 인해 호이스팅이 일어난다
호이스팅은 왜 문제 되는가?
함수 선언과 같은 경우 선언된 함수가 통째로 끌어올려지기 때문에 a라는 사람이 함수를 선언하고 b라는 사람이 나중에 같은 이름의 함수를 선언하게 되면 코드 전반에 영향을 미칠 수 있다. → 사이드 이펙트를 발생시킬 가능성이 있다.
TDZ(Temporal Dead Zone)
일시적 사각지대라는 뜻으로 변수를 사용하는 것을 비허용하는 개념상의 공간을 의미(스코프 시작~초기화 사이 구간)
- 변수가 선언되고 초기화가 이루어지기 직전까지의 공간을 말한다.
- 선언되지 않거나 초기화 전인 변수를 참조하게 되면 에러를 발생시킴
- let, const, class가 tdz 구간에 영향을 받음
- function(함수 선언식). var, import 구문 영향 받지 않음
변수 생성 단계
- 선언 단계 - 변수를 실행 컨텍스트의 변수 객체에 등록
- 초기화 단계 → 실행 컨텍스트에 등록한 변수를 위한 메모리를 만드는 단계 → undefined 할당
- 할당 단계 → 사용자가 undefined로 할당된 변수에 다른 값을 할당
var와 let
스코프에서 차이가 남. 기존 ES5와 같은 경우 전역 변수 혹은 함수 실행때만 스코프가 생성되고 var로 선언된 변수들은 전역 변수와 같이 동작했는데 ES6부터는 let과 const를 사용해서 const 또는 block을 사용해 스코프를 생성할 수 있게 되었다.
var로 선언된 변수는 함수 범위를 가지고 있으며 호이스팅을 발생시킨다. 때문에 변수 선언 전에 해당 변수를 사용해도 오류가 발생하지 않고 undefined로 값이 초기화된다. 반변 let으로 선언된 변수는 블록 범위를 가지고 있고 호이스팅을 발생시키지 않아 선언 전에 사용하려고 하면 에러를 발생시킨다(TDZ 영향을 받음)
let도 사용을 지양해야 하는 이유
상황에 따라 다름
- const를 사용하면 코드의 가독성이 향상됩니다. 이는 변수의 값이 다른 곳에서 재할당되지 않을 것이라는 것을 나타내므로, 코드를 읽고 이해하는 데 도움이 된다.
- const를 사용하면 변수의 값이 의도치 않게나 일부러 재할당되는 것을 방지할 수 있습니다. 이는 버그나 악성 코드가 변수의 값을 변경하는 것을 방지할 수 있다.
- const는 변수의 값이 변경되지 않을 것이라는 것을 자바스크립트 엔진이 알고 있기 때문에 코드를 최적화하는 데 도움이 된다.
this란?
객체지향 프로그램에서 this는 클래스 기반으로 생성된 인스턴스를 말한다.
때문에 class 내부에서만 사용될 수 있다는 특징을 지니는데 자바스크립트와 같은 경우 this가 클래스 내부 뿐만 아니라 모든 곳에서 사용될 수 있다는 특징을 가지고 있다.
실행컨텍스트의 소유자를 가리키는 역할을 수행한다.
실행컨텍스트가 실행되는 순간 this가 정해진다.
this가 사용될 수 있는 모든 상황에는 어떤 것이 있는가?
- 일반 브라우저 환경에서 전역객체를 가리키는 상황. window, global
- 메소드로 호출될 경우, 해당 객체명을 가리키는 경우
- 클래스에서 클래스를 기반으로 생성된 인터페이스를 가리키는 경우
- bind, call, apply를 통해 직접적으로 this가 무엇을 가리킬지 지정하는 경우
함수 내에서 this가 가리키는 것?
- 함수가 메서드로 불릴 경우, 해당 메서드를 호출한 객체
- 전역 공간에서 함수가 호출될 경우 this
- 생성자 함수로 호출될 경우, 생성자 함수에 의해 새로운 객체를 가리킴
- 화살표 함수로 호출될 경우, 상위 스코프의 this를 상속받음
명시적으로 this를 바인딩 하는 방법
call, apply, binding 세 가지 방법이 있다.
- call은 첫번째 인자를 this로, 두 번째 인자부터 함수에 넘길 매개변수로 받는다.
- apply는 첫번째 인자를 this로, 두 번째는 배열로 넘긴다.
- this는 직접적으로 this가 가리킬 객체를 명시할 수 있다.
- 암시적 바인딩은 명시적 이외의 모든 상황이다.
this와 실행 컨텍스트는 어떤 관계인가
- 먼저 실행 컨텍스트는 코드를 실행하기 위해 필요한 환경 정보를 모아놓은 객체이자 코드를 실행할 때의 맥락입니다. 이때 해당 코드를 실행 컨텍스트가 실행할 때의 소유자를 가리키는데 this를 사용할 수 있습니다. 때문에 실행컨텍스트가 전역에서 실행될 경우 this는 전역 객체를 바라보고, 실행 컨텍스트가 함수 메서드로서 호출될 경우, 해당 메서드를 호출한 객체를 가리키게 됩니다.
내용 정리 : 실행컨텍스트는 코드가 실행되기 위한 환경정보를 모아둔 객체이고, this는 해당 실행 컨텍스트를 호출한 객체를 가리킨다.
자바스크립트에서 this는 함수가 어떻게 호출되었는지를 참조하는 특별한 식별자다. 함수가 어떻게 호출되었는가에 따라 this의 값이 달라진다.
프로토타입
JavaScript : 프로토타입(prototype) 이해
기존 객체를 복사하여 새로운 객체를 만드는 방식을 말한다.
- 프로토타입 객체 - 함수를 정의하면 동시에 생성되는 객체로 함수로부터 생성된 모든 객체의 원형(prototype)
- 프로토타입 링크 - 객체를 생성하는데 사용된 원형인 프로토타입 객체를 참조하는 링크(__ proto__)
프로토타입에서 객체 지향 이야기가 나오는 이유
객체 지향
객체의 상태를 나타내는 데이터와 상태 데이터를 조작할 수 있는 하나의 논리적인 단위로 묶어 생각하는 프로그래밍 패러다임
객체의 상태 데이터를 프로퍼티, 동작을 메서드라고 한다.
자바스크립트 프로토타입
자바스크립트는 프로토타입 기반 객체 지향 언어이다.
이때 프로토타입은 객체간 상속을 위해 사용된다.
프로토타입 체인은 상속과 프로퍼티 검색을 위한 메커니즘이며, 자바스크립트 엔진은 객체의 프로퍼티에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 프로토타입 체인을 따라 상위 객체의 프로퍼티를 순차적으로 검색한다.
클래스 기반 객체 지향과 프로토타입 기반 객체 지향의 차이점
클래스 기반 객체 지향은 클래스를 통해 객체의 형태와 기능을 정의하고 생성자를 통해 인스턴스를 만든다.
프로토타입 기반 객체 지향은 클래스라는 개념이 없으며, 객체의 원형인 프로토타입 객체를 이용하여 객체를 생성하고 프로토타입 체인을 통해 상속을 구현한다.
프로토타입에는 상속이 없는가? 프로토타입 상속이란?
- 프로토타입은 기본적으로 클래스를 기반으로 하는 상속이 아닌 참조의 형태로 상속이 이루어지게 됩니다. 때문에 프로토타입 상속과 같은 경우에는 프로토타입 체인을 통해 하위에 있는 프로토타입이 상위에 있는 프로토타입을 참조하는 형태로, 상위에 있는 메소드와 값들을 상용할 수 있는 구조로 되어 있다.
프로토타입의 상속은 프로토타입 체인을 따라 상위 프로토타입의 속성과 메서드를 하위 프로토타입이 사용할 수 있게 되는 것을 말한다.
프로토타입 체인을 통해 상속받은 속성을 상속 프로퍼티라고 한다.
prototype과 __ proto__의 차이점
__ proto__는 상위 프로토타입의 prototype객체와 모든 프로퍼티 메소드를 상속받는다. 프로토타입 체인을 통해 메서드를 검색할 때 실질적으로 사용되는 객체이다.
prototype은 new로 새로운 인스턴스를 생성할 때 __ proto__를 생성하는데 사용하는 객체이다.
- prototype: 모든 함수는 prototype이라는 속성을 가지고 있습니다. 이 속성은 함수가 생성자로 사용될 때 해당 함수를 통해 생성된 객체의 프로토타입 객체를 지정합니다.즉, **prototype**은 함수 객체만 가지고 있으며, 생성자를 가지는 원형으로 선언할 수 있다.
- proto: 반면에 모든 객체는 **__proto__라는 속성을 가지고 있다. 이 속성은 객체가 생성될 때 자신을 생성한 생성자 함수의 prototype 속성을 참조한다. __proto__**는 모든 객체가 가지고 있으며, 하나의 링크로 볼 수 있습니다.
Class 와 함수
클래스도 엄밀히 말하면 함수다.
- 가장 큰 차이는 호이스팅 여부다. 함수는 호이스팅이 되지만, 클래스틑 TDZ의 영향을 받기 때문에 호이스팅이 되지 않는다.
- 클래스는 new 키워드를 통해 생성하지 않으면 에러가 발생한다.
- 클래스는 constuctor, static, 프로토타입 메서드로 구성된다.
Class
클래스 내에서 static 키워드가 어떻게 동작하는가?
- 일반적으로 스태틱으로 정의된 함수와 같은 경우 생성자를 호출해서 해당 스테틱 함수를 사용할 수 있다.
- 때문에 상속이 이뤄지고 서브 클래스가 동작할 때 스태틱 메서드는 생성자를 통해서만 호출가능하지만 메서드로 정의된 함수는 서브클래스가 자신의 함수처럼 부를 수 있다는 특징을 가진다.
static 키워드는 어떨 때 쓰면 좋을까?
- 자식 클래스에 상속하고 싶지 않은 별도의 함수를 정의할 때
스태틱 키워드가 어떻게 생성자를 통해 접근할 수 있는가?
자바스크립트는 프로토타입 기반 언어이기 때문에 상속 또한 프로토타입 체인에 의해 동작합니다. 이때 부모 클래스는 자식 클래스에 메서드와 속성을 상속할 수 있지만static 메서드를 상속하지 않기 때문에 자식 클래스 인스턴스로는 해당 static에 접근할 수 없지만, 자식 클래스 내부에 있는 this.constructor를 사용하여 부모 클래스 자체를 참조할 수 있습니다. 이를 활용하여 부모 클래스 내부에 있는 static 메서드에 접근이 가능합니다.
super() 키워드에 대하여
서브 클래스의 생성자 내에서 사용되며, 부모 클래스의 생성자를 호출하기 위해 사용
- 부모 클래스에 정의된 생성자를 자식 클래스에서 동일하게 상속받아 사용하기 위해 일반적으로 사용된다.
- 상위 클래스에 사각형을 정의하는 컨스트럭터가 있고, 하위 클래스에서 정사각형이라는 생성자를 생성할 때 super 키워드를 사용하여 생성자를 만들 수 있습니다.
인스턴스
클래스를 기반으로 생성된 객체가 메모리에 할당된 경우
자바스크립트에서는 프로토타입을 참조하여 새로운 객체를 만들 경우 인스턴스라고 함
오버라이딩이란 무엇인가?
오버라이딩이란 상속 관계에 있는 부모 클래스에서 이미 정의된 메소드를 자식 클래스에서 같은 이름을 가진 메소드로 다시 정의를 하는 것이다.
- 메소드 명이 같아야 한다.
- 매개변수가 같아야 한다.
- 리턴 타입이 같아야 한다.
오버라이딩은 왜 쓰는가?
- 코드 단순화 - 비슷한 기능을 수행하는데 다른 이름으로 정의하면 코드가 복잡해질 가능성이 있음
- 유지보수 용이성 - 기능의 확장과 객체의 수정이 유연하다
- 재사용성 증가 - 객체를 재사용하기 쉬워진다.
- 느슨한 결합 - 클래스의 의존성이 낮아져 확장성이 높고 결합도가 낮은 코드 가능
예를 들어 상위 클래스에 있는 메서드를 하위 클래스에서 상속받았을 때, 함수의 이름과, 인자, 리턴값이 동일한 조건에서 일부 추가적인 변경이 요구될 때 굳이 함수를 새로 작성하는 것 보다 기존의 함수를 활용하는 것이 코드 관리와 개발에 더 효율적이다.
함수
일급함수
함수를 변수와 같이 다른 함수의 인자로 할당하거나 변수에 할당하는 등 일반적인 값과 같이 사용할 수 있는 프로그래밍 언어의 특징이다.
new.target이란 무엇인가
Javascript 스코프 세이프 생성자 패턴과 new.target
함수 또는 생성자가 new 연산자를 사용하여 호출되었는지를 감지할 수 있다.
생성자 함수가 new 연산자 없이 호출되는 것을 방지하기 위해 사용된다.(ES6부터 나온 문법)
new 연산자와 함께 생성자 함수로 호출되면 함수 내부의 new.target은 함수 자신을 가리킨다. 아닐 경우에는 undefined
왜 new 연산자로 호출되었는지가 중요할까?
- 생성자 함수의 적절한 사용 강제 : 일반적으로 생성자 함수는 new 연산자와 함께 사용되어야 하며, 그렇지 않을 경우 오류를 발생시키거나 다른 동작을 수행할 수 있다.
- 생성자 함수와 일반 함수를 구분하기 위해 : 함수가 생성자로서 new로 호출되었는지 아닌지를 구분 가능
기타
promise.All()을 하고 실패할 경우 어떻게 처리할 것인가?
- catch 블록을 사용하여 에러를 처리하는 방법(한꺼번에 처리)
- promise.all은 첫 번째로 거부된 프로미스에서 실채하며, 이후의 프로미스는 무시된다.
Promise.all([promise1, promise2]) .then(results => { // 모든 프라미스가 성공적으로 이행되었을 때 실행됩니다. }) .catch(error => { // 첫 번째로 거부된 프라미스에서 실행됩니다. console.error(error); });
- 프로미스 all 안의 개별적 프로미스에 대한 에러 처리
- 각 프로미스에 대한 catch 블록을 추가하여 개별적으로 에러를 처리할 수 있다.
const promises = [promise1, promise2].map(p => p.catch(error => null)); Promise.all(promises) .then(results => { // 모든 프라미스가 성공적으로 이행되거나 거부되었을 때 실행됩니다. });
비동기 api 처리를 실패할 경우 어떻게 처리할 것인가?
- async await일 경우, try catch문을 통해 예외가 발생하여 api 처리에 실패할 경우 해당 에러에 대한 내용을 콘솔에 출력하도록 한다. 에러가 발생한 이후에도 반드시 처리되어야 할 경우 finally로 처리한다.
- promise는 예외 사항이 발생할 경우, catch 블록을 통해 해당 에러에 대한 내용을 콘솔로 출력시킬 수 있다. 동일하게 finally() 사용이 가능하다
실패 시 에러 로깅은 어떻게 하고 있는가?(정답 없음)
에러 로깅
실행 중인 자바스크립트 애플리케이션에서 발생하는 런타임 에러를 캡쳐하고 기록하는 과정
에러 로깅을 통해 개발자는 무엇을 잘못했는지 파악하고 이를 신속하게 수정하는데 도움을 받을 수 있다.
일반적인 에러 로깅 조건
- timestamp : 이벤트가 발생한 정확한 날짜와 시간, 에러가 발생하기까지의 이벤트 순서
- source : 에러가 발생한 위치
- level : 로그 항목의 심각도(일반적인 레벨에는 debug, info, warning, error, fata이 있다.
- message : 이벤트에 대한 세부 정보를 제공하는 설명적인 텍스트
- context : 이슈를 진단하는데 도움을 줄 수 있는 추가 데이터 - 에러가 발생했을 때의 애플리케이션 또는 시스템 상태에 대한 세부 정보를 포함
일반적인 에러 로깅 방법
- console.error() 혹은 console.log()를 사용하여 에러 메세지 출력
try {
// do some crazy stuff
} catch (e) {
(console.error || console.log).call(console, e.stack || e);
}
- async await에서는 try, catch를 활용하여 예외가 발생했을 떄 에러 로그를 출력할 수 있게 할 수 있음
- promise 는 catch 블록을 활용하여 가능
- window.onerror 이벤트 핸들러 사용하기 - 브라우저에서 발생하는 모든 에러를 잡아 서버에 로그를 보낼 수 있다.
window.onerror = function (message, source, lineno, colno, error) {
console.error(`Error: ${message}\\nSource: ${source}\\nLine: ${lineno}\\nColumn: ${colno}\\nError object: ${JSON.stringify(error)}`);
};
window.onerror = function (message, source, lineno, colno, error) {
console.error(`Error: ${message}\\nSource: ${source}\\nLine: ${lineno}\\nColumn: ${colno}\\nError object: ${JSON.stringify(error)}`);
return true; // 이를 반환하면 기본 에러 핸들러가 실행되지 않습니다.
};
- LogRocket 사용하기
- 에러 로깅 라이브러리 또는 서비스 이용
현재 개발하고 있는 시스템의 타겟은 누구인가(정답 없음)
현재 개발중인 시스템은 모두 현장의 생산 관리자들을 타겟으로 생산 현황 모니터링을 목적으로 개발되고 있습니다.
바벨이란?
- 개발자들이 실행 환경에 구애받지 않고 항상 최신 문법의 자바스크립트로 코딩할 수 있도록 도와준다.4
- 자바스크립트 언어의 문법은 빠르게 진화하고 있지만 자바스크립트 코드를 실행해주는 환경이 이를 받쳐주지 못하는 경우가 있기 때문에 이러한 딜레마를 해결하고자 등장했다.
- 바벨은 TypeScript나 JSX로 작성된 코드를 변환할 때도 많이 사용된다.
- 트랜스파일러로의 역할을 수행
instanceOf란 어떤 상황에서 사용되는가?
객체가 특정 클래스에 속하는지, 특정 클래스를 상속받는 클래스에 속하는지 확인하는데 사용된다.
- 해당 변수가 어떤 인스턴스 타입을 가진 것인지 확인할 때 사용할 수 있다.
- 예를 들어 해당 객체가 위크맵인지 위크셋인지, map, set를 확인 가능하다.
instanceOf의 내부 동작은 어떻게 이뤄지는가
object의 프로토타입 체인에서 constructor.prototype이 존재하는지 여부와 해당 constructor 의 value가 어떤 타입인지를 체크하여 동작한다.
결과적으로 모든 instance는 객체이기 떄문에 instanceOf Object 타입에 대하여 true를 반환한다.
확인하고자 하는 객체 뿐만 아니라 모든 프로토타입 체인의 constructor.prototype을 확인한다.
iterator()란?
반복을 위해 설계된 특정 인터페이스가 있는 객체다. value와 done을 반환하고 Next 메서드를 가진다.
시퀀스를 정의하고 종료시에 반환값을 잠재적으로 정의하는 객체
value 프로퍼티가 다음 시퀀스 값을 반환하고, done은 마지막 값이 산출되었는지 여부를 반환한다.
자바스크립트에서 string, array, map, set등은 열거가능한 속성이 있는 객체이며 이터러블하다고 말한다.
이터러블한 객체는 Symbol.iterator()를 가진다.
for of문을 통해 순회 가능하다. for of는 이터러블한 객체만 순회하며, for in은 일반 객체도 순회한다.
iterable은iterator를 리턴하는 [Symbol.iterator]() 를 가진 값,
iterator는{value, done} 객체를 리턴하는 next() 를 가진 값입니다
- 객체 내부에서 데이터 값들을 순환하여 가져오는 함수
- 키값이 존재하고 순환 가능한 객체여야 한다.
- 때문에 순환이 불가능한 weakmap과 weakset에서는 사용할 수 없다.
generator()란?
언제든지 실행을 일시 중지하고 재개할 수 있는 기능으로 시간이 지남에 따라 일련의 값을 생성 가능. yield를 통해 일시중지 했다가 다시 시작 가능
기본적으로 비동기적으로 동작한다.
이터레이터를 발생시키는 함수이다.
function* 키워드를 사용해서 코드를 구현한다.
실행이 연속적이지 않으며 사용자가 제어할 수 있는 함수다.
계산을 더 작고 관리하기 쉬운 청크로 분할하여 병렬 또는 한 번에 하나씩 실행 가능하다. → 머신러닝 알고리즘 등 대량의 데이터를 처리해야 하는 상황에서 유리하다.
function* generator(){
yield console.log(1)
}
let a = generator();
console.log(a);
// generator {<suspended>}
a.next();
// 1
// {value: undefined, done: false}
a.next();
// {value: undefined, done: true}
console.log(a);
// generator {<closed>}
스테틱을 사용하는 이유가 어떻게 되죠?
- 메모리 측면에서 효율적이다 → 메모리 영역에 저장되고 고정된 메모리 영역을 사용하기 때문에 매번 인스턴스를 생성하며 낭비되는 메모리를 줄일 수 있다.
- 객체(인스턴스)를 생성하지 않고 사용 가능하기 때문에 일반 메서드보다 실행 속도가 빠르다. 클래스가 메모리에 올라가는 시점에 생성되어 바로 사용이 가능하다.
단점
- 프로그램 종료시까지 static 메서드는 계속 메모리에 할당된 채 존재한다.
- 객체지향적이지 않다. → 따로 객체를 생성하지 않고 메모리의 static 영역에 할당된 곳에서 여러 클래스들이 데이터를 불러온다.
- static 메서드는 interface를 구현하는데 사용될 수 없다.
- 싱글톤 패턴과 static은 어떤 관계인가?일반적으로 new를 통해 생성된 객체는 heap 영역에 생성된다. 이는 객체 사용 여부에 따라 자동으로 가비지 콜랙팅이 된다. → 동적 할당싱글톤 패턴은 오직 하나의 인스턴스를 제공하는 패턴이다. 이때 메서드를 static으로 선언하여 stack 메모리에 할당하고, 하나의 인스턴스만을 생성하고 어디에서든 사용될 수 있도록 만들었다.
- 반면 stack 영역에 생성된 객체는 종료시까지 삭제되지 않는다. 접근의 편의성에 대한 이점도 있다. 하지만 가비지콜랙팅으로 관리되지 않으며 메모리를 차지한다.
- [Java] 스태틱, 싱글톤 패턴 (static, singleton pattern)
싱글톤 패턴에서 스테틱을 사용한 이유
하나의 인스턴스만 생성하여 사용해야 하는 경우.
가장 대표적인 예로 데이터베이스 연결 시에 싱글톤 패턴을 사용하여 한번만 인스턴스를 생성하고 필요한 곳에 이 인스턴스를 사용하여 메모리 낭비를 줄인다.
이벤트 델리게이션이 뭐죠?
이벤트 위임이라고도 불리며, 상위 부모 요소에서 하위 자식 요소에 대한 이벤트를 위임하여 동작하는 방식을 말합니다.
예를 들어 동적으로 생성되는 li 태그에 대하여 부모 요소에서 대신 이벤트를 등록하여 하위 자식 요소를 컨트롤 하면, 자식 요소에 일일이 이벤트를 등록할 필요 없이 효율적으로 이벤트를 관리할 수 있습니다.
장점
- 메모리 절약 : 모든 요소에 이벤트 핸들러를 할당하지 않아도 되므로 초기화가 단순해지고 메모리가 절약된다.
- 코드 간결성 : 요소를 추가하거나 제거할 때 해당 요소에 할당된 핸들러를 추가하거나 제거할 필요가 없기 때문에 코드가 짧아진다.
- DOM 수정 용이성 : innerHTML이나 유사한 기능을 하는 스크립트로 요소 덩러리를 더하거나 뺄 수 있어 DOM 수정에 용이하다.
const ul = document.querySelector("ul");
ul.addEventListener("click", (event) => {
if (event.target.tagName == "LI") {
event.target.classList.add("selected");
}
});
'개발 > 스터디' 카테고리의 다른 글
기술 면접 준비 - React (5) 完 (0) | 2024.06.29 |
---|---|
기술 면접 준비 - Typescript 기본 (4) (0) | 2024.06.24 |
기술 면접 준비 - HTML, CSS 기본 (3) (0) | 2024.06.23 |
기술 면접 준비 - HTTP 기본 (2) (0) | 2024.06.17 |
F-Lab 내돈내산 수강 후기 (2) | 2024.06.12 |