ES6 In Depth 시리즈는 ECMAScript 표준의 6번째 에디션(줄여서 ES6)을 통해 JavaScript 에 새로 추가된 기능들을 살펴보는 시리즈입니다.
지난번 ES6 모듈에 관한 글까지 우리는 ES6 에 새로 도입된 주요 기능들을 살펴보았습니다.
이번 글에서는 새로운 기능을 십여 개 더 살펴보려고 합니다. 우리가 지금까지 깊게 살펴보지 않은 기능들입니다. JavaScript 라는 건축물의 벽장과 구석방들을 들여다보는 재미있는 여행이라고 생각하세요. 거대한 동굴도 한두 개 나올 겁니다. 아직 읽지 않은 ES6 in depth 시리즈가 있는지 확인해 보세요. 다른 글들을 읽기 전에 이번 글을 먼저 읽는 것은 별로 좋지 않습니다!
좋습니다. 이제 시작하죠.
벌써 쓰고 있을지도 모르는 기능들
ES6 는 지금까지 다른 표준에 있었거나, 비표준이었지만 널리 사용되던 기능들을 표준화했습니다.
- TypedArray,
ArrayBuffer
,DataView
. 이 타입들은 모두 WebGL 표준의 것들입니다. 이 타입들은 Canvas, Web Audio API, WebRTC 등 많은 API 들에서 사용되어왔습니다. 이 타입들은 많은 양의 바이너리 데이터나 숫자 데이터를 처리할 때 편리합니다.예를 들어 만약Canvas
의 랜더링 컨텍스트에 당신이 원하는 기능이 없을 경우, 그리고 만약 당신이 그것을 직접 구현할만큼 열정이 넘칠 경우, 당신은 다음처럼 구현할 수 있습니다.var context = canvas.getContext("2d"); var image = context.getImageData(0, 0, canvas.width, canvas.height); var pixels = image.data; // Uint8ClampedArray 객체 // ... 여기에 코드를 작성하세요! // ... `pixels` 안의 비트를 직접 조작하세요 // ... 그리고 조작한 픽셀을 canvas에 출력하세요 context.putImageData(image, 0, 0);
표준화 과정에서 TypedArray 에
.slice()
,.map()
,.filter()
같은 메소드들이 추가됐습니다. - Promise. Promise 를 한 문단으로 설명하는 것은 감자칩을 하나만 먹는 것처럼 불가능합니다. 얼마나 어려운지 말도 하지 마세요. 엄두도 못 낼 일입니다. 무엇을 말해야 할까요? Promise 는 비동기 JS 프로그래밍을 위한 구성요소입니다. Promise 는 나중에 비로소 유효해지는 값을 나타냅니다. 그래서, 예를 들어 당신이
fetch()
함수를 호출하면,fetch()
함수가 완료될 때까지 실행이 블락킹되지 않고, 즉시Promise
객체를 리턴합니다.fetch()
작업은 백그라운드에서 수행됩니다. 그리고fetch()
응답이 도착하면 당신을 되부를 것입니다(callback). Promise 는 callback 만 쓰는 것보다 좋은 방법입니다. 왜냐하면 Promise 는 정말 깔끔하게 체인(chain)으로 엮을 수 있기 때문입니다. Promise 는 일급(first-class) 객체이며 재미있는 메소드들을 갖고 있습니다. 또 에러 핸들링 코드도 간결하게 작성할 수 있습니다. 폴리필을 이용하면 지금 당장 브라우저에서 Promise 를 사용할 수 있습니다. 만약 Promise 에 대해 아직 잘 모르겠다면, Jake Archibald 의 상세한 설명을 보세요. - 블록 스코프(block scope) 함수. 원래 이것은 쓰면 안되는 거였습니다. 하지만 아마 써 본 적이 있을 겁니다. 의도하지 않게요.ES1 부터 ES5 까지, 아래 코드는 문법적으로 허용되지 않는 것이었습니다.
if (temperature > 100) { function chill() { return fan.switchOn().then(obtainLemonade); } chill(); }
if
블록 안에 함수를 선언하는 것은 원칙적으로 금지였습니다. 함수 선언은 톱레벨(toplevel)에서만 가능했습니다. 또는 어떤 함수 블록의 제일 바깥쪽 블럭 안에서만 가능했습니다.하지만 실제로는 이 코드가 거의 모든 주요 브라우저들에서 동작했습니다. 뭐 그런거죠.
하지만 브라우저들 사이에 호환성이 없었습니다. 브라우저들마다 상세 규정이 조금씩 달랐습니다. 어쨌든 동작했습니다. 그리고 많은 웹 페이지들이 아직도 이 기능을 사용하고 있습니다.
ES6 가 이 기능을 표준화했습니다. 감사합니다. 이제 함수는 블럭의 최상단으로 호이스팅(hoisting) 됩니다.
불행히도, Firefox 와 Safari 는 새로운 표준을 아직 구현하지 않고 있습니다. 그래서 지금은, 함수 표현식을 대신 쓰는 것이 좋습니다.
if (temperature > 100) { var chill = function () { return fan.switchOn().then(obtainLemonade); }; chill(); }
블록 스코프 함수가 지금까지 표준화되지 않았던 이유는 하위 호환성 조건이 무척 까다로왔기 때문입니다. 누구도 이 문제를 해결할 수 있다고 생각하지 않았습니다. ES6 는 아주 이상한 규칙을 도입해서 이 문제를 해결했습니다. 즉, non-strict 코드에만 적용되는 규칙입니다. 저는 이 규칙을 설명할 수 없습니다. 그냥 저를 믿으세요. strict 모드만 사용하세요.
- 함수의 name 속성. 모든 주요 JS 엔진들이 오랫동안 지원해온 비표준 기능이 또 있습니다. 함수 객체의
.name
속성입니다. 이 속성에는 함수 이름이 저장됩니다. ES6 는 이 속성을 표준화해서 좀 더 낫게 만들었습니다. 그래서 이름이 부여되지 않은 함수에도 의미있는.name
을 추론해서 할당합니다.> var lessThan = function (a, b) { return a < b; }; > lessThan.name "lessThan"
세상에는
.then
메소드에 인자로 전달되는 콜백 함수를 비롯해서 기타 이름을 추론할 수 없는 함수들이 있습니다. 이런 경우,fn.name
에는 공백 문자열이 할당됩니다.
좋은 것들
Object.assign(target, ...sources)
. 새로운 표준 라이브러리 함수입니다. 언더스코어(Underscore)의_.extend()
와 유사합니다.- 함수 호출을 위한 전개 연산자(spread operator). 스프레드(spread) 연산자는 누텔라와 상관 없습니다. 누텔라는 맛있는 스프레드(spread, 빵에 발라먹는 쨈)입니다. 어쨌든 전개 연산자(spread operator)도 달콤합니다. 좋아하게 될 것입니다.몇 달 전에 우리는 레스트 파라메터(rest parameter)를 살펴봤습니다. 레스트 파라메터는 함수가 임의 갯수의 인자를 전달 받는 방법입니다. 임의적이고, 모호한
arguments
객체보다 문명화된 방법입니다.function log(...stuff) { // stuff 는 레스트 파라메터. var rendered = stuff.map(renderStuff); // stuff 는 진짜 배열. $("#log").add($(rendered)); }
그 때 우리가 얘기하지 않은 것이 있습니다. 비슷한 방법으로, 함수를 호출할 때 임의 갯수의 인자를 전달하는 방법이 있습니다.
fn.apply()
대신 쓸 수 있는 좀 더 문명화된 방법입니다.// 배열 안의 모든 값을 로그로 남기기 log(...myArray);
당연히, 전개 연산자는 모든 이터러블 객체와 함께 동작 가능합니다. 그래서
Set
에 담겨진 모든 것을log(...mySet)
코드를 이용해서 로그로 남길 수 있습니다.레스트(rest) 파라메터와 달리, 전개 연산자는 한 개의 인자 구문 안에서 여러번 사용할 수 있습니다.
// kicks 는 trids 보다 앞서 오는 인자 log("Kicks:", ...kicks, "Trids:", ...trids);
전개 연산자는 배열의 배열을 단순한 배열로 만들 때 편리합니다.
> var smallArrays = [[], ["one"], ["two", "twos"]]; > var oneBigArray = [].concat(...smallArrays); > oneBigArray ["one", "two", "twos"]
…아마도 제게 필요한 기능은 이것 하나뿐인 것 같습니다. 저는 하스켈이 좋습니다.
- 전개 연산자를 이용한 배열 만들기. 우리는 또 지난 글에서 “레스트(rest)” 패턴을 디스트럭처링에 이용하는 방법에 대해 살펴봤습니다. 어떤 배열에서 필요한 수만큼 엘리먼트를 가져오는 방법입니다.
> var [head, ...tail] = [1, 2, 3, 4]; > head 1 > tail [2, 3, 4]
생각해보세요! 비슷한 방법으로 임의 숫자의 엘리먼트를 배열로 만드는 문법이 있습니다.
> var reunited = [head, ...tail]; > reunited [1, 2, 3, 4]
이 문법에는 전개 연산자가 함수 호출에 사용될 때 적용되는 것과 똑같은 규칙이 적용됩니다. 예를 들어 전개 연산자로 배열을 만들 때, 함수 호출 때처럼, 전개 연산자를 여러번 쓸 수 있습니다.
- 꼬리 호출(proper tail calls). 이 기능은 제게 너무 낯선 것이어서 설명하기 어렵습니다.이 기능을 이해하기 위한 가장 좋은 자료는 Structure and Interpretation of Computer Programs 의 1 페이지입니다. 만약 이 자료가 마음에 든다면, 다른 글도 읽어 보세요. section 1.2.1, “Linear Recursion and Iteration” 에 꼬리 호출(Tail calls)에 대한 설명이 있습니다. ES6 표준은 구현체가 “tail-recursive” 를 문서에 정의된 용어 그대로 지원하도록 규정합니다.주요 JS 엔진들 중에서 이 기능을 지원하는 브라우저는 아직 없습니다. 이 기능은 구현하기 어렵습니다. 하지만 머지않아 지원될 것입니다.
문자열
- 유니코드 버전 업그레이드. ES5 를 따르는 구현체는 적어도 유니코드 버전 3.0을 완벽히 지원해야 했습니다. ES6 구현체는 반드시 유니코드 5.1.0 이상을 지원해야 합니다. 우리는 이제 Linear B 문자열로 함수 이름을 지을 수 있습니다!Linear A 문자열 지원은 아직 완벽하지 않습니다. 2가지 이유가 있는데, 하나는 유니코드 버전 7.0 까지는 Linear A 문자열이 표준에 포함되지 않았기 때문이고, 또 하나는 아직까지 해독되지 못한 언어로 코딩하면 유지보수가 힘들 것이기 때문입니다.(JavaScript 엔진은 유니코드 6.1 에 추가된 이모지(emoji)를 지원합니다. 하지만, 우리는 😺 를 변수 이름으로 사용할 수 없습니다. 몇 가지 이유 때문에 유니코드 컨소시엄은 이모지를 식별자 용도로 쓰지 않기로 결정했습니다. 😾)
- 길이가 긴 유니코드 에스케이프 시퀀스 (Long Unicode escape sequences). ES6 는, 이전 ES 버전들처럼, 4자리 숫자(four-digit) 유니코드 에스케이프 시퀀스를 지원합니다. 예를 들면
\u212A
같은 형태입니다. 이건 멋진 일입니다. 당신은 긴 유니코드 에스케이프 시퀀스를 문자열에 쓸 수 있습니다. 만약 당신이 장난치고 싶다거나 현재 참여 중인 프로젝트의 코드 리뷰 정책이 엄격하지 않다면, 긴 유니코드 에스케이프 시퀀스를 이용해서 변수 이름을 지어 볼 수 있을 것입니다. 사람이 물구나무 선 모양의 이집트 상형문자 U+13021 (
) 를 쓰자면 약간 문제가 있습니다.13021
은 다섯자리 숫자입니다. 5자리는 4자리 보다 큽니다.ES5 에서는, 에스케이프 구문을 2번 써야 했습니다. UTF-16 써로게이트 페어(surrogate pair) 말입니다. 이것은 암울한 느낌을 줍니다. 춥고, 불쌍하고, 원시적인 느낌 말입니다. ES6 는, 이탈리아 르네상스의 여명기처럼 멋진 변화를 가져왔습니다. 우리는 이제\u{13021}
처럼 쓸 수 있습니다. - BMP(Basic Multilingual Plane) 외부 영역에 대한 지원 강화. 이제
.toUpperCase()
와.toLowerCase()
메소드를 데제렛 알파벳 (Deseret alphabet) 에도 사용할 수 있습니다!같은 맥락에서,String.fromCodePoint(...codePoints)
도 기능은 오래된String.fromCharCode(...codeUnits)
와 아주 비슷하지만, BMP 를 벗어나는 코드 영역을 지원합니다. - 유니코드를 위한 RegExp. ES6 의 정규 표현식(regular expression)은 새로운
u
플랙을 지원합니다 이 플랙은 정규 표현식이 BMP 외부 영역의 문자를 2개의 코드로 분리된 문자가 아니라 단일한 문자로 취급하게 합니다. 예를 들어,u
플랙이 없다면,/./
는 반쪽 문자만 찾아낼 것입니다"😭"
. 하지만/./u
는 완전한 문자를 찾아냅니다.u
플랙을RegExp
에 추가하면 대소문자를 구분하지 않는 유니코드 검색도 가능해집니다. 그리고 긴 유니코드 문자 이스케이프 시퀀스 검색도 가능해집니다. 보다 자세한 내용은 Mathias Bynens 의 자세한 설명이 담긴 포스트를 참조하세요. - Sticky RegExp. 유니코드와 상관 없는 기능으로
y
플랙이 있습니다. 이 플랙은 sticky 플랙으로도 알려져 있습니다. sticky 정규 표현식은.lastIndex
속성으로 지정된 오프셋 이후의 문자열만 검색합니다. 만약 찾는 문자열이 존재하지 않으면 문자열 앞부분을 추가로 검색하지 않고 바로null
을 리턴합니다. - 공식적인 다국어 스펙 (An official internationalization spec). 다국어 기능을 제공하는 ES6 구현체는 반드시 ECMA-402, ECMAScript 2015 Internationalization API 스펙을 지원해야 합니다. 이 별도의 표준은
Intl
객체를 정의하고 있습니다. 이미 Firefox, Chrome, IE11+ 가 이 객체를 완벽하게 지원하고 있습니다. Node 0.12 도 이 객체를 지원합니다.
숫자
- 2진 표기법과 8진 표기법. 숫자 8,675,309 을 나타내는 멋진 표기법이 필요한가요?
0x845fed
같은 표기법만으로는 만족할 수 없나요? 이제부터는0o41057755
(8진수) 또는0b100001000101111111101101
(2진수) 같은 표기법을 사용할 수 있습니다.Number(str)
도 이제 이런 포맷의 문자열을 인식합니다.Number("0b101010")
는 42 를 리턴합니다.(여기서 잠깐, 임의의 진법을 변환하는 원래의 방법은number.toString(base)
와parseInt(string, base)
였습니다.) Number
객체의 새로운 함수들과 상수들. 이것들은 꽤 편리합니다. 관심 있다면, 표준을 직접 살펴보시는 것도 좋을 것입니다.Number.EPSILON
부터 시작하세요.아마도 가장 재밌고 새로운 아이디어는 −(253 – 1) 부터 +(253 – 1) 범위의 “safe integer” 일 것입니다. 이 특별한 범위의 정수들은 처음부터 JS 에 존재했습니다. 이 범위 안의 모든 정수들은 자신과 가장 가까운 이웃 정수들처럼 정확한 JS 숫자로 표현됩니다. 간단히 말해, 이 범위에서는++
와--
가 기대한 바대로 동작합니다. 이 범위 밖에 있는 홀수들은 64-bit 부동소수점으로 표현할 수 없습니다. 그나마 64-bit 부동소수점으로 표현할 수 있는 숫자들(모두 짝수들입니다)을 증가시키거나 감소시키면 정확하지 않은 결과값이 나옵니다. 당신 코드에서 이것이 문제가 되는 경우를 대비해, 표준은Number.MIN_SAFE_INTEGER
상수와Number.MAX_SAFE_INTEGER
상수, 그리고Number.isSafeInteger(n)
메소드를 제공합니다.Math
객체의 새로운 함수들. ES6 는 쌍곡선–함수–와 그–역함수–들, 그리고 세제곱근 계산을 위한Math.cbrt(x)
함수, 직각삼각형의 빗변 계산을 위한Math.hypot(x, y)
함수, 로그 계산을 위한Math.log2(x)
함수와Math.log10(x)
함수, 정수로그 계산을 돕기 위한Math.clz32(x)
함수 등등을 추가 제공합니다.Math.sign(x)
함수는 숫자의 부호를 리턴합니다.또 ES6 는 부호 붙은 정수 곱셈을 위해Math.imul(x, y)
함수를 제공합니다. 이 함수는 232 까지 계산 가능합니다. 이 함수는 의외였습니다… JS 가 64-bit 정수 또는 big integer 를 지원하지 않는다는 사실이 고통스러웠던 적이 없다면 그다지 큰 필요를 느끼지 못할 것입니다. 하지만 그런 필요가 있다면 무척 유용할 것입니다. 이 함수는 컴파일러를 만들 때 도움이 됩니다. Emscripten 이 이 함수를 이용해서 JS 의 64-bit 정수 곱셈을 구현합니다.비슷하게Math.fround(x)
함수도 32-bit 실수형 숫자를 지원하는 컴파일러를 만들 때 유용한 함수입니다.
마지막
이게 전부인가요?
글쎄요, 아닙니다. 저는 아직 모든 빌트인(built-in) 이터레이터들의 공통 프로토타입에 관한 비밀, GeneratorFunction 생성자, Object.is(v1, v2)
함수, Symbol.species
를 이용한 빌트인 객체 (Array 객체나 Promise 객체 같은)의 계승(subclassing) 법, 또 지금까지 표준화된 적 없는 글로벌 객체들의 동작 양식 등에 대해 말도 꺼내지 않았습니다.
그리고 분명 제가 놓친 것들도 있을 것입니다.
하지만 지금까지 따라오신 분들이라면 우리가 가고 있는 방향에 대한 큰 그림을 이해하실 것입니다. 당신은 ES6 의 기능들을 지금 당장 쓸 수 있다는 사실과 그렇게 하는 것이 더 나은 랭귀지를 쓰는 길이라는 사실을 알고 있습니다.
몇 일 전, Josh Mock 이 제게 말했습니다. 그는 방금 ES6 의 기능 8개를 50 라인짜리 코드에 적용했다고 말했습니다. 그러는 동안 ES6 의 새로운 기능을 쓴다는 사실을 인식하지 못했다고 합니다. 그가 사용했던 것은 모듈, 클래스, 디폴트 인자, Set
, Map
, 템플릿 문자열, 화살표 함수, let
구문 등입니다. (for
–of
루프 구문은 사용하지 못했다고 합니다.)
제 경험도 같습니다. 새로운 기능들은 서로 무척 잘 들어맞습니다. 당신은 결국 모든 JS 코드에 새로운 기능들을 쓰게 될 것입니다.
한편, 거의 모든 JS 엔진들이 지난 몇 달 동안 살펴본 새로운 기능들을 서둘러 구현하고 있으며, 서둘러 최적화하고 있습니다.
모든 일이 끝나고나면 랭귀지는 완벽해질 것입니다. 다시 뭔가를 바꿀 필요가 없을 것입니다. 저는 다른 일을 찾아야 할 것입니다.
농담입니다. ES7 을 위한 제안이 벌써 무르익고 있습니다. 몇 가지만 소개하면 다음과 같습니다.
- 지수 연산자.
2 ** 8
는 256 을 리턴할 것입니다. Firefox Nightly 에 구현되었습니다. Array.prototype.includes(value)
. 만약 배열이 인자로 주어진 value 를 포함하고 있다면 true 를 리턴합니다. Firefox Nightly 에 구현되었습니다. 아마도요.- SIMD. 최신 CPU가 제공하는 128-bit SIMD 명령어를 노출합니다. 이 명령어는 배열 안에 인접해 있는 2개, 또는 4개, 또는 8 개 배열 요소들에 대한 산술 연산을 수행합니다. 이 명령어는 오디오 비디오 스트리밍 처리, 암호 처리, 게임, 이미지 프로세싱 등 다양한 알고리즘 처리 속도를 극적으로 향상시킵니다. 아주 로레벨(low-level)이고, 아주 강력합니다. Firefox Nightly 에 구현되었습니다. 아마도요.
- 비동기 함수 (Async functions). 우리는 제너레이터에 관한 포스트에서 이 기능에 대한 힌트를 얻은 바 있습니다. 비동기 함수는 제너레이터와 유사합니다. 하지만 비동기 프로그래밍에 특화되어 있습니다. 우리가 제너레이터를 호출하면, 제너레이터는 이터레이터를 리턴합니다. 우리가 비동기 함수를 호출하면, 비동기 함수는 프라미스(promise)를 리턴합니다. 제너레이터는
yield
키워드를 이용해서 실행을 멈추고 값을 생성합니다. 대신 비동기 함수는await
키워드를 이용해서 실행을 멈추고 프라미스를 기다립니다.짧은 문장으로 설명하기는 어렵지만, 비동기 함수는 ES7 의 가장 중요한 기능이될 것입니다. - TypedObject. 이것은 TypedArray 의 후속편입니다. TypedArray 배열은 TypedObject 배열요소를 갖습니다. TypedObject 객체는 그냥 객체입니다. 하지만 속성의 타입이 지정된 객체입니다.
// 새로운 struct 타입을 만듭니다. // 모든 Point는 x와 y라는 이름의 2개 필드를 갖습니다. var Point = new TypedObject.StructType({ x: TypedObject.int32, y: TypedObject.int32 }); // 이제 정의한 타입의 인스턴스를 만듭니다. var p = new Point({x: 800, y: 600}); console.log(p.x); // 800
이런 일을 하는 이유는 성능 때문입니다. TypedArray 배열처럼, TypedObject 객체는 타입 지정 덕분에 생기는 장점(컴팩트한 메모리 사용과 속도)을 제공합니다. 게다가 그 장점을 사전에 협의된 모든 객체에 제공합니다. 이것은 모든 객체를 똑같은 타입으로 다루는 랭귀지의 특성과 비교되는 기능입니다.
이 기능 역시 JS 컴파일러를 만들 때 유용한 기능입니다.
Firefox Nightly 에 구현되었습니다.
- Class 와 속성 장식자 (property decorator). 장식자(decorator)는 속성, 클래스, 메소드 등에 부여하는 태그입니다. 예제를 보면 좀 더 쉽게 이해할 수 있습니다.
import debug from "jsdebug"; class Person { @debug.logWhenCalled hasRoundHead(assert) { return this.head instanceof Spheroid; } ... }
여기서
@debug.logWhenCalled
가 장식자입니다. 이 장식자가 메소드를 위해 어떤 역할을 하는지 상상할 수 있을 것입니다.장식자에 대한 자세한 내용은 제안서에 많은 예제와 함께 설명되어 있습니다.
반드시 언급해야 하는 멋진 결정이 하나 더 있습니다. 랭귀지의 기능에 관한 것은 아닙니다.
ECMAScript 표준화 위원회인 TC39 는 표준을 좀 더 자주 릴리즈 하는 방향으로 그리고 좀 더 개방적인 프로세스로 나아가려고 합니다. ES5 가 발표된 이후 6년이 지나서 ES6 가 발표됐습니다. 표준화 위원회는 ES7 을 ES6 발표 이후 딱 12개월 뒤에 발표할 계획입니다. 이후 개정판들도 12개월 간격으로 발표될 것입니다. 위에 나열한 기능들 중 일부는 시간에 맞게 표준에 포함될 것입니다. 이 기능들은 “기차를 놓치지 않고” ES7 의 일부가 될 것입니다. 시간 안에 완료되지 않는 기능들은 다음 기차를 타게 될 것입니다.
그동안 ES6 에 포함된 엄청난 양의 좋은 기능들을 나눌 수 있어서 아주 즐거웠습니다. 앞으로는 새로운 기능들이 이렇게 무지막지하게 많이 한꺼번에 쏟아지지 않을 것이라는 사실도 다행스럽습니다.
ES6 In Depth 를 함께 해주셔서 감사합니다! 이 시리즈가 즐거웠기를 바랍니다. 자주 만납시다.
이 글은 Jason Orendorff 가 쓴 ES6 In Depth: The Future 의 한국어 번역본입니다.
작성자: ingeeKim
"누구에게나 평등하고 자유로운 웹"에 공감하는 직장인.
8 댓글