본문 바로가기

TIL

You Don't Know JS 책 공부 시작 -!

You Don't Know JS 공부를 시작하게 된 이유 

프론트엔드 개발자로 3년 넘게 일했지만, 어느 순간부터 관성적으로 코드를 작성하는 내 모습을 발견했다. 자바스크립트의 기본 원리나 동작 방식에 대한 깊은 이해 없이, 익숙한 방식대로 타이핑하고 있는 내 모습에서 부족함을 느꼈다.

이 책을 공부하는 이유는 단순하다.

  • 코드의 작동 방식을 깊이 이해하고 싶다.
  • 내가 작성하는 코드의 의도를 명확히 알고 싶다.
  • 기본기에 충실한 개발자가 되고 싶다.

결국, 왜 이 코드를 이렇게 작성했는지, 어떤 원리로 동작하는지, 이를 명확히 설명할 수 있는 개발자가 되기 위해 You Don't Know JS를 공부하기로 결심했다.

 

책을 읽고 몇 가지 기억하고 싶은 것들을 정리해보려고 한다. 

 

Chapter 1  자바스크립트 

명세서

  • TC39 : JS를 관리하는 기술 운영 위원회로 JS의 공식 명세를 관리함. TC39 위원은 정기 모임에서 명세 변경 안건을 투표하고 합의된 변경 사항을 국제 표준화 기구인 ECMA에 제출함. 
  • https://github.com/tc39/proposals 
  • JS에는 버전이 없다. TC39와 ECMA에 의해 유지되는 공식적인 표준 JS는 단 하나뿐임. 

JS지만 JS가 아닌 웹 전용 문법 

  • alert()는 JS 프로그램일까? -> 브라우저 엔진, Node.js 등과 같이 JS가 실행되는 환경은 전역 스코프에 API를 추가해 사용할 수 있는 기능을 제공함. 즉 웹에서만 지원되는 API로 JS가 아님. (fetch, getCurrentLocation, getUserMedia 등도 마찬가지) 
  • console.* -> 브라우저 벤더(예: 크롬, 파이어폭스, 사파리)와 Node.js 같은 실행 환경에서 제공하는 API. JavaScript 표준은 아니지만, 사실상 모든 환경에서 지원하는 디버깅 도구로 자리 잡음.

JS의 다양한 얼굴 

  • 패러다임(paradigm) : 코드를 어떻게 구조화할지에 대한 접근 방식과 사고방식 
  • 절차적 프로그래밍 패러다임 : 코드가 top-down이면서 선형적(linear)으로 구조화되는데 이때 프로시저(procedure)라 불리는 코드 단위에 미리 정해진 일련의 연산을 작성함  ex) C언어 
  • 객체 지향 프로그래밍 패러다임 : 클래스 기준으로 코드를 구조화하며, 클래스에는 로직과 데이터가 정의됨. ex) C++, 자바
  • 함수형 프로그래밍 패러다임 : 코드를 함수 단위로 구조화. 함수는 (절차적 프로그래밍의 함수와는 달리) 부수 효과가 없는 순수 함수임. 또한 함수 자체가 값으로 취급된다는 특징이 있음.  ex) 하스켈(Haskell) 
  • JS는 다중 페러다임 언어라 절차적, 객체 지향, 함수형 스타일 코드를 모두 작성할 수 있음. 

하위 호환성과 상위 호환성 

  • JS를 지탱하는 기본 원칙 중 하나가 하위 호환성. 
  • 하위 호환성 : 단 한번이라도 유효한 JS 문법이라고 인정되면 명세서가 변경되더라도 절대 그 유효성이 깨지지 않음. 하위 호환성 덕분에 1995년 작성한 코드가 시간이 흘러도 무조건 작동한다는 걸 보장받음.
  • 상위 호환성 : 만약 JS가 상위 호환성을 준수하는 언어였다면, 새로운 명세서에 추가된 문법으로 코드를 작성했을 때 이전 명세서를 준수하는 구형 엔진에서 문제가 발생하지 않아야 함. 하지만 JS는 상위 호환성을 보장하지 않음. 
  • HTML, CSS : 상위 호환성을 보장, 하위 호환성은 보장하지 않음. 
    • WHY? 마크업(HTML)이나 스타일링(CSS)는 본질상 선언적이므로 인식되지 않는 선언은 인식 가능한 선언에 최소한의 영향을 끼치면서 건너뛰는 게 훨씬 쉬움. 하지만 프로그래밍 언어를 처리하는 엔진이 이해할 수 없는 구문이나 표현식을 선택적으로 건너뛴다면 건더뛴 부분으로 인해 프로그램에 문제가 생기고 혼란이 발생할 수 있음.  

간극을 줄이기 위한 노력 (Transpile)

  • 구 엔진과 호환되지 않는 문법은 트랜스파일(transpile)을 통해 호환성 문제를 해결할 수 있음. 
    • 트랜스 파일 : JS 커뮤니티에서 만든 용어로, 한 형태에서 다른 형태로 소스 코드를 변환해주는 것. 트랜스파일러는 새로운 JS 문법을 오래된 문법으로 바꿔주며 주로 바벨(Babel)을 사용함. 
  • 옛날 문법을 쓰면 될 걸 트랜스파일러를 쓰면서까지 최신 문법으로 코드를 작성해야 할까? -> 최신 JS를 쓰는 게 좋다고 강하게 권유하는 이유는 코드가 깔끔해지고 의도하는 바를 효과적으로 전달할 수 있기 때문 

간극을 메우기 위한 방법 찾기 (Polyfill)

  • 폴리필(Polyfill) : 상위 호환성 문제가 새로운 문법이 아닌 근래에 추가되었지만, 아직 지원하지 않은 API 메서드 때문에 발생했다면 메서드 정의를 추가해 이미 이 메서드가 오래된 환경에도 있었던 것처럼 해주는 방법

ex) ES2019를 지원하지 않는 환경에서 finally()를 사용하려면 다음과 같은 폴리필이 필요함 

if(!Promise.prototype.finally){
	Promise.prototype.finally = function f(fn){
    	return this.then(
        	function t(v){
            	return Promise.resolve( fn() )
                	.then(function t(){
                    	retun v; 
                        });
                  }, 
                  function c(e){
                  	return Promise.resolve( fn() )
                    	.then(function t(){
                        	throw e; 
                            }); 
                    }
                  );
              };
     }

 

  • 바벨과 같은 트랜스파일러는 개발자가 작성한 코드 중 폴리필이 필요한 코드를 찾아내고 자동으로 폴리필을 추가함. 
  • 바벨과 트랜스파일러를 사용하는 시점? 실행 환경에서 아직 지원하지 않는 문법이나 api를 수동으로 조정해 코드 가독성이 떨어지지 않게 해야할 때

인터프리터 이해하기

  • JS는 인터프리터 언어일까 컴퍼일러 언어일까? 
  • 컴파일을 거치면 보통 분산 시스템에서 언제든 배포할 수 있는 바이너리 파일이 생성됨. 
  • 스크립트 언어나 인터프리터 언어는 대게 위에서 아래로 한 줄씩 코드가 실행되는 방식으로 만들어지고, 보통 실행이 시작되기 전에 거치는 사전단계가 없음. 그렇기 때문에 사전에 오류가 있는지 그 코드를 읽기 전까지 알 수 없음. 
  • 프로그램이 실행되기 전에 파싱(parsing)이라 부르는 사전 단계를 거치는 언어도 있음. 파싱과 컴파일을 거치는 언어는 파싱 단계에서 오류가 발견됨. 
  • '파싱'을 거치는 언어와 '컴파일'을 거치는 언어에는 어떤 공통점이 있을까? 
    • 모든 컴파일 언어가 파싱을 거친다. 
    • 파싱이 완전히 끝난 다음에는 파싱 결과인 추상 구문 트리(abstract syntax tree)를 컴퓨터가 실행할 수 있는 형태로 바꿔주는 작업이 이어짐. 
  • JS로 작성한 소스 코드는 실행전에 파싱을 거침. 컴파일 언어라고 할 수도 있음. 파싱이 끝난 코드는 컴파일러를 거쳐 최적화된 이진 코드로 변환된 후 실행이 됨. 
  • 컴파일 단계에서 일어나는 일 
    • JS 가상머신(virtual machine)에 전달할 이진 바이트 코드가 생성됨. -> 가상 머신의 역할이 전달받은 바이트 코드를 해석하는 것이라고 주장하는 사람들은 JS를 인터프리터 언어라고 해석하기도 함.  
    • 이 과정에서 JIT 컴파일러가 작동하며 최적화가 함께 진행이 된다. 

다시한번 JS로 만든 소스 코드가 실행될 때까지 어떤 절차를 거치는지 정리해보자면 다음과 같다. 

1. 개발자의 손을 떠난 코드는 바벨이 트랜스파일함. 이후 웹팩(webpack)을 비롯한 번들러를 거쳐 번들링되고, 그 결과가 JS 엔진에 전달됨. 
2. JS 엔진은 전달받은 코드를 파싱해 추상 구문 트리로 바꿈 
3. 이어서 엔진은 추상 구문 트리를 이진 바이트 코드로 바꿈. 이 과정에서 JIT 컴파일러가 작동하며 최적화가 함께 진행됨. 
4. 마지막으로 JS 가상 머신이 프로그램을 실행함. 

 

웹어셈블리 

  • Wasm : JS가 주력이 아닌 개발자도 JS 엔진에서 돌아가는 코드를 쉽게 작성할 수 있게 해주는 데 그 목적이 있고 이는 ASM.js의 철학과 유사함. 
    • 참고) ASM.js : 일반적인 JS 코드와 스타일이 다르지만 JS 부분집합으로 인정되는 엄연한 JS. 일관성 있는 타입 시스템을 사용하므로 성능 최적화가 매우 뛰어남. JS의 런타임 성능을 개선하는 방안으로 떠올랐음. 사람이 작성한 코드가 아니고 C 등의 언어를 작성한 코드를 트랜시프아리한 것이므로 코드 생성 과정에서 타입 관련 '주석'이 자동으로 붙음. 
  • Wasm은 일반 JS와는 다르게 실행 전 단계인 파싱과 컴파일을 거치지않아 파싱과 컴파일에 대한 본직적인 지연을 피한다는 데 있어 ASM.js와 다름. 
  • 파싱, 컴파일은 실행 직전(AOT, ahead of time)에 일어나고 배포는 JS 엔진의 별도 처리가 많이 필요하지 않은 바이너리 파일 형식으로 진행됨. 
  • Wasm을 사용하면 다른 언어에서 사용하는 기능, 특히 트랜스파일을 거치는 프로그램 전용으로 설계된 기능을 JS에도 추가해야 한다는 압박이 줄어듬. 
  • 웹 전용 기술이 아니며 JS도 아님.

 

글을 마치며 ☺️ 

이 책을 읽으며 가장 흥미로웠던 점은, 자바스크립트에 대해 알고 있다고 생각했던 개념들을 다시금 깊이 있게 바라보게 되었다는 것이다. 챕터 1에서는 자바스크립트가 단순한 언어가 아니라 깊은 이해가 필요한 언어임을 강조하는데, 이 부분이 특히 인상적이었다.

 

평소에는 자바스크립트를 직관적으로 이해하고 넘어가는 경우가 많았지만, 책을 읽으며 이러한 접근이 얼마나 위험할 수 있는지 깨달았다. 자바스크립트의 내부 동작 원리를 이해하지 않으면 예상치 못한 동작을 경험할 수 있다는 점이 크게 와닿았다. 특히 "자바스크립트를 아는 것과 이해하는 것은 다르다"는 말이 가장 기억에 남는다. 단순히 문법을 익히는 것이 아니라, 그 의미와 동작 원리까지 이해해야 진정으로 알고 있는 것이라는 점을 깨달았다. 또한, 자바스크립트가 단순한 인터프리터 언어가 아니라 컴파일 과정을 거치며 최적화를 진행한다는 점, 트랜스파일러와 폴리필이 코드 호환성을 유지하는 방식 등을 알게 되면 자바스크립트의 동작 방식에 대해 더 깊이 이해하게 된 것 같다.

 

앞으로도 단순히 코드를 작성하는 것에 그치지 않고, 코드의 동작 원리를 깊이 고민하며 더 나은 방식으로 개발하는 습관을 들이고 싶다. 이 책을 통해 얻은 개념들을 실제 프로젝트에도 적용해 보면서, 더욱 탄탄한 기본기를 갖춘 개발자로 성장해 나가야겠다는 다짐을 하며 공부를 이어가려고 한다.