TypedArray
TypedArray
TypedArray 객체는 기본 바이너리 데이터 버퍼에 대한 배열형 뷰(array-like view)를 나타낸다. TypedArray라는 이름의 전역 속성은 존재하지 않으며, 직접 보이는 TypedArray 생성자도 없다. 대신 아래에 나열된 것처럼 특정 요소 타입에 대한 typed array 생성자들을 값으로 갖는 여러 개의 서로 다른 전역 속성들이 존재한다.
본문
개요
TypedArray 생성자(JavaScript 프로그램에 노출된 전역이 없으므로 그 "내재성(intrinsicness)"을 나타내기 위해 종종 %TypedArray%라고 부름)는 모든 TypedArray 하위 클래스의 공통 슈퍼클래스 역할을 한다. %TypedArray%를 모든 typed array 하위 클래스에 공통 인터페이스의 유틸리티 메서드를 제공하는 "추상 클래스"라고 생각하면 된다. 이 생성자는 직접 노출되지 않으며, Object.getPrototypeOf(Int8Array) 등을 통해서만 접근할 수 있다.
TypedArray 하위 클래스(예: Int8Array)의 인스턴스를 만들 때 메모리 내부에 array buffer가 생성되거나, 생성자 인자로 ArrayBuffer 객체가 주어지면 그 ArrayBuffer가 대신 사용된다. 버퍼 주소는 인스턴스의 내부 속성으로 저장되며, %TypedArray%.prototype의 모든 메서드는 그 array buffer 주소를 기준으로 값을 설정하고 가져온다.
TypedArray 객체
| 타입 | 값 범위 | 크기(바이트) | Web IDL 타입 |
|---|---|---|---|
Int8Array |
-128 ~ 127 | 1 | byte |
Uint8Array |
0 ~ 255 | 1 | octet |
Uint8ClampedArray |
0 ~ 255 | 1 | octet |
Int16Array |
-32768 ~ 32767 | 2 | short |
Uint16Array |
0 ~ 65535 | 2 | unsigned short |
Int32Array |
-2147483648 ~ 2147483647 | 4 | long |
Uint32Array |
0 ~ 4294967295 | 4 | unsigned long |
Float16Array |
-65504 ~ 65504 |
2 | N/A |
Float32Array |
-3.4e38 ~ 3.4e38 |
4 | unrestricted float |
Float64Array |
-1.8e308 ~ 1.8e308 |
8 | unrestricted double |
BigInt64Array |
-2^63 ~ 2^63 - 1 | 8 | bigint |
BigUint64Array |
0 ~ 2^64 - 1 | 8 | bigint |
값 인코딩과 정규화(Value encoding and normalization)
모든 typed array는 ArrayBuffer에서 동작하며, 각 요소의 정확한 바이트 표현을 관찰할 수 있으므로 숫자가 바이너리 형식으로 어떻게 인코딩되는지가 중요하다.
- 부호 없는 정수 배열(
Uint8Array,Uint16Array,Uint32Array,BigUint64Array)은 숫자를 바이너리로 직접 저장한다. - 부호 있는 정수 배열(
Int8Array,Int16Array,Int32Array,BigInt64Array)은 2의 보수(two's complement)로 숫자를 저장한다. - 부동소수점 배열(
Float16Array,Float32Array,Float64Array)은 IEEE 754 부동소수점 형식으로 저장한다. JavaScript 숫자는 기본적으로 배정밀도 부동소수점 형식(=Float64Array)을 사용한다.Float32Array는 가수부에 52비트 대신 23비트, 지수부에 11비트 대신 8비트를 사용한다.Float16Array는 가수부 10비트, 지수부 5비트를 사용한다. 명세는 모든NaN값이 동일한 비트 인코딩을 사용하도록 요구하지만, 정확한 비트 패턴은 구현에 따라 다르다. Uint8ClampedArray는 특별한 경우이다.Uint8Array처럼 숫자를 바이너리로 저장하지만, 범위를 벗어나는 숫자를 저장하면 최상위 비트를 잘라내는 대신 수학적 값으로 0~255 범위에 클램프(clamp)한다.
Int8Array, Uint8Array, Uint8ClampedArray를 제외한 모든 typed array는 각 요소를 여러 바이트로 저장한다. 이 바이트들은 최상위 바이트부터(big-endian) 또는 최하위 바이트부터(little-endian) 정렬될 수 있다. typed array는 항상 플랫폼의 네이티브 바이트 순서를 사용한다. 버퍼를 읽고 쓸 때 엔디언(endianness)을 지정하려면 DataView를 사용해야 한다.
이 typed array들에 쓸 때, 표현 가능한 범위를 벗어난 값은 정규화된다.
- 모든 정수 배열(
Uint8ClampedArray제외)은 고정 폭 숫자 변환을 사용하며, 먼저 숫자의 소수부를 잘라낸 다음 최하위 비트를 취한다. Uint8ClampedArray는 먼저 숫자를 0~255 범위로 클램프한다(255보다 큰 값은 255, 0보다 작은 값은 0). 그런 다음 결과를 버림(floor) 대신 반올림(round)하여 가장 가까운 정수로 만들되, half-to-even 방식을 사용한다. 즉 숫자가 정확히 두 정수 사이에 있으면 가장 가까운 짝수로 반올림한다. 예를 들어0.5는0,1.5는2,2.5는2가 된다.Float16Array와Float32Array는 64비트 부동소수점 숫자를 32비트와 16비트로 변환할 때 "round to even"을 수행한다. 이는Math.fround()와Math.f16round()가 제공하는 알고리즘과 동일하다.
조정 가능한(resizable) 버퍼를 볼 때의 동작
TypedArray가 크기 조정 가능한 버퍼의 뷰로 생성될 때, 기본 버퍼를 조정하면 TypedArray가 length-tracking 방식으로 생성되었는지에 따라 TypedArray의 크기에 미치는 영향이 달라진다.
세 번째 파라미터를 생략하거나 undefined를 넘겨 특정 크기 없이 typed array를 만들면 length-tracking이 되어, 기본 buffer가 조정됨에 따라 자동으로 크기를 조정한다:
const buffer = new ArrayBuffer(8, { maxByteLength: 16 });
const float32 = new Float32Array(buffer);
console.log(float32.byteLength); // 8
console.log(float32.length); // 2
buffer.resize(12);
console.log(float32.byteLength); // 12
console.log(float32.length); // 3
세 번째 length 파라미터로 특정 크기를 지정해 typed array를 만들면 buffer가 커져도 이를 포함하도록 크기가 조정되지 않는다:
const buffer = new ArrayBuffer(8, { maxByteLength: 16 });
const float32 = new Float32Array(buffer, 0, 2);
console.log(float32.byteLength); // 8
console.log(float32.length); // 2
console.log(float32[0]); // 0, the initial value
buffer.resize(12);
console.log(float32.byteLength); // 8
console.log(float32.length); // 2
console.log(float32[0]); // 0, the initial value
buffer가 줄어들면 이를 보는 typed array가 범위를 벗어날(out of bounds) 수 있으며, 이 경우 typed array의 관찰 크기가 0으로 줄어든다. 이는 non-length-tracking typed array의 길이가 변경될 수 있는 유일한 경우이다.
const buffer = new ArrayBuffer(8, { maxByteLength: 16 });
const float32 = new Float32Array(buffer, 0, 2);
buffer.resize(7);
console.log(float32.byteLength); // 0
console.log(float32.length); // 0
console.log(float32[0]); // undefined
그런 다음 buffer를 다시 키워 typed array를 다시 범위 안으로 되돌리면, typed array의 크기는 원래 값으로 복원된다.
buffer.resize(8);
console.log(float32.byteLength); // 8
console.log(float32.length); // 2
console.log(float32[0]); // 0 - back in bounds again!
length-tracking typed array에서도 버퍼가 byteOffset보다 작게 줄어들면 같은 일이 발생할 수 있다.
const buffer = new ArrayBuffer(8, { maxByteLength: 16 });
const float32 = new Float32Array(buffer, 4);
// float32 is length-tracking, but it only extends from the 4th byte
// to the end of the buffer, so if the buffer is resized to be shorter
// than 4 bytes, the typed array will become out of bounds
buffer.resize(3);
console.log(float32.byteLength); // 0
생성자(Constructor)
이 객체는 직접 인스턴스화할 수 없다. new로 생성하려 시도하면 TypeError가 발생한다.
new (Object.getPrototypeOf(Int8Array))();
// TypeError: Abstract class TypedArray not directly constructable
대신 Int8Array나 BigInt64Array 같은 특정 타입의 typed array 인스턴스를 만든다. 이 객체들은 생성자에 대해 모두 공통된 문법을 가진다:
new TypedArray()
new TypedArray(length)
new TypedArray(typedArray)
new TypedArray(object)
new TypedArray(buffer)
new TypedArray(buffer, byteOffset)
new TypedArray(buffer, byteOffset, length)
여기서 TypedArray는 구체 타입 중 하나의 생성자이다.
참고: 모든 TypedArray 하위 클래스 생성자는 new로만 생성할 수 있다. new 없이 호출하려 하면 TypeError가 발생한다.
파라미터
typedArray—TypedArray하위 클래스의 인스턴스와 함께 호출하면typedArray가 새 typed array로 복사된다. non-bigintTypedArray생성자의 경우typedArray파라미터는 오직 non-bigint 타입(예:Int32Array) 중 하나만 될 수 있다. 마찬가지로 bigintTypedArray생성자(BigInt64Array,BigUint64Array)의 경우typedArray파라미터는 bigint 타입 중 하나만 될 수 있다.typedArray의 각 값은 새 배열로 복사되기 전에 생성자의 해당 타입으로 변환된다. 새 typed array의 길이는typedArray인자의 길이와 동일하다.object—TypedArray인스턴스가 아닌 객체와 함께 호출하면TypedArray.from()메서드와 같은 방식으로 새 typed array가 생성된다.length(선택) — 객체가 아닌 값과 함께 호출하면 파라미터는 typed array의 길이를 지정하는 숫자로 취급된다. 크기가length에BYTES_PER_ELEMENT바이트를 곱한 값인 내부 array buffer가 메모리에 생성되어 0으로 채워진다. 모든 파라미터를 생략하는 것은length로0을 사용하는 것과 동일하다.buffer,byteOffset(선택),length(선택) —ArrayBuffer또는SharedArrayBuffer인스턴스와 함께, 선택적으로byteOffset과length인자와 함께 호출하면 지정된 버퍼를 보는 새 typed array 뷰가 생성된다.byteOffset(바이트)과length(각각BYTES_PER_ELEMENT바이트를 차지하는 요소 수) 파라미터는 typed array 뷰가 노출할 메모리 범위를 지정한다. 둘 다 생략하면buffer전체가 보이고,length만 생략하면byteOffset부터 시작하는buffer의 나머지가 보인다.length가 생략되면 typed array는 length-tracking이 된다.
예외(Exceptions)
모든 TypedArray 하위 클래스 생성자는 같은 방식으로 동작하며 다음과 같은 예외를 발생시킨다:
TypeError— 다음 중 한 경우에 발생: bigint 타입인typedArray가 전달됐는데 현재 생성자가 bigint가 아니거나 그 반대인 경우. 또typedArray가 전달됐는데 그것이 보는 버퍼가 분리(detach)되었거나, 분리된buffer가 직접 전달된 경우.RangeError— 다음 중 한 경우에 발생: 새 typed array의 길이가 너무 큰 경우.buffer의 길이(length파라미터가 지정되지 않은 경우) 또는byteOffset이 새 typed array의 요소 크기의 정수 배수가 아닌 경우.byteOffset이 유효한 배열 인덱스(0과 2^53 - 1 사이의 정수)가 아닌 경우. 버퍼로부터 뷰를 만들 때 경계가 버퍼 밖을 넘는 경우, 즉byteOffset + length * TypedArray.BYTES_PER_ELEMENT > buffer.byteLength인 경우.
정적 속성(Static properties)
TypedArray 생성자 객체에 정의되어 모든 TypedArray 하위 클래스 생성자가 공유한다.
TypedArray[Symbol.species]— 파생 객체를 만드는 데 사용되는 생성자 함수.TypedArray.BYTES_PER_ELEMENT— 서로 다른TypedArray객체들의 요소 크기 값을 숫자로 반환한다.
정적 메서드(Static methods)
TypedArray.from()— array-like 또는 iterable 객체로부터 새TypedArray를 만든다.Array.from()도 참고.TypedArray.of()— 가변 인자 수로 새TypedArray를 만든다.Array.of()도 참고.
인스턴스 속성(Instance properties)
TypedArray.prototype에 정의되어 모든 TypedArray 하위 클래스 인스턴스가 공유한다.
TypedArray.prototype.buffer— typed array가 참조하는ArrayBuffer를 반환한다.TypedArray.prototype.byteLength— typed array의 길이(바이트)를 반환한다.TypedArray.prototype.byteOffset—ArrayBuffer의 시작부터 typed array까지의 오프셋(바이트)을 반환한다.TypedArray.prototype.constructor— 인스턴스 객체를 생성한 생성자 함수.TypedArray.prototype.constructor는 숨겨진TypedArray생성자 함수지만, 각 typed array 하위 클래스는 자신만의constructor속성도 정의한다.TypedArray.prototype.length— typed array에 담긴 요소 수를 반환한다.TypedArray.prototype[Symbol.toStringTag]— 초기 값은 typed array 생성자 이름과 같은 문자열을 반환하는 getter이다.this값이 typed array 하위 클래스 중 하나가 아니면undefined를 반환한다.Object.prototype.toString()에서 사용된다. 다만TypedArray는 자체toString()메서드도 있으므로, typed array를thisArg로 해서Object.prototype.toString.call()을 호출하지 않는 한 이 속성은 사용되지 않는다.TypedArray.prototype.BYTES_PER_ELEMENT— 서로 다른TypedArray객체들의 요소 크기 값을 숫자로 반환한다.
인스턴스 메서드(Instance methods)
TypedArray 프로토타입 객체에 정의되어 모든 하위 클래스 인스턴스가 공유한다. 대부분 Array.prototype의 메서드와 대응한다.
TypedArray.prototype.at()— 정수 값을 받아 해당 인덱스의 항목을 반환한다. 음의 정수를 허용하며, 마지막 항목부터 거꾸로 센다.TypedArray.prototype.copyWithin()— 배열 내의 요소 시퀀스를 복사한다.Array.prototype.copyWithin()참고.TypedArray.prototype.entries()— 배열의 각 인덱스에 대한 키/값 쌍을 담은 새 배열 반복자 객체를 반환한다.TypedArray.prototype.every()— 제공된 테스트 함수를 만족하지 않는 요소를 찾으면false, 그렇지 않으면true를 반환한다.TypedArray.prototype.fill()— 시작 인덱스부터 끝 인덱스까지 배열의 모든 요소를 정적 값으로 채운다.TypedArray.prototype.filter()— 제공된 필터링 함수가true를 반환하는 이 배열의 모든 요소로 새 배열을 만든다.TypedArray.prototype.find()— 제공된 테스트 함수를 만족하는 배열의 첫 번째element를 반환하거나, 적절한 요소가 없으면undefined를 반환한다.TypedArray.prototype.findIndex()— 테스트 함수를 만족하는 요소가 있는 배열의 첫 번째 인덱스를 반환하거나, 없으면-1을 반환한다.TypedArray.prototype.findLast()— 테스트 함수를 만족하는 배열의 마지막 요소 값을 반환하거나, 없으면undefined를 반환한다.TypedArray.prototype.findLastIndex()— 테스트 함수를 만족하는 배열의 마지막 요소 인덱스를 반환하거나, 없으면-1을 반환한다.TypedArray.prototype.forEach()— 배열의 각 요소에 대해 함수를 호출한다.TypedArray.prototype.includes()— typed array가 특정 요소를 포함하는지 판별해 그에 따라true/false를 반환한다.TypedArray.prototype.indexOf()— 지정된 값과 같은 배열 내 요소의 첫 번째(가장 작은) 인덱스를 반환하거나, 없으면-1을 반환한다.TypedArray.prototype.join()— 이 typed array의 모든 요소를 쉼표나 지정된 구분 문자열로 연결한 새 문자열을 반환한다.TypedArray.prototype.keys()— 배열의 각 인덱스에 대한 키를 담은 새 배열 반복자를 반환한다.TypedArray.prototype.lastIndexOf()— 지정된 값과 같은 배열 내 요소의 마지막(가장 큰) 인덱스를 반환하거나, 없으면-1을 반환한다.TypedArray.prototype.map()— 이 배열의 모든 요소에 제공된 함수를 호출한 결과로 새 배열을 만든다.TypedArray.prototype.reduce()— 배열의 각 값에 대해 accumulator에 함수를 적용해(왼쪽부터 오른쪽) 단일 값으로 줄인다.TypedArray.prototype.reduceRight()— 배열의 각 값에 대해 accumulator에 함수를 적용해(오른쪽부터 왼쪽) 단일 값으로 줄인다.TypedArray.prototype.reverse()— 배열 요소의 순서를 뒤집는다.Array.prototype.reverse()참고.TypedArray.prototype.set()— 지정된 배열에서 입력 값을 읽어 typed array에 여러 값을 저장한다.TypedArray.prototype.slice()— 배열의 한 구간을 추출해 새 배열을 반환한다.TypedArray.prototype.some()— 제공된 테스트 함수를 만족하는 배열 요소를 찾으면true, 그렇지 않으면false를 반환한다.TypedArray.prototype.sort()— 배열 요소를 제자리에서 정렬하고 배열을 반환한다.TypedArray.prototype.subarray()— 주어진 시작·끝 요소 인덱스에서 새TypedArray를 반환한다.TypedArray.prototype.toLocaleString()— 배열과 그 요소를 나타내는 지역화된 문자열을 반환한다.TypedArray.prototype.toReversed()— 원본을 수정하지 않고 요소가 역순인 새 배열을 반환한다.TypedArray.prototype.toSorted()— 원본을 수정하지 않고 요소가 오름차순으로 정렬된 새 배열을 반환한다.TypedArray.prototype.toString()— 배열과 그 요소를 나타내는 문자열을 반환한다.TypedArray.prototype.values()— 배열의 각 인덱스에 대한 값을 담은 새 배열 반복자 객체를 반환한다.TypedArray.prototype.with()— 원본을 수정하지 않고 주어진 인덱스의 요소를 주어진 값으로 교체한 새 배열을 반환한다.TypedArray.prototype[Symbol.iterator]()— 배열의 각 인덱스에 대한 값을 담은 새 배열 반복자 객체를 반환한다.
예제
속성 접근(Property access)
표준 배열 인덱스 문법(대괄호 표기법)으로 배열의 요소를 참조할 수 있다. 다만 typed array에서 인덱스 속성을 가져오거나 설정할 때, 인덱스가 범위를 벗어나더라도 프로토타입 체인에서 이 속성을 검색하지 않는다. 인덱스 속성은 ArrayBuffer를 조회하며 객체 속성을 절대 보지 않는다. 다른 모든 객체처럼 명명된 속성은 계속 사용할 수 있다.
// Setting and getting using standard array syntax
const int16 = new Int16Array(2);
int16[0] = 42;
console.log(int16[0]); // 42
// Indexed properties on prototypes are not consulted (Fx 25)
Int8Array.prototype[20] = "foo";
new Int8Array(32)[20]; // 0
// even when out of bound
Int8Array.prototype[20] = "foo";
new Int8Array(8)[20]; // undefined
// or with negative integers
Int8Array.prototype[-1] = "foo";
new Int8Array(8)[-1]; // undefined
// Named properties are allowed, though (Fx 30)
Int8Array.prototype.foo = "bar";
new Int8Array(32).foo; // "bar"
동결(freeze)할 수 없음
비어 있지 않은 TypedArray는 동결할 수 없다. 그 기본 ArrayBuffer가 버퍼의 다른 TypedArray 뷰를 통해 변경될 수 있기 때문이다. 이는 객체가 진정으로 동결된 적이 없음을 의미하게 된다.
const i8 = Int8Array.of(1, 2, 3);
Object.freeze(i8);
// TypeError: Cannot freeze array buffer views with elements
byteOffset이 정렬되어야 함
TypedArray를 ArrayBuffer의 뷰로 생성할 때 byteOffset 인자는 요소 크기에 정렬되어야 한다. 즉 오프셋은 BYTES_PER_ELEMENT의 배수여야 한다.
const i32 = new Int32Array(new ArrayBuffer(4), 1);
// RangeError: start offset of Int32Array should be a multiple of 4
const i32 = new Int32Array(new ArrayBuffer(4), 0);
byteLength가 정렬되어야 함
byteOffset 파라미터와 마찬가지로 TypedArray 생성자에 전달된 ArrayBuffer의 byteLength 속성도 생성자의 BYTES_PER_ELEMENT의 배수여야 한다.
const i32 = new Int32Array(new ArrayBuffer(3));
// RangeError: byte length of Int32Array should be a multiple of 4
const i32 = new Int32Array(new ArrayBuffer(4));
명세(Specifications)
- ECMAScript® 2027 Language Specification — sec-typedarray-objects
브라우저 호환성
baseline 기준 2015년 7월부터 널리 사용 가능하다. 호환성 표는 JavaScript를 활성화해야 볼 수 있다.
참고 자료
- typed array의
core-js폴리필 - JavaScript typed arrays 가이드
ArrayBufferDataViewTextDecoder