** 이전 포스팅에서 렌더트리를 형성하는 과정에(웹브라우저 동작 과정) 대해서 포스팅했는데, 추가적으로 이번엔 렌더링 엔진은 아니지만 자바스크립트 엔진에 의해서 이뤄지는 자바스크립트 코드 파싱 과정에 대해서, 그리고 리렌더링 과정에 대해서 포스팅을 하고자 한다.

자바스크립트(JS) 파싱 및 실행 과정

  1. CSS와 마찬가지로, HTML 파싱 과정에서 script 태그 속 js 코드나, link 태그로 불러온 js 파일을 만나게 될 때, 렌더링 엔진은 파싱 및 DOM 생성을 멈추고, 해당 코드를 파싱 및 실행하게 된다(script 태그를 body 태그 마지막에 쓰는 것이 좋은 이유로, js 로직이 복잡하다면, 렌더링에 그만큼 로딩시간이 걸리기 때문에 순차적으로 코드를 파싱하는 렌더링 과정에서 body 태그 다음에 script 태그를 삽입하는게 추천된다)
  2. 이 때, 렌더링 엔진이 진행하던 작업의 주도권을 자바스크립트 엔진에게 넘겨준다. 즉, 자바스크립트 파싱 및 실행은 자바스크립트 엔진이 담당하게 된다(브라우저별로 렌더링 엔진도 다르듯이, 자바스크립트 엔진도 다르다. 예를 들어, 구글 크롬 및 node.js가 쓰는 V8도 있고, 파이어폭스가 쓰는 SpiderMonkey도 있다).
  3. 먼저, 자바스크립트 코드(개발자가 타이핑했을)를 받은 자바스크립트 엔진은 그것을 읽을 수 없기에 역시 파싱 과정이 필요하다. 따라서, 코드를 먼저 최소의 문법 단위인 토큰 으로 '토크나이징(tokenizing)' 한다. 이는 DOM 트리 생성 과정에서도 나왔던 개념으로 어휘 분석 과정으로도 불린다(Lexcial Analysis).
  4. 다음으로, 어휘 단위로 혹은 최소의 문법적 단위로 코드를 쪼갰으니, 이제 그 쪼갠 단위를 바탕으로 구문분석(syntax analysis)을 진행한다. 이 과정을 바탕으로 AST(Abstract Syntax Tree)를 생성한다(파싱 과정).
  5. ** AST explorer 실제로 해당 사이트에 들어가면, 자바스크립트 코드를 AST 형태로 컨버팅한 것을 볼 수 있게 해뒀다.
  6. 이렇게 파싱한 AST를 바이트코드로 변환해서 이를 실행할 수 있는 인터프리터를 통해 실행을 하게 된다.

리플로우와 리페인트 과정

  1. 저번 포스팅 마지막 부분에 나온 '리렌더링'에 관련한 내용인데, 이미 렌더링 엔진을 통해 최종적으로 렌더트리를 생성해서, 레이아웃, 페인트 과정을 거쳐 브라우저의 뷰포트에 렌더링을 마친 상태에서 유저의 특정 동작으로 인해 인터랙션이 발생하고(by JS), DOM API를 통해 DOM 혹은 CSSOM(CSS + DOM)에 영향을 미쳤다고 가정해보자.
  2. ** DOM API란 이미 생성된 DOM을 동적으로 조작할 수 있도록 (html 요소와 스타일 등을 변경할 수 있는) 만들어 놓은 프로그래밍 인터페이스이다.
    ** 주의점 : 앞서 말했듯이 html 렌더링 과정은 동기적(synchronous)으로 처리되기 때문에 중간에 js 리소스를 불러오면 js 코드가 최종 실행될 때까지 파싱과 DOM 생성을 멈춘다(blocking). 하지만, 이 때, JS 코드가 DOM API를 이용하여 DOM 혹은 CSSOM을 수정하는 로직이라면, 문제가 발생한다. DOM API는 이미 완성된 렌더트리에 DOM에 대해서 사용할 수 있는데, 아직 생성중인 단계에서 DOM API를 사용하면 문제가 생기는 것이다. 애초에 아직 그려지지 않은 부분에 대해서 수정을 하려하면 에러가 나는 것이 상식적인 것이기도 하다;
  3. 그러면,다시 변경된 DOM, CSSOM을 바탕으로 렌더트리가 생성되고, 또 이를 바탕으로 레이아웃, 페인트 과정이 이뤄지는데, 레이아웃을 새로짜는 것을 리플로우, 페인트를 다시하는 것을 리페인트라고 한다.
  4. 리플로우는 노드의 추가, 제거 및 요소의 크기, 위치 변경 등 레이아웃과 관련된 부분이 수정되는 경우에 발생하고, 리페인트는 재결합된 렌더트리를 기반으로 다시 페인트를 하는 경우를 말하기 때문에 각각의 과정은 반드시 리플로우가 일어나고, 리페인트가 일어나는 식이 아니라 리플로우가 안일어나고(레이아웃에 변화x인 경우), 리페인트만 일어날 수 있다.

자바스크립트 파싱에 의한 DOM 생성 Blocking

  • 위에서 말했듯이, DOM 생성 과정(렌더링 중간과정)에서 script 태그나, link를 통한 js 리소스 요청 후 js코드를 실행하게 되면, DOM 생성이 멈추는 blocking 현상이 발생한다.
  • blocking 현상은 일단 당연하게도 렌더링 속도를 지연시킨다. DOM이 생성되고, 렌더트리가 생성돼야 웹브라우저의 뷰포트에 렌더링이 되는데, 그 과정을 중간에 막기 때문에 로딩 시간이 걸릴 수 밖에 없다.
  • 또한, blocking 이외에도 아직 생성되지 않은 DOM을 수정하려는 접근(DOM API로)은 에러를 일으킨다.
  • 따라서 이러한 blocking 이슈 및 에러 이슈를 방지하기 위해서 어떤 방법이 있을지 생각해보면, 먼저, 자바스크립트를 body 태그의 가장 아래에 위치시키는 것이다. </body> 직전에 말이다. 이 방법은 가장 마지막에 자바스크립트를 파싱 및 실행하도록 하기에 앞서 말한 이슈에서 벗어날 수 있게 해준다.
  • 다음으로 HTML5에 추가된 script 태그의 attribute인 defer과 async를 쓰는 것이다. Defer vs async
    • 공통적으로 script 태그에 쓸 수 있고, src 어트리뷰트를 반드시 써준 다음에 써야한다.
    • 외부 자바스크립트를 로딩하는 과정과 HTML파싱 과정을 비동기적으로(asynchronous)처리한다. 즉, 로딩시에 파싱을 중단(blocking)하지 않는다.
    • 그러나, defer는 로딩 후에 바로 자바스크립트를 파싱 및 실행하지 않고, html 파싱이 끝난 직후에 이뤄진다. html 파싱이 끝난 시점에 DOMContentLoaded 이벤트가 발생하는데, 이 시점에 자바스크립트 파싱과 실행이 이뤄진다는 것.
    • 이와 달리, async는 로딩 직후 파싱과 실행이 이뤄지기 때문에 로딩은 비동기적으로 blocking을 안하고 이뤄지지만, 파싱과 실행 시에는 어쩔 수 없이 동기적으로 blocking을 하게된다. 또한, 여러개의 script로 자바스크립트를 로딩하면서 해당 태그에 async 속성을 주면 로딩이 끝난 파일부터 파싱 및 실행을 하고 나머지는 block하기 때문에 순서를 보장하지 못한다. 더 위에 있는 script1보다 script2가 먼저 실행될 수 있다는 것.
    • 결과적으로, defer 속성은 html파싱이 끝난 후에 실행되어야하는 코드 예를 들어, DOM을 조작하는 등의 코드에 쓰는 것이 좋고, async 속성은 순서를 보장할 필요가 없지만, 로딩된 직후에 파싱 및 실행해야하는 부분에 써주면 좋을 것.
    • # 각각의 사용법
          1. defer : <script defer src="myJs.js">
          2. async : <script async src="myJs.js">
          
        
profile
완벽함 보다는 최선의 결과를 위해 끊임없이 노력하는 개발자

0개의 댓글