본문 바로가기

Study Record/Javascript

[JavaScript] 스크립트 import 하는 방법에 대해 알아보기 - defer&async

* 이 글은 Udemy의 "JavaScript 완벽 가이드 : 초급 + 고급 마스터 과정" 강의를 수강한 뒤 개인 복습을 위해 정리하는 글입니다.

(섹션 2: 50)

 

 

 

 

 


 

 

 

 

 

스크립트를 import 하는 방법에 따라서 스크립트를 실행하는 것과 HTML 코드가 구문 분석하는 등의 순서가 바뀌게 되어 웹페이지가 가장 효율적으로 빠르게 작동할 수 있도록 할 수 있다. 방법의 종류는 4가지가 있다.

 

 

Inline External File External File - async External File - defer
<script>alert('Hi');</script> <script src=file.js></script> <script src=file.js async></script> <script src=file.js defer></script>
즉시 실행, HTML 분석 및 렌더링을 차단 즉시 로드 및 실행, HTML 분석 및 렌더링 차단  즉시 로드, HTML 분석 및 렌더링 후 실행 HTML 분석 및 렌더링 후 즉시 로드 및 실행

 

 

 

이에 대해 자세히 알아보기 위해 개발자 도구의 performance를 통해 각각이 어떻게 실행되는지 살펴보자.

 

 

 

 

 

 

먼저 시크릿 모드 창에서 개발자 도구를 켜주고 performance를 클릭한다.

 

 

그 화면에 뜨는 동그라미가 있는 버튼을 눌러 녹화를 시작하고 페이지를 새로고침 한 후에 중지 버튼을 눌러준다.

 

 

 

 

 

 

 

실행된 부분을 확대해보면 이와 같은 화면이 나타나게 된다. 

 

 

performance의 network에서는 어떤 네트워크에서 요청이 전송되며 브라우저는 어떤 작업을 했는지, 무엇이 분석되었고 실행됐는지 등을 확인할 수 있다.

 

또한 performance의 main에서는  브라우저의 작업을 자세히 살펴볼 수 있다.

 

 

 

 


 

 

이제 import를 어떻게 하느냐에 따라 달라지는지 알아보자.

 

 

 

 

먼저 원래 코드를 실행시켜본 결과 화면이다.

 

 

먼저 Network를 보면 index.html 파일을 먼저 다운로드하고 app.css 파일과 js 파일을 다운로드하였음을 볼 수 있다. 그리고 Main에서 브라우저의 작업을 보면 다운로드된 index.html 파일을 받고 로딩이 끝난 후에 HTML 코드의 구문 분석을 시작하는 것을 볼 수 있다. 구문 분석이 끝난 후에는 js 파일로 요청을 보낸다. 이때 js 파일보다 css 파일이 먼저 전송된 것을 볼 수 있는데 이는 head 섹션에서 css 파일을 요청하기 때문이다. js 파일은 body 섹션에서 요청하기 때문에 css 파일보다 늦게 요청된 것이다. 이는 js 파일이 구문 분석 후에 실행된다는 장점이 있지만 파일이 커지게 되면 로드하고 실행하는 시간이 길어져서 HTML 파일과 js 파일 실행 사이에 일시정지가 발생할 수 있다. js 파일이 HTML 코드에 의존하기 때문에 구문 분석 후에 실행해야 하지만 이와 같이 하면 전과 같은 상황이 발생할 수 있다. 이는 로딩이나 서버에서 미리 다운로드를 함으로써 해결할 수 있다. 최대한 빨리 스크립트를 로드하고 구문 전체가 분석된 후에 실행되도록 하는 방법이 해결 방법이다. 이 방법을 알아보기 위해 코드를 수정해보자.

 

 

 

이다. 스크립트 작성한 것을 head 섹션으로 옮겨주면 된다.

 

 

<script src="assets/scripts/vendor.js"></script>
<script src="assets/scripts/app.js"></script>

 

 

이 코드를 head 섹션에 넣어준다.

 

 

 

 

그럼 이와 같은 결과 화면이 나타난다.

 

 

 

HTML 구문 분석을 시작하면 스크립트를 import해서 다운로드하면 HTML 구문 분석이 일시 정지되고 스크립트 블록이 구문 분석이 되면 스크립트를 실행한 후에만 HTML 구문 분석을 계속한다. 하지만 이때는 다음과 같은 오류가 발생하게 된다.

 

 

 

웹페이지 버튼이 준비되지 않은 상태에서 상호작용을 시도한 것이기 때문이다. 미리 스크립트를 다운로드한 건 좋지만 너무 빨리 실행한 것이 문제가 된 것이다. 이를 해결하기 위해서는 defer 속성을 사용하면 된다.

 

 

 

 

defer 속성의 역할

브라우저에 스크립트를 바로 다운로드하지만 HTML 구문 분석을 차단하지 않도록 해서 HTML 구문 분석을 계속하고 구문 분석이 끝난 후에만 스크립트만 실행되도록 한다.

 

 

라서 defer를 스크립트에 적어주게 되면 스크립트를 미리 다운로드를 하지만 다운로드가 끝나면 바로 스크립트를 실행하지 않고 HTML 구문 분석이 완료된 후에만 스크립트를 실행하도록 해준다.

 

 

<script src="assets/scripts/vendor.js" defer></script>
<script src="assets/scripts/app.js" defer></script>

 

이와 같이 작성해준다.

 

 

그럼 다음과 같은 결과가 나온다.

 

 

* 뒤에 있는 파란색 블록은 HTML이 아니라 stylesheet이다.

 

 

 

 

 

 

 

 

하지만 가끔은 스크립트를 미리 로드하고 실행도 미리 해야할 경우가 있다. 이 경우에는 HTML 코드에 의존하지 않아서 연결을 하지 않아도 될 경우이기 때문에 HTML 구문 분석의 완료 여부는 중요하지 않다. 이때는 defer 대신 async를 사용하여 브라우저에 최대한 빨리 스크립트를 로딩하도록 해서 브라우저를 차단하지 않고 HTML 구문 분석이 계속 진행되도록 하면 된다. 이는 defer와 달리 스크립트가 다운로드되면 바로 실행되게 된다. HTML 코드가 구문 분석될 때까지 기다리지 않고 최대한 빨리 실행한다. 또한 defer는 실행 순서가 보장되지만 async는 보장되지 않는다. 따라서 이는 독립 실행형 스크립트용 솔루션이다.

 

 

 

async 속성의 역할

브라우저에 최대한 빨리 스크립트를 로딩하도록 해서 브라우저를 차단하지 않고 HTML 구문 분석이 계속 진행되도록 하고 스크립트가 다운로드되면 바로 실행되게 한다.

 

 

 

 

※ defer와 async는 외부 스크립트에서만 사용 가능하다,

 

 

 

 

 

 

import 방법에 대한 중요성

 

실제로 웹 서버에서 호스팅 하는 큰 애플리케이션의 경우 이 부분이 매우 중요해질 수 있음.

 

JavaScript로 작업 시 꼭 head 섹션에 defer 속성으로 임포트를 해야 한다. 그러면 빨리 로드되지만 HTML 구문 분석이 완료된 후에만 실행돼서 2가지 장점을 모두 활용하게 된다.

 

 

 

 

 

 

📝 전체 코드

 

 

 
const defaultResult = 0;
let currentResult = defaultResult;
let logEntries = [];

// Gets input from input field
function getUserNumberInput() {
  return userInput.value;
}

// Generates and writes calculation log
function createAndWriteOutput(operator, resultBeforeColc, calNumver) {
  const calcDescription = `${resultBeforeColc} ${operator} ${calNumver}`;
  outputResult(currentResult, calcDescription); // from vendor file
}

function writeToLog(
  operationIdentifier,
  prevResult,
  operationNumber,
  newResult
) {
  const logEntry = {
    operation: operationIdentifier,
    prevResult: prevResult,
    number: operationNumber,
    result: newResult,
  };
  logEntries.push(logEntry);
  console.log(logEntry);
}

function add(num1, num2) {
  const enteredNumber = getUserNumberInput();
  const initialResult = currentResult;
  currentResult += enteredNumber;
  createAndWriteOutput("+", initialResult, enteredNumber);
  writeToLog("ADD", initialResult, enteredNumber, currentResult);
}

function subtract() {
  const enteredNumber = getUserNumberInput();
  const initialResult = currentResult;
  currentResult -= enteredNumber;
  createAndWriteOutput("-", initialResult, enteredNumber);
  writeToLog("SUBTRACT", initialResult, enteredNumber, currentResult);
}

function multifly() {
  const enteredNumber = getUserNumberInput();
  const initialResult = currentResult;
  currentResult *= enteredNumber;
  createAndWriteOutput("*", initialResult, enteredNumber);
  writeToLog("MULTIFY", initialResult, enteredNumber, currentResult);
}

function divide() {
  const enteredNumber = getUserNumberInput();
  const initialResult = currentResult;
  currentResult /= enteredNumber;
  createAndWriteOutput("/", initialResult, enteredNumber);
  writeToLog("DEVIDE", initialResult, enteredNumber, currentResult);
}

addBtn.addEventListener("click", add);
subtractBtn.addEventListener("click", subtract);
multiplyBtn.addEventListener("click", multifly);
divideBtn.addEventListener("click", divide);