script의 위치에 대해 언급하기 전에 html,css,javascript가 브라우저에 표시되는 과정을 다시 한번 떠올려 보자!
브라우저가 서버에서 페이지에 대한 HTML응답을 받으면, 화면에 표시되기 전에 많은 단계를 거쳐야한다. 브라우저가 페이지의 초기 출력을 위해 실행해야하는 이 순서를 'Critical Rendering Path(CRP)'라고 한다.
DOM트리는 완전히 구분 분석된 HTML페이지의 Object표현이다. 루트 요소
<html>로 시작하여 페이지의 각 요소에 대한 노드가 만들어진다.
CSSOM(CSS Object Model)은 DOM과 연관된 스타일의 Object표현이다. css는 "렌더링 차단 리소스"로 간주된다.즉, 먼저 리소스를 완전히 파싱하지 않으면 렌더링 트리를 구성할 수 없다.
Javascript는 "파서 차단 리소스"로 간주된다. 즉, HTML문서 자체의 구분 분석은 Javascript에 의해 차단된다.
파서가 내부 태그이든 외부 태그이든<script>태그에 도달하면 fetch를 중단하고 실행한다. 따라서 문서 내의 요소를 참조하는 Javascript파일이 있는 경우, 해당 문서가 표시된 이후에 배치해야 된다.
렌더링 트리는 DOM과 CSSOM의 조합이다. 페이지에서 최종적으로 렌더링될 내용을 나타내는 트리다. 즉, 표시되는 내용만 캡쳐하기 때문에
display:none을 사용하여 css로 숨겨진 요소는 포함하지 않는다.
레이아웃은 뷰포트의 크기에 따라 css스타일에 대한 컨텍스트에 의해 뷰포트의 크기를 결정한다.
마지막으로 Painting단계에서 페이지의 가시적인 내용을 픽셀로 변환하여 화면에 표시할 수 있다.
그렇다면 우리는 script를 어떤 식으로 넣어야할까..??
<head></head>안에 위치시키기<!DOCTYPE html>
<html lang=ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="main.js"></script>
</head>
<body>
</body>
</html>

HTML을 파싱하다가 <script>태그를 만나면 HTML파싱을 중단하고 script파일을 다운받고 실행을 끝마친 후에야 다시 HTML파싱을 재개한다.
문제 : script파일의 용량이 크거나 인터넷이 엄청 느리다면 다운받는데도 많은 시간이 소요
<body></body>의 끝부분에 위치시키기<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div></div>
<script src="main.js"></script>
</body>
</html>

이 방법은 HTML을 전부 파싱하여서 준비를 끝낸 후에 script파일을 다운받고 실행한다
문제 : 웹사이트가 javascript에 의존적이라면 즉, 사용자가 의미있는 컨텐츠를 보기위해서 서버에 있는 데이터를 받아온다던지 DOM요소를 더 꾸미던지 등의 정상적인 페이지를 보려면 사용자가 스크립트를 fetching하는 시간까지 기다려야하는 불편함이 있다.
<head></head>안에 위치시키기 + async<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script async src="main.js"></script>
</head>
<body>
<div></div>
</body>
</html>

async는 boolean타입의 속성으로 위의 코드처럼 선언만 해줘도 true로 설정이 되어서 바로 쓸 수 있다. 이 방법은 HTML을 파싱하다가 script태그를 만나면 HTML파싱과 script파일을 fetching하는 단계가 병렬로 이루어진다. 그리고 다운로드가 완료되면 HTML파싱은 중단되고 스크립트 파일만 실행된다. 스크립트 파일의 실행이 끝난 후에 다시 HTML파싱이 재개된다.
문제 : 우선, HTML을 파싱하는 도중에 스크립트가 실행되므로 만약 querySelector로 요소를 찾으려고 한다면 아직 정의되지않은 요소일 수 있다. 그리고 자바스크립트를 실행하는 동안에 파싱이 중단되기때문에 시간이 아직도 여전히 걸리는 방법이다.
async 옵션으로 다수의 script파일을 선언한 경우
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script async src="a.js"></script>
<script async src="b.js"></script>
</head>
<body>
<div></div>
</body>
</html>

이런 경우 script를 순차적으로 다운로드를 받지만 먼저 다운로드된 script파일이 먼저 실행된다. 즉, 선언한 순서에 상관없이 다운로드가 빨리된 것이 먼저 실행된다는 말이다.
스크립트의 순서가 중요하다면 이 방법은 쓰지않는 것을 권장
<head></head>안에 위치시키기 + defer<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script defer src="main.js"></script>
</head>
<body>
<div></div>
</body>
</html>

이 방법은 HTML파싱을 하다가 script태그를 만나면 HTML파싱과 스크립트파일의 fetching을 병렬적으로 수행하고 HTML파싱이 끝난 후 스크립트가 실행된다.
defer 옵션으로 다수의 script파일을 선언한 경우
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script defer src="a.js"></script>
<script defer src="b.js"></script>
</head>
<body>
<div></div>
</body>
</html>

이 방법은 HTML을 파싱하는 동안에 각각의 script파일이 fetching되고 파싱이 끝나면 선언한 순서대로 실행된다.
가장 권장하는 방법!! 그리고 알아둬야 할 것은 defer옵션은 외부스크립트에만 유효하다.