자바스크립트 개발자가 알아야 할 지식

sunclock·2023년 9월 14일
0

프론트엔드

목록 보기
8/8

시작하며

면접 전형을 진행하며 자바스크립트 기본지식의 부재를 알게 됐다.
이에 자바스크립트 개발자가 알아야 할 지식을 정리해본다.

브라우저 동작 과정

출처: 브라우저는 어떻게 동작하는가?
출처: https://12bme.tistory.com/140

렌더링 엔진이 아래 작업을 수행한다.
1. HTML문서를 파싱하고 HTML 태그를 DOM 트리로 변환한다.
2. 외부 CSS파일과 함께 포함된 스타일 요소를 파싱해 CSSOM 트리로 한다.
3. 스타일 정보와 HTML 표시 규칙을 바탕으로 렌더 트리를 구축한다. (attachment) 이때 색상 또는 면적과 같은 시각적 속성이 있는 사각형을 정해진 순서대로 화면에 표시한다.
4. 각 노드의 구체적인 크기와 위치를 연산해 화면에 배치한다. (배치 / layout / reflow)
5. 렌더 트리의 각 노드를 가로지르며 브라우저에 픽셀을 그린다. (repaint)
6. 스타일이나 DOM이 변경되면 위의 과정을 재반복한다. (reflow, repaint)

  • reflow, repaint는 앱 성능에 영향을 미친다. 이를 유발하는 스타일 속성을 인지해야 한다.

  • 렌더트리와 DOM 트리의 대응. 1:1이 아니다.
  • 렌더링 엔진은 좀 더 나은 사용자 경험을 위해 모든 HTML을 파싱할 때까지 기다리지 않고 배치와 그리기 과정을 시작한다.

CORS와 해결 방법

출처: 결제창에서 CORS 대응하기

CORS 발생 이유

  • SOP 기준에 맞춰 CORS 정책을 사용하지 않으면 CORS 에러가 발생한다.
  • 브라우저에는 서로 다른 서버 리소스는 공유하지 않는 정책 SOP(Same-Origin Policy, 동일 출처 정책)이 있다.
  • CORS(Cross-Origin Resource Sharing, 교차 출처 리소스 공유)는 서로 다른 서버끼리 리소스를 공유하기 위한 정책이다. 리소스 요청, 응답을 허용할지 결정하는 브라우저의 검증과 허락, 그를 위한 HTTP 헤더 사용 등을 포함한다.

Origin의 기준

  • 프로토콜
  • 도메인(호스트 이름)
  • 포트
  • 이 중 하나라도 다르면 CORS 에러가 발생한다.

CORS 대응 방법

  1. 서버에서 Access-Control-Allow-Origin 응답 헤더 세팅하기
'Access-Control-Allow-Origin': <origin> | *
// *는 출처 무관 리소스 접근 가능해서 보안이 취약해진다. origin 명시하는 편이 좋다.
  1. 프록시 서버 사용하기
// webpack.config.ts
const config: webpack.Configuration = {
  devServer: {
    port: 3000,
    proxy: {
      '/api/': { 
        target: 'http://localhost:4000',
        changeOrigin: true, 
      },
    },
  },
}

iframe, frameset, frame 태그의 경우 출처가 같더라도 페이지 이동 시 에러가 발생할 수 있다.

preflight 요청이란?

  • CORS 정책의 일부로, SOP만 가능하다는 가정하에 만들어진 과거의 서버의 보안적 취약점을 보호하기 위해 preflight가 추가되었다. 브라우저가 예비 요청과 본 요청으로 나누어 서버에 보낸다. 이중 예비 요청을 preflight라고 부른다.
  • 브라우저가 서버에 origin, 메서드, 헤더 목록을 담아 서버에게 예비 요청을 보낸다.
  • 서버는 어떤 것을 허용하고 금지하는지 정보를 응답 헤더에 담아 브라우저에 보내준다.
  • 브라우저가 예비 요청과 서버의 응답을 비교한 후, 이 요청을 보내는 것이 안전하다고 판단되면 본 요청을 보낸다.

let, const, var의 차이

출처: Var, Let, Const의 차이점은?

var

  • var 선언은 전역 범위 혹은 함수 범위로 지정됩니다.
  • var 변수는 재선언되고, 업데이트될 수 있습니다.
  • var변수는 범위 내에서 맨 위로 올려지고, 값은 undefined(정의되지 않음)으로 초기화됩니다. 변수와 함수 선언이 맨 위로 이동되는 호이스팅 때문이다.

let

  • 블록 범위
  • let은 업데이트될 수 있지만, 재선언은 불가능하다.
  • 다른 범위라면 재선언이 가능하다.
  • let의 키워드는 초기화되지 않습니다. 선언 이전에 사용하려고 시도한다면 Reference Error(참조 오류)가 발생

const

  • 블록 범위
  • 업데이트도, 재선언도 불가능하다.
  • const는 업데이트 할 수 없지만, const의 속성은 업데이트 할 수 있다.
  • const 선언도 맨 위로 끌어올려지지만, 초기화되지는 않습니다. 선언 이전에 사용하려고 시도한다면 Reference Error(참조 오류)가 발생

var, let, const 선언법의 차이점

  • var 선언은 전역 범위 또는 함수 범위이며, let과 const는 블록 범위이다.
  • var 변수는 범위 내에서 업데이트 및 재선언할 수 있다. let 변수는 업데이트할 수 있지만, 재선언은 할 수 없다. const 변수는 업데이트와 재선언 둘 다 불가능하다.
  • 세 가지 모두 최상위로 호이스팅된다. 하지만 var 변수만 undefined(정의되지 않음)으로 초기화되고 let과 const 변수는 초기화되지 않는다.
  • var와 let은 초기화하지 않은 상태에서 선언할 수 있지만, const는 선언 중에 초기화해야한다.

function, arrow 선언 방식의 차이점

출처: MDN, 화살표 함수
출처: Hacks, ES6 In Depth and JavaScript

화살표 함수 표현(arrow function expression)은 전통적인 함수표현(function)의 간편한 대안입니다. 하지만, 화살표 함수는 몇 가지 제한점이 있고 모든 상황에 사용할 수는 없습니다.

  • this, arguments나 super에 대한 자체 바인딩이 없고, methods로 사용해서는 안됩니다.
  • new.target키워드가 없습니다.
  • 일반적으로 스코프를 지정할 때 사용하는 call, apply, bind methods를 이용할 수 없습니다.
  • 생성자(Constructor)로 사용할 수 없습니다.
  • yield를 화살표 함수 내부에서 사용할 수 없습니다.

this

일반적인 함수

  • this는 global(window) 객체를 가리킨다
  • 메소드 함수를 호출할 경우 this는 메소드를 소유한 객체를 가리킨다
  • 생성자를 호출할 경우 this는 새 인스턴스를 가리킨다.
  • bind, call, apply를 사용해 this 바인딩이 가능하다.
// 특정 컨텍스트와 함수를 객체에 속한 것처럼 묶을 수 있다.

// apply(), call()은 함수를 즉시 실행 
func.apply(obj, arg0, arg1, ...)
func.call(obj, [arg0, arg1, ...])

// bind() 는 나중에 실행할 수 있는 함수를 반환
const func2 = func.bind(obj, arg0, arg1, ...)

화살표 함수

  • this가 없다. 대신 화살표 함수를 둘러싸는 렉시컬 범위(lexical scope)의 this를 사용한다. (가장 가까운 부모, 최상위의 경우 global)
function Person() {
  this.age = 0;

  setInterval(() => {
    this.age++; // |this|는 Person 객체를 참조
  }, 1000);
}

var p = new Person();
  • 화살표 함수 내에서 bind, call, apply를 사용해 this 바인딩을 할 수 없다. 바인딩 하려는 obj를 무시한다.

argument

일반함수

  • arguments의 사용이 가능하다.

화살표함수

  • arguments의 사용이 불가능하다.
  • rest 파라미터를 사용하면 arguments 사용이 가능하다.

생성자함수

일반함수

  • 생성자 함수 사용이 가능하다.

화살표함수

  • 생성자 함수 사용이 불가능하다. 화살표 함수 내의 this가 렉시컬 스코프로 사용되기 때문이다.

차이점

  • 일반 함수는 동적으로 this가 결정되지만, 화살표함수는 정적으로 this가 결정된다.
  • 이벤트 호출 시나 콜백 함수를 사용할 경우 this를 어떻게 사용하는지에 따라 적절히 함수를 사용할 수 있다.

Promise와 async, await의 차이점

Promise란?

출처: MDN, Using Promises
Promise는 자바스크립트 비동기 처리에 사용되는 객체로 어떤 작업의 중간 상태를 나타내는 오브젝트다. 미래에 어떤 종류의 결과가 반환됨을 약속해 주는 오브젝트로 비동기 작업이 맞이할(Pending) 미래의 완료(Fulfilled) 또는 실패(Rejected)와 그 결괏값을 나타낸다.

// 콜백 함수 전달 방식
createAudioFileAsync(audioSettings, successCallback, failureCallback);

// Promise 방식: 비동기 함수 호출
const promise = createAudioFileAsync(audioSettings);
promise.then(successCallback, failureCallback);

// Promise Chaining
doSomething()
  .then((result) => doSomethingElse(result))
  .then((newResult) => doThirdThing(newResult))
  .then((finalResult) => {
    console.log(`Got the final result: ${finalResult}`);
  })
  .catch(failureCallback);

콜백 함수를 전달해주는 고전적인 방식과는 달리, Promise는 아래와 같은 특징을 보장합니다.

  • 콜백은 자바스크립트 Event Loop가 현재 실행중인 콜 스택을 완료하기 이전에는 절대 호출되지 않는다.
  • then()을 여러번 사용하여 여러개의 콜백을 추가 할 수 있다. 그리고 각각의 콜백은 주어진 순서대로 하나 하나 실행한다.
  • then() 함수는 새로운 promise를 반환한다. 처음에 만들었던 promise와는 다른 새로운 promise다.
  • Promise의 가장 뛰어난 장점 중의 하나는 chaining이다. Promise Chaining을 이용해 '지옥의 콜백 피라미드'를 해결할 수 있다.

async, await란?

출처: 모던 자바스크립트 튜토리얼, async와 await
async/await를 함께 사용하면 읽고, 쓰기 쉬운 비동기 코드를 작성할 수 있다.

async

  • 함수는 언제나 프라미스를 반환한다.
  • 함수 안에서 await를 사용할 수 있다.

await

  • await는 async 함수 안에서만 동작한다.
  • 자바스크립트는 프라미스가 처리될 때까지 대기합니다. 결과는 그 이후 반환된다.
  • 프라미스가 처리되길 기다리는 동안엔 엔진이 다른 일(다른 스크립트를 실행, 이벤트 처리 등)을 할 수 있기 때문에, CPU 리소스가 낭비되지 않는다.
  • 처리가 완료되면 에러 발생 시 예외가 생성됨(에러가 발생한 장소에서 throw error를 호출한 것과 동일함)
  • 처리가 완료되면 에러 미발생 시 프라미스 객체의 result 값을 반환한다.

여러 작업이 있고, 이 작업들이 모두 완료될 때까지 기다리려면 Promise.all을 활용할 수 있다.

이벤트 루프란?

출처: 모던 자바스크립트, 이벤트 루프와 매크로태스크, 마이크로태스크

이벤트 루프는 단일 스레드를 사용하는 자바스크립트 엔진과 멀티 스레드를 사용하는 실행 환경을 연결하기 위해 필요하다. 이벤트 루프는 작업의 우선 순위를 결정하고, 실행한다.

이벤트 루프 알고리즘

  1. 매크로태스크 큐에서 가장 오래된 태스크를 꺼내 실행한다.
  2. 모든 마이크로태스크를 실행한다. 이 작업은 마이크로태스크 큐가 빌 때까지 이어지고 태스크는 오래된 순서대로 처리된다.
  3. 렌더링할 것이 있으면 처리한다.
  4. 매크로태스크 큐가 비어있으면 새로운 매크로태스크가 나타날 때까지 기다린다.
  5. 1번으로 돌아간다.

매크로태스크 스케줄링

지연시간이 0인 setTimeout(f) 사용하기

  • 이 방법을 사용하면 계산이 복잡한 큰 태스크 하나를 여러 개로 쪼갤 수 있다.
  • 태스크를 여러 개로 쪼개면 태스크 중간중간 사용자 이벤트에 반응할 수 있고, 작업 진척 상태를 화면에 표시해줄 수도 있다.
  • 이벤트가 완전히 처리되고 난 후(버블링이 끝난 후)에 특정 작업을 수행하도록 스케줄링할 때도 사용된다.

마이크로태스크 스케줄링

queueMicrotask(f) 사용하기, 프라미스 핸들러 사용하기

  • 마이크로태스크 전체가 처리되는 동안에는 UI 변화나 네트워크 이벤트 핸들링이 일어나지 않는다.
  • 렌더링이나 네트워크 요청 등의 작업들은 마이크로태스크 전부가 처리되고 난 직후 처리된다.
  • 이런 처리 순서 덕분에 queueMicrotask를 사용해 함수를 비동기적으로 처리할 때 애플리케이션 상태의 일관성이 보장된다.

순서 예측하기

마이크로태스크가 매크로태스크보다 우선순위가 높다.

console.log('script start'); // A

setTimeout(function () { // B
  console.log('setTimeout');
}, 0);

Promise.resolve() 
  .then(function () { // C
    console.log('promise1');
  })
  .then(function () { // D
    console.log('promise2');
  });

console.log('script end'); // E

// A -> E -> C -> D -> B 순서로 실행된다.

웹 워커란?

출처: 카카오 기술블로그, 브라우저 Web Worker 다루기 with 오피스 문서 텍스트 추출 및 암호해제

사용자가 직접 데이터를 처리하는 부분은 싱글 스레드에서 동작되므로 데이터 처리가 많아질수록 병목현상이 생겨 렌더링 프레임에 영향을 미친다.

이벤트 루프를 막을 우려가 있는 무거운 연산은 웹 워커(Web Worker)를 사용해 처리할 수 있다.

  • UI를 처리하는 메인스레드 외, 데이터를 처리할 수 있는 Web Worker를 필요한 개수만큼 생성하여 병렬적으로 실행할 수 있다.
  • 메인 스레드와 메시지를 교환할 수 있긴 하지만 웹 워커엔 메인 스레드와 연관 없는 고유한 변수들과 자체 이벤트 루프가 있다.
  • 웹 워커는 DOM에 접근할 수 없기 때문에 여러 CPU 코어를 동시에 사용해야 하는 연산에 주로 사용한다.

마치며

오늘은 브라우저와 자바스크립트의 기본 지식을 공부했다.
이전에는 내가 사용하는 기능이 어떤 의도로 만들어졌는지 잘 알지 못했다.
이후에는 과거 자바스크립트 사용자가 겪은 문제를 알게 되었고, 이에 대한 해결 방안이 현재의 자바스크립트임을 이해하게 됐다.
부끄러웠던 면접 경험과 이후의 공부를 언어의 철학에 맞게 기술을 적절하게 사용하는 계기로 삼겠다.

profile
안녕하세요.

0개의 댓글