JavaScript는 결코 지금과 같은 모습이 될 운명이 아니었습니다. 1995년, 브렌던 아이크(Brendan Eich)는 Netscape에 앉아 단 10일 만에 프로토타입을 만들어냈습니다. 10일이라니요. 수십억 개의 기기를 구동할 프로그래밍 언어를 설계하기는커녕, 제대로 된 명세서를 작성하기에도 턱없이 부족한 시간입니다. 그 결과, 개발자들이 거의 30년이 지난 지금까지도 발을 헛디디게 만드는 기묘한 특징(quirks)들을 가진 언어가 탄생했습니다. 하지만 그 서두른 창조물은 소프트웨어 역사상 가장 널리 배포된 단일 런타임이 되었습니다. 우아해서 승리한 것이 아니라, 웹이 그것을 필요로 하던 바로 그 순간 브라우저에 탑재되었기 때문입니다.
서둘러 태어나다
90년대 중반의 브라우저 전쟁은 우호적인 경쟁이 아니었습니다. Netscape는 Navigator 브라우저에서 Java와 함께 실행될 수 있는 가벼운 스크립팅 언어가 필요했습니다. 아이크는 경영진을 안심시킬 수 있을 만큼 Java와 비슷해 보이면서도, 프로그래머가 아닌 사람들도 웹 페이지에 쉽게 붙여넣을 수 있을 만큼 단순한 것을 만드는 임무를 맡았습니다. 마감 기한은 터무니없었습니다. 그는 Mocha를 만들어냈고, 이는 곧 LiveScript로, 그리고 마지막에는 Java의 인기에 편승하기 위한 마케팅 전략으로서 JavaScript로 이름이 바뀌었습니다.
그 서두른 탄생은 영구적인 상처를 남겼습니다. + 연산자가 경고 없이 문자열과 숫자를 결합할 때, 타입 강제 변환(Type coercion)은 여전히 초보자들을 혼란스럽게 합니다. typeof null이 "object"를 반환하는 이유는 초기 구현의 버그 때문인데, 웹 생태계를 망가뜨릴까 두려워 아무도 감히 이를 수정하지 못하고 있습니다. 자동 세미콜론 삽입(ASI)은 소리 없는 오류를 일으킵니다. var로 선언된 변수는 예측할 수 없는 방식으로 스코프를 유출합니다. 이것들은 추상적인 설계 결함이 아닙니다. 1995년 5월의 2주간의 질주(sprint)에서 직접적으로 기인한 일상적인 좌절감입니다.
기묘한 혈통
JavaScript를 자세히 들여다보면 서로 다른 세 가지 혈통이 엮여 있는 것을 볼 수 있습니다. 문법은 Java에서 많은 부분을 빌려왔습니다. 중괄호, if 문, for 루프는 C 스타일 언어를 사용하는 사람들에게 익숙한 형태를 제공합니다. 하지만 그 표면 아래의 동작 방식은 완전히 다릅니다.
이 언어의 진정한 계산적 핵심은 Lisp의 방언인 Scheme에서 왔습니다. 여기서 JavaScript는 일급 함수(first-class functions)를 얻었습니다. 즉, 함수를 인자로 전달하거나, 다른 함수에서 반환하거나, 변수에 할당할 수 있다는 뜻입니다. 또한 클로저(closures)를 제공했는데, 이는 외부 함수가 실행을 마친 후에도 내부 함수가 외부 함수의 스코프에 접근할 수 있게 해줍니다. 콜백을 작성하거나 이벤트 리스너를 연결해 본 적이 있다면, 여러분은 Scheme으로부터 물려받은 DNA를 사용한 것입니다.
다음은 Self에서 유래한 객체 모델입니다. JavaScript는 엄격한 클래스를 사용하는 고전적인 상속 대신 프로토타입(prototypes)을 사용합니다. 객체는 다른 객체에 직접 연결될 수 있으며, 속성 조회를 상위로 위임할 수 있습니다. 클래스를 정의하지 않고도 Object.create를 사용하여 객체를 생성하고 체인을 구축할 수 있습니다. 현대의 JavaScript에는 class 키워드가 추가되었지만, 이는 대부분 이 기저에 깔린 프로토타입 메커니즘 위에 씌워진 문법적 설탕(syntactic sugar)에 불과합니다.
페이지 장식에서 진지한 도구로
처음 몇 년 동안 JavaScript는 사소한 작업만 수행했습니다. 서버로 전송되기 전 폼 입력을 검증하거나, 마우스 호버 시 이미지를 교체하는 정도였습니다. 도구가 아닌 장난감에 가까웠습니다. 브라우저 구현이 일관되지 않았기 때문에 개발자들은 Netscape와 Internet Explorer를 위해 서로 다른 코드 경로를 작성해야 하는 경우가 많았습니다.
ECMAScript를 통한 표준화가 그 궤도를 바꾸어 놓았습니다. 명세서는 브라우저 제조사들에게 공통의 구현 목표를 제공했고, 이는 최악의 호환성 문제를 서서히 해결해 나갔습니다. 그리고 Ajax가 등장했습니다.
Asynchronous JavaScript and XML의 약자인 Ajax는 단일 신기술이 아니라 기존 요소들을 결합한 패턴이었습니다. 핵심 요소는 XMLHttpRequest 객체였는데, 이를 통해 브라우저는 페이지 전체를 다시 불러오지 않고도 백그라운드에서 서버에 데이터를 요청할 수 있었습니다. Google이 2005년에 Maps를, 2004년에 Gmail을 출시했을 때, 사용자들은 브라우저 탭 안에서 갑자기 데스크톱 앱과 같은 반응성을 경험하게 되었습니다. 웹 페이지는 애플리케이션이 되었습니다. JavaScript는 더 이상 곁들임 요리가 아니었습니다. 그것은 메인 요리였습니다.
속도와 야망
순수 성능은 한때 JavaScript의 가장 큰 웃음거리였습니다. 초기 인터프리터는 느렸습니다. 그러다 2008년 Google이 Chrome과 함께 V8 엔진을 출시했고, 그 농담은 더 이상 웃기지 않게 되었습니다. V8은 JIT(just-in-time) 컴파일을 도입하여, JavaScript를 한 줄씩 해석하는 대신 런타임에 기계어로 변환했습니다. 또한 숨겨진 클래스(hidden classes)와 인라인 캐싱(inline caching)을 도입하여 동적 객체에서도 속성 접근을 빠르게 만들었습니다. 다른 브라우저들도 각자의 고속 엔진으로 대응했고, 이 언어는 갑자기 실제 연산을 수행할 수 있을 만큼 빨라졌습니다.
그 속도는 다음 세대의 변화를 가능하게 했습니다. Ryan Dahl은 2009년 Node.js를 출시하며, 브라우저에서 V8을 분리해 이벤트 기반의 논블로킹(non-blocking) 입출력 모델로 감싸 안았습니다. 과거의 웹 서버는 들어오는 모든 요청마다 새로운 스레드를 생성했기에, 높은 동시성 부하가 걸리면 무너졌습니다. Node.js는 이벤트 루프와 비동기 콜백을 사용하여 단일 스레드에서 수만 개의 동시 연결을 처리했습니다. JavaScript는 클라이언트를 넘어 서버로, 빌드 도구로, 그리고 결국 모든 곳으로 흘러 들어갔습니다.
어디에나 존재하는 언어
이제 JavaScript는 제작자가 상상조차 하지 못했던 곳에서 실행됩니다.
프론트엔드에서는 React와 Vue가 현대적인 인터페이스가 구축되는 방식을 결정합니다. 브라우저가 비용이 많이 드는 전체 페이지 새로고침을 수행하지 않고도, 상태 변화에 따라 컴포넌트가 업데이트됩니다. 백엔드에서는 Node.js가 API와 실시간 서비스를 구동하며, Bun과 같은 최신 런타임은 더 빠른 패키지 관리와 내장 번들링을 실험하고 있습니다.
React Native는 JavaScript 코드를 네이티브 플랫폼 뷰로 변환하여, 개발 팀이 Swift와 Kotlin으로 된 두 개의 완전히 분리된 코드베이스를 유지할 필요 없이 iOS와 Android용 모바일 애플리케이션을 출시할 수 있게 해줍니다. Electron은 웹 기술을 Chromium 셸로 감싸 데스크톱 소프트웨어를 구축하며, Slack과 Visual Studio Code가 여러분의 노트북에 설치될 수 있는 것도 바로 이 덕분입니다. 이 언어는 클라우드 함수와 엣지 컴퓨팅 워커 내부에도 자리 잡고 있으며, 분산 네트워크를 통해 최종 사용자로부터 불과 몇 밀리초 떨어진 곳에서 로직을 실행합니다.
풍요의 역설
편재성(Ubiquity)에는 대가가 따릅니다. 생태계는 거대하며, 그 규모는 불안을 야기합니다. 이전 빌드 도구의 설정을 마치기도 전에 새로운 빌드 도구가 등장합니다. 프레임워크의 인기는 계절처럼 빠르게 뜨고 집니다. JavaScript 세계를 벗어나지 않고도 거의 모든 문제를 해결할 수 있지만, 우선 수많은 독자적인 성향이 강한(highly opinionated) 솔루션 중 하나를 선택해야 합니다. 의존성 트리는 깊고 취약해집니다. 배열의 왼쪽 패딩을 처리하는 작은 패키지 하나가 레지스트리에서 사라지는 것만으로도 수천 개의 하위 프로젝트가 망가질 수 있습니다.
이 중 어느 것도 우연이 아닙니다. 웹이 무질서하기 때문에 JavaScript도 무질서한 것입니다. JavaScript는 하위 호환성, 서둘러 만들어진 표준, 그리고 서로 충돌하는 구현 이익들이 층층이 쌓인 유기적인 레이어 케이크와 같습니다. 하지만 바로 그 무질서함 덕분에 이 언어는 강력합니다. 웹은 어디에나 존재하며, JavaScript는 기본적으로 모든 브라우저 내부에 존재하기 때문에, 우리가 가진 것 중 범용 런타임(universal runtime)에 가장 가까운 존재입니다.
JavaScript가 단순한 스크립팅 언어였던 시절은 오래전에 끝났습니다. 이제 JavaScript는 소프트웨어를 위한 글로벌 플랫폼이며, 10일 만에 만들어졌지만 '웹은 이전의 것을 깨뜨려서는 안 된다'는 보이지 않는 계약으로 유지되고 있습니다. 그 강점과 함께 그 상처를 함께 배운다면, 여러분은 현대 인터넷 그 자체의 역사를 배우고 있는 것입니다.
