1.3 명세서
TC39 ❓
JS를 관리하는 기술 운영 위원회로, JS의 공식 명세를 관리한다. 정기 모임에서 명세 변경 안건을 투표하고, 합의된 변경 사항을 국제 표준화 기구인 ECMA에 제출한다. 이 명세서에 JS 문법과 작동 방식이 정의된다.
위원회는 약 50~100명으로 브라우저를 만드는 조직(모질라, 구글, 애플)과 하드웨어 제조사(삼성 등) 같이 웹에 투자를 많이 하는 회사 출신이 주를 이룬다. 격월로 회의를 열고, 3일동안 진행한다. 지난 회의 이후 진행된 작업을 검토하고, 이슈를 논의하고, 투표를 진행한다. 모든 제안은 다섯 단계로 이루어진다. (0단계 부터 시작된다.) 4단계까지는 몇 달 ~ 몇 년이 걸린다.
새로운 아이디어가 0단계로 올라가는 방법
TC39 회원이 아닌 누군가가 소셜 미디어 같은 비공식 수단을 통해 아이디어를 '제안'한다. 위원 중 한 명이 '0단계 이전'의 아이디어가 가치 있다고 생각해 옹호하면 '0단계'로 올라온다. 반드시 위원의 지원이 필요하다. 제안서 작성 과정은 누구나 참여할 수 있지만 의결은 TC39 위원만 참여할 수 있다.
공개 깃허브 저장소에서 확인할 수 있다.
JS 버전
JS에는 버전이 없다. TC39와 ECMA에 의해 유지되는 공식적인 표준 JS는 단 하나이다. 2000년대 초반, 마이크로소프트가 JS를 불완전하게 리버스 엔지니어링해 당시에는 여러 버전의 JS가 있었지만, 버전으로 JS를 구분하던 시대는 지났다.
모든 주요 브라우저, 디바이스 제조사는 단 하나뿐인 명세서를 기준으로 JS 구현체를 만든다. (크롬에서 돌아가는 엔진인 V8이 모질라의 엔진 스파이더멍키와 다르거나 호환하지 않는 기능을 구현하는 일은 절대 없다.)
덕분에 우리는 단 하나의 JS만 배워도 되며, 어디서나 동일한 JS를 이용할 수 있게 되었다.
☄️ 1.3.1 JS를 지배하는 웹
JS 구현체를 만들 때는 웹 브라우저 환경을 가장 중요하게 고려해야 한다. 대부분 명세서에 정의된 JS와 브라우저 엔진에서 돌아가는 JS는 동일하지만 반드시 알아야 할 차이점이 있다. 명세서가 개정될 때, 개정안이 기존 브라우저 엔진에서 실행되던 JS의 작동 방식과 다른 경우가 발생한다.
역사적 배경
JS 엔진 제조사들은 명세서 개정안을 반영했을 때 기존에 잘 보이던 콘텐츠가 깨지는 경우, 명세서 개정안을 자사 엔진에 반영하지 않겠다는 결정을 내리게 된다.
불일치가 발생하면 TC39 위원회는 기존 결정을 철회하고 명세서를 웹에 맞춘다. 배열 메서드 contains()
가 몇몇 JS 프레임워크에서 충돌이 발생한다는 걸 발견하고 메서드명을 includes()
로 수정한 사례가 대표적이다. (flatten()
-> flat()
)
하지만, 때때로 TC39는 브라우저 기반 JS 엔진이 명세서를 준수할 가능성이 거의 없음에도 불구하고 명세서를 변경하지 않는 경우가 있다. 이런 문제는 명세서 페이지의 부록 B을 참고해 해결할 수 있다.
명세서 페이지의 부록
역사적인 사례로 명세서에 등장하지 않은 JS 문법과 API를 다룬다.
🎈 B.1과 B.2절
0 접두사가 붙은 8진 리터럴, 빌트인 전역 함수 escape()
와 unescape()
, 문자열을 쉽게 DOM으로 바꿔주는 헬퍼 함수 anchor()
와 blink()
, 정규 표현식 메서드 compile()
🎈 B3절
웹 엔진과 웹이 아닌 엔진 모두에서 실행되지만, 작동 결과가 달라 문제를 초래할 수 있는 문법과 의미론(시맨틱스), 대부분 엄격 모드에서 실제 코드가 돌아갈 때 오류를 유발하는 상황
실제 개발 상황
부록 B에 있는 명세를 자주 접하지는 않겠지만 미래를 위해 관련 구현체는 사용하지 않는 것이 좋다. 가급적 특정 JS 엔진에 종속되지 않으면서 명세서를 준수하는 구현체만 사용하도록 하자.
☄️ 1.3.2 JS지만 JS가 아닌 웹 전용 문법
❓다음 코드는 JS 프로그램일까❓
alert("JS야 안녕!");
관점에 따라 프로그램일 수도 아닐 수도 있다. alert()
는 명세서에는 없지만 모든 웹 환경에서 지원하는 함수이다. 하지만 이런 문법이 부록 B에 적혀있지 않다. 왜일까?
브라우저 엔진, Node.js 등과 같이 JS가 실행되는 환경은 전역 스코프에 API를 추가해 자체적으로 사용할 수 있는 기능을 제공한다. alert()
함수가 여기에 해당된다.
-> JS처럼 보이는 API 상당수가 실제로는 웹에서만 지원되는 API인 경우가 많다.
ex) fetch()
, getCurrentLocation()
, getUserMedia(),
fs.write()
JS처럼 인식되는 대표적인 메서드 console.*
는 명세서에는 없지만 광범위하게 사용되어 거의 모든 환경에서 이 메서드를 지원한다.
결론
alert()
와 console.log()
는 JS에 정의된 문법이 아니지만, JS의 함수와 객체 메서드 규칙을 따르고 JS처럼 보인다. 동작의 이면은 해당 코드가 돌아가는 환경에 의해 달라지긴 하지만 JS라는 생태계에 속하려면 표면상 JS 문법 규칙을 준수해야 한다.
여러 브라우저를 대응하다보면 브라우저 간 차이 때문에 'JS는 일관성이 없다'라고 생각하지만, 이 차이는 JS의 작동 방식이 아닌 환경에 따른 작동 방식 때문에 발생한다.
따라서 alert()
코드를 본다면 alert
호출은 JS이지만 alert
자체는 명세서에 없는 손님인 것이다.
☄️ 1.3.3 모든 코드가 JS인 것은 아닙니다
개발자 도구
개발자 도구는 개발자를 위한 도구로 개발자 경험(DX)을 가장 우선순위에 둔다. 명세서에 정의된 명세를 정확히 재현하기 위해 만든 도구가 아니다. 따라서 콘솔이나 REPL(Node.js의 환경)이 순수 JS를 지원할 것이라는 생각으로 실험하면 안된다.
필자는 개발자에게 편리함(변수나 프로퍼티 자동 완성 등 UX 측면)을 제공하는 개발자 도구를 긍정적으로 생각한다. 다만, 개발자 도구가 JS의 프로그램 처리 방식을 항상 엄격하게 준수하지 않는다는 점과 이를 기대해서도 안 된다는 점을 지적한다. 개발자 도구가 이를 목표로 만들어 진 것이 아니기 때문이다.
개발자 편의를 위해 만든 도구들은 브라우저마다 동작도 다르고 자주 바뀌기 때문에 브라우저별 차이는 구체적으로 비교하지 않겠다. 다만 개발자 도구가 순수 JS 작동 방식을 보장하지 않는다는 점을 알아보기 위해 환경별 차이를 몇 가지 살펴보자.
콘솔 '전역 스코프' 최상위 레벨에서 var나 function으로 변수나 함수를 선언하면 실제 전역에 변수가 생기는지 여부(그리고 이 변수가 window 프로퍼티 속성을 지원하는지, 그 역은 성립하는지 여부)
'전역 스코프' 최상위 레벨에서 let과 const로 변수 여러 개를 선언했을 때 작동 방식
첫 번째 줄에 "use strict";를 입력한 후 엔터를 눌렀을 때, js 파일 첫 줄에 "use strict"; 를 입력한 것처럼 해당 콘솔 세션이 엄격 모드로 작동하는지 여부와 첫 번째가 아닌 다른 줄에 "use strict";를 입력해도 해당 세션이 엄격 모드로 작동하는지 여부
비엄격 모드에서 함수를 호출할 때 this의 기본 바인딩 방식과 '전역 객체'가 쓰이는 경우, 실제 기대하 는 전역 변수가 이 전역 객체에 있는지 여부
여러 줄을 입력할 때 호이스팅hoisting의 작동 방식(2부 '스코프와 클로저'에서 자세히 다룸)
기타 등등
콘솔에 입력한 코드는 JS 엔진이 .js 파일을 다루는 방식과 동일한 방식으로 처리되지 않는다. 콘솔의 목적은 개발자가 짧은 코드를 입력하고 그 결과를 즉시 확인하기 위함이다. JS 엔진과 콘솔의 유스 케이스는 완전히 다르다. 따라서 개발자 도구가 두 엔진처럼 작동하길 기대하는 것은 무리이다.
콘솔에 나타난 결과가 JS 문법을 정확히 지키더라도 결과를 신뢰하지 말자. 자세한 내용은 항상 명세서를 읽자.
Last updated