JavaScript로 상호작용 추가

Ilya Grigorik
Ilya Grigorik

JavaScript를 사용하면 콘텐츠, 스타일 지정, 사용자 상호작용에 대한 응답 등 페이지의 거의 모든 측면을 수정할 수 있습니다. 하지만 JavaScript는 DOM 생성을 차단하고 페이지가 렌더링될 때 지연시킬 수도 있습니다. 최적의 성능을 제공하려면 자바스크립트를 비동기로 설정하고 주요 렌더링 경로에서 불필요한 자바스크립트를 제거하세요.

요약

  • JavaScript는 DOM 및 CSSOM을 쿼리하고 수정할 수 있습니다.
  • JavaScript 실행은 CSSOM을 차단합니다.
  • JavaScript는 명시적으로 비동기로 선언되지 않는 한 DOM 생성을 차단합니다.

JavaScript는 브라우저에서 실행되는 동적 언어이며 페이지 동작 방식의 거의 모든 측면을 변경할 수 있습니다. DOM 트리에서 요소를 추가 및 삭제하여 콘텐츠를 수정하거나, 각 요소의 CSSOM 속성을 수정하고, 사용자 입력을 처리하는 등 많은 작업을 할 수 있습니다. 이를 설명하기 위해 간단한 인라인 스크립트를 사용하여 이전의 'Hello World' 예시를 확장해 보겠습니다.

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script>
      var span = document.getElementsByTagName('span')[0];
      span.textContent = 'interactive'; // change DOM text content
      span.style.display = 'inline'; // change CSSOM property
      // create a new element, style it, and append it to the DOM
      var loadTime = document.createElement('div');
      loadTime.textContent = 'You loaded this page on: ' + new Date();
      loadTime.style.color = 'blue';
      document.body.appendChild(loadTime);
    </script>
  </body>
</html>

사용해 보기

  • JavaScript를 사용하면 DOM에 접근하여 숨겨진 span 노드에 대한 참조를 가져올 수 있습니다. 이 노드는 렌더링 트리에 표시되지 않을 수 있지만 DOM에는 여전히 있습니다. 그런 다음 참조가 있으면 .textContent를 통해 텍스트를 변경하고 계산된 디스플레이 스타일 속성을 '없음'에서 '인라인'으로 재정의할 수도 있습니다. 이제 페이지에 'Hello interactive students!'가 표시됩니다.

  • JavaScript를 사용하면 DOM에서 새 요소를 만들고, 스타일을 지정하고, 추가하고, 제거할 수도 있습니다. 기술적으로는 전체 페이지가 요소를 하나씩 생성하고 스타일을 지정하는 하나의 커다란 JavaScript 파일일 수도 있습니다. 이것도 작동하기는 하지만, 실제로는 HTML과 CSS를 사용하는 것이 훨씬 쉽습니다. JavaScript 함수의 두 번째 부분에서는 새 div 요소를 만들고, 텍스트 콘텐츠를 설정하고, 스타일을 지정하고, 본문에 추가합니다.

페이지 미리보기

이를 사용하여 기존 DOM 노드의 콘텐츠와 CSS 스타일을 수정하고 완전히 새로운 노드를 문서에 추가했습니다. 이 페이지는 어떠한 디자인 상도 수상하지는 못하지만 JavaScript가 제공하는 강력한 기능과 유연성을 보여줍니다.

그러나 JavaScript는 많은 기능을 제공하지만 페이지를 렌더링하는 방법과 시기에 대한 많은 추가 제한을 야기합니다.

먼저, 위의 예에서 인라인 스크립트가 페이지 하단 근처에 있음을 알 수 있습니다. 왜냐하면 직접 해보아야 하지만, 스크립트를 span 요소 위로 이동하면 스크립트가 실패하고 문서에서 span 요소에 대한 참조를 찾을 수 없다고 불평할 것입니다. 즉, getElementsByTagName(‘span')null을 반환합니다. 이는 스크립트가 문서에 삽입된 정확한 지점에서 실행된다는 중요한 속성을 보여줍니다. HTML 파서는 스크립트 태그를 만나면 DOM 생성 프로세스를 일시 중지하고 자바스크립트 엔진에 제어 권한을 넘깁니다. JavaScript 엔진의 실행이 완료되면 브라우저는 중단한 부분부터 시작하여 DOM 생성을 재개합니다.

즉, 스크립트 블록은 아직 처리되지 않았기 때문에 페이지의 후반부에서 요소를 찾을 수 없습니다. 즉, 인라인 스크립트를 실행하면 DOM 생성이 차단되어 초기 렌더링도 지연됩니다.

페이지에 스크립트를 도입하는 또 다른 미묘한 속성은 스크립트가 DOM뿐 아니라 CSSOM 속성도 읽고 수정할 수 있다는 점입니다. 실제로 이 예에서 span 요소의 표시 속성을 none에서 인라인으로 변경할 때 이 작업을 수행합니다. 결과는? 이제 경합 상태가 생겼습니다.

스크립트를 실행하려고 할 때 브라우저에서 CSSOM을 다운로드하고 빌드하는 작업을 완료하지 않으면 어떻게 될까요? 답은 간단하지만 성능에는 그다지 좋지 않습니다. 브라우저는 CSSOM을 다운로드하고 생성할 때까지 스크립트 실행 및 DOM 생성을 지연시킵니다.

간단히 말해, JavaScript는 DOM, CSSOM 및 JavaScript 실행 사이에 새로운 종속 항목을 많이 도입합니다. 이로 인해 브라우저가 화면에서 페이지를 처리하고 렌더링할 때 상당한 지연이 발생할 수 있습니다.

  • 문서에서 스크립트의 위치가 중요합니다.
  • 브라우저에서 스크립트 태그를 만나면 스크립트 실행이 완료될 때까지 DOM 생성이 일시 중지됩니다.
  • JavaScript는 DOM 및 CSSOM을 쿼리하고 수정할 수 있습니다.
  • JavaScript 실행은 CSSOM이 준비될 때까지 일시 중지됩니다.

일반적으로 '주요 렌더링 경로 최적화'란 HTML, CSS 및 자바스크립트 간의 종속성 그래프를 이해하고 최적화하는 것을 의미합니다.

파서 차단과 비동기 자바스크립트 비교

기본적으로, JavaScript 실행은 "파서를 차단"합니다. 브라우저가 문서에서 스크립트를 만나면 DOM 생성을 일시 중지하고, JavaScript 런타임에 제어 권한을 넘겨주고, 스크립트가 실행되도록 한 다음 DOM 생성을 계속합니다. 이전 예에서 인라인 스크립트에서 이러한 동작이 실행되는 것을 확인했습니다. 실제로, 실행을 지연시키기 위해 추가 코드를 작성하지 않는 한 인라인 스크립트는 항상 파서를 차단합니다.

스크립트 태그를 통해 포함된 스크립트는 어떤가요? 이전 예를 살펴보고 코드를 별도의 파일로 추출해 보겠습니다.

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script External</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js"></script>
  </body>
</html>

app.js

var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);

사용해 보기

<script> 태그를 사용하든 인라인 자바스크립트 스니펫을 사용하든 둘 다 동일한 방식으로 작동할 것으로 예상됩니다. 두 경우 모두 브라우저가 일시중지했다가 스크립트를 실행한 후에 문서의 나머지 부분을 처리합니다. 그러나 외부 자바스크립트 파일의 경우 브라우저에서 스크립트를 디스크, 캐시 또는 원격 서버에서 가져올 때까지 대기해야 하며, 이로 인해 주요 렌더링 경로에 수만 밀리초에서 수만 밀리초의 지연이 발생할 수 있습니다.

기본적으로 모든 JavaScript는 파서를 차단합니다. 브라우저는 스크립트가 페이지에서 무엇을 수행할지 모르기 때문에 브라우저는 최악의 시나리오를 가정하고 파서를 차단합니다. 스크립트가 참조되는 정확한 지점에서 스크립트를 실행할 필요가 없다는 신호를 브라우저에 알리면 브라우저가 계속해서 DOM을 생성하고 준비가 되었을 때(예: 파일을 캐시 또는 원격 서버에서 가져온 후) 스크립트를 실행할 수 있습니다.

이를 위해 스크립트를 async로 표시합니다.

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script Async</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

사용해 보기

async 키워드를 스크립트 태그에 추가하면 스크립트가 사용 가능해질 때까지 기다리는 동안 DOM 생성을 차단하지 않도록 브라우저에 지시하므로 성능이 크게 향상될 수 있습니다.

의견