자바스크립트는 바이너리 데이터를 다루기 위한 방법으로
버퍼(Buffer)를 구현한 ArrayBuffer, ArrayBufferView를 제공한다.
이번 글에서 다룰 내용을 요약하면 아래와 같다.
1. 버퍼(Buffer)란?
- 임시로 바이너리 데이터를 저장하기 위한 메모리 공간 혹은 바이너리 데이터 자체
2. ArrayBuffer란?
- 자바스크립트에서 구현된 버퍼이다.
- 고정된 크기의 메모리 공간에 바이너리 데이터를 저장하는 객체다.
3. ArrayBufferView란?
- ArrayBuffer에 저장된 바이너리 데이터에 접근하는 객체다.
- TypedArray, DataView 2개가 제공된다.
4. TypedArray란?
- ArrayBufferView의 한 종류이다.
- 배열 요소의 타입/크기를 개발자가 지정하여 생성할 수 있다.
- Uint8Array, Uint16Array, Float32Array 등이 있다.
5. ArrayBuffer와 TypedArray의 관계
- TypedArray는 ArrayBuffer에 저장된 바이너리 데이터를 이용해 만드는 배열이다.
지금부터 하나씩 자세하게 살펴보자.
# 버퍼(Buffer)란?
버퍼란 스트림 데이터를 조금씩 저장하고, 처리하고, 비우기를 반복하는 메모리 공간이다.
이런 행위를 버퍼링이라고 하며, 메모리 공간 자체 혹은 메모리에 저장된 데이터를 버퍼라고 부른다.
▼버퍼와 스트림에 대한 자세한 내용은 이전에 정리해둔 글을 참고하자▼
스트리밍(Streaming)이란: 스트림(Stream), 버퍼(Buffer) 원리
즉, Buffer란 임시 메모리 공간에 저장하는 Binary 형태의 데이터 객체이다.
* Binary Data란 컴퓨터가 이해할 수 있는 0100 1001 형태의 이진수로 이루어진 데이터를 말한다.
* Binary Data는 해석 방식에 따라 문자, 숫자, 배열, 이미지, 오디오 등으로 변환되어 사용된다.
그렇다면 자바스크립트에서는 Buffer를 어떻게 사용할까?
# 자바스크립트의 Buffer란?
자바스크립트의 버퍼란 "특정 크기의 메모리 공간"에 "바이너리 데이터"를 저장해두는 객체이다.
데이터가 저장될 메모리의 크기는 개발자가 바이트(byte) 단위로 직접 지정할 수 있다.
그렇다면 기존에는 데이터를 저장할 메모리의 크기를 개발자가 지정하지 못했단 말인가?
그렇다.
자바스크립트는 메모리 영역에 개발자가 직접 접근하는 것을 허용하지 않았다.
let num = 10;
const arr = [0, 1, 2];
const obj = {id: "카레유"};
위의 코드는 각각 원시타입, 배열, 객체를 저장한다.
하지만 개발자는 데이터가 저장될 메모리의 크기에 관여할 수 없다.
자바스크립트 엔진이 알아서 메모리 공간을 확보하고 해제하는 등의 관리를 수행하기 때문이다.
그런데 갑자기 왜 버퍼라는 것을 만들어
데이터가 저장될 메모리의 크기를 개발자가 직접 지정할 수 있도록 해준 것일까?
레퍼런스에 따르면,
자바스크립트의 용도가 다양해지면서
오디오, 비디오 및 웹소켓 통신에서 사용하는 Raw Bianry Data를 직접 다룰 필요가 생겼기 때문이라고 한다.
예를 들어,
숫자 1을 내부적으로 [0000 0000 0000 0001] 처럼 여유공간을 두고 저장하는 방식을
엄청난 크기의 비디오 파일에도 적용한다면 엄청난 손실이 있지 않았을까?
그래서 다른건 몰라도 데이터를 저장할 메모리 공간의 크기 정도는 지정할 수 있어야
메모리 효율성을 높이고 성능저하를 막을 수 있지 않았을까 싶다.
자바스크립트는 사용 용도에 따라 아래의 3가지 Buffer를 제공한다.
1. ArrayBuffer: 배열 용도
2. AudioBuffer: 오디오 용도
3. SourceBuffer: 미디어 용도
이 글에서는 가장 기본이 되는 ArrayBuffer를 정리해 본다.
(추후에 Web Audio API 등을 다루면서 다른 Buffer도 다뤄볼 예정이다)
# ArrayBuffer란?
ArrayBuffer란 개발자가 지정한 메모리 크기만큼의 바이너리 데이터를 저장하는 객체다.
단, ArrayBuffer는 메모리를 확보해 바이너리 데이터를 0으로 저장해두는 역할만 하며,
실제 데이터 접근은 별도로 제공되는 ArrayBufferView를 통해서만 가능하다.
1. ArrayBuffer: 메모리 확보 및 데이터 생성(디폴트 0000...)
2. ArrayBufferView: 데이터 접근(읽기, 수정)
ㄴ TypedArray: Uint8Array, Uint16Array, Float32Array, ...
ㄴ DataView
데이터의 저장과 접근을 수행하는 객체를 분리한 이유는
Efficiency와 Flexibility를 위해서라고 하는데 이어지는 글에서 살펴보도록 하겠다.
* ArrayBufferView 역할을 수행하는 객체는 2종류(TypedArray, DataView)가 있는데 이 글에서는 TypedArray를 다룬다.
* ArrayBufferView는 객체가 아니라, TypedArray와 DataView를 통칭하는 말이다.
* TypedArray도 객체가 아니라, UInt8Array, Float32Array 등을 통칭하는 말이다.
# ArrayBuffer 생성 방법
- 생성자: new ArrayBuffer(byte)
- 인자: 바이너리 데이터를 저장할 메모리의 크기(byte)
- 반환: 바이너리 데이터(디폴트 0)가 저장된 ArrayBuffer 객체
// ArrayBuffer 생성: 4byte(4*8bit)의 이진데이터 저장
const arrayBuffer = new ArrayBuffer(4);
// 결과: [00000000 00000000 00000000 00000000]
# ArrayBuffer의 데이터 구조
생성된 ArrayBuffer의 데이터 구조를 자세히 살펴보자.
ArrayBuffer는 바이트 단위로 생성된 메모리에 0, 1의 이진데이터가 저장되는 객체다.
현재 ArrayBuffer는 4byte(=32bit)의 메모리 상에 디폴트 값인 0이 저장된 상태다.
[00000000 00000000 00000000 00000000]
4byte는 32bit이므로 4,294,967,296(232)개의 값을 표현할 수 있다.
따라서 양의 정수(Unsinged Int)를 저장한다면 0~4,294,967,2965 사이의 숫자 하나를 저장할 수 있다.
[0~4294967295]
그런데 4byte를 하나로 인식하지 않고,
1byte(8bit)씩 4개로 나누어 인식한다면, 0~255 사이의 숫자 4개를 저장할 수도 있다.
[0~255, 0~255, 0~255, 0~255]
즉 new ArrayBuffer(4)로 생성한 메모리 공간은 4byte(32bit)의 바이너리 데이터로 동일하지만
[00000000 00000000 00000000 00000000]
한번에 4byte를 인식하면 1개의 큰 숫자를 표현할 수 있고,
[00000000000000000000000000000000]
== [0~4294967296]
1byte씩 4개로 나누어 인식하면 4개의 작은 숫자를 표현할 수 있다.
[00000000, 00000000, 00000000, 00000000]
== [0~255, 0~255, 0~255, 0~255]
즉, ArrayBuffer는 4byte(32bit)의 메모리 공간만 제공할 뿐이고,
이 메모리 공간을 한번에 다 사용할지, 몇 개씩 나누어 사용할지는 개발자의 선택인 것이다.
그리고 그 방법을 제공하는 것이 바로 TypedArray이다.
TypedArray를 이용하면 ArrayBuffer가 확보한 메모리 공간을
개발자가 원하는대로 구분하고, 원하는 타입의 데이터를 저장해 사용할 수 있다.
이게 바로 Efficiency와 Flexibility를 위해
ArrayBuffer와 ArrayBufferView를 나누어 제공하는 이유 중 하나가 아닐까 한다.
그럼 이제 ArrayBufferView 중 하나인 TypedArray에 대해 살펴보자.
# TypedArray란?
TypedArray란 ArrayBufferView의 하나로 ArrayBuffer에 저장된 바이너리 데이터를 이용해 생성하는 배열이다.
개발자가 배열 요소의 타입과 크기를 직접 지정할 수 있다.
따라서 TypedArray를 이용하면 ArrayBuffer에 저장된 바이너리 데이터를 원하는 형태로 인식하여 사용할 수 있다.
1. 요소의 타입(type)
- Int, UnsingedInt, BigInt, Float
2. 요소의 크기(bit)
- 8bit(=1byte), 16bit, 32bit, 64bit
*TypedArray 자체는 객체가 아니라 아래의 종류들을 통칭하는 말이다.
Type | Value Range | Size in bytes | Description |
Int8Array | -128 to 127 | 1 | 8-bit two's complement signed integer |
Uint8Array | 0 to 255 | 1 | 8-bit unsigned integer |
Uint8ClampedArray | 0 to 255 | 1 | 8-bit unsigned integer (clamped) |
Int16Array | -32768 to 32767 | 2 | 16-bit two's complement signed integer |
Uint16Array | 0 to 65535 | 2 | 16-bit unsigned integer |
Int32Array | -2147483648 to 2147483647 | 4 | 32-bit two's complement signed integer |
Uint32Array | 0 to 4294967295 | 4 | 32-bit unsigned integer |
Float32Array | -3.4E38 to 3.4E38 and 1.2E-38 is the min positive number | 4 | 32-bit IEEE floating point number (7 significant digits e.g., 1.123456) |
Float64Array | -1.8E308 to 1.8E308 and 5E-324 is the min positive number | 8 | 64-bit IEEE floating point number (16 significant digits e.g., 1.123...15) |
BigInt64Array | -2^63 to 2^63 - 1 | 8 | 64-bit two's complement signed integer |
BigUint64Array | 0 to 2^64 - 1 | 8 | 64-bit unsigned integer |
출처: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays
# TypedArray의 크기
TypedArray는 메모리 크기(ArrayBuffer)와 각 요소의 크기가 지정되어 있다.
이에 따라 배열의 length가 고정되어 변경이 불가하다.
TypedArray 요소 갯수(length) = 메모리 크기 / 각 요소의 크기
요소의 갯수가 고정되어 있으므로 push(), pop() 등의 요소 추가/제거 메서드는 사용할 수 없으며,
기존에 저장된 데이터의 값만 변경할 수 있다.
예를 들어,
ArrayBuffer(4byte)를 이용해 Uint8Array를 생성한다면,
1byte(8bit)의 Unsigned Int 요소 4개를 갖는 배열이 생성되며 요소의 추가, 삭제는 불가하다.
# TypedArray 생성자
자주 사용하는 TypedArray의 생성자 3가지만 살펴보자.
1. new TypedArray(ArrayBuffer)
- 인자로 들어온 ArrayBuffer를 사용해 TypedArray 생성
// ArrayBuffer객체 생성: 4bytes의 메모리 확보하고, 바이너리 데이터를 0으로 채움
const arrayBuffer = new ArrayBuffer(4);
// TypedArray 객체 생성: ArrayBuffer에 저장된 바이너리 데이터 접근 가능
const arr = new Uint8Array(arrayBuffer);
// Uint8Array는 ArrayBuffer의 바이너리 데이터를 8bit(1byte)씩 Unsinged Int로 인식
console.log(arr); // [0, 0, 0, 0]
* ArrayBuffer에 저장된 데이터(4byte)에 접근하는 Uint8Array를 생성한다.
* Uint8Array 배열의 각 요소는 1byte(8bit)이므로 총 4개의 요소가 만들어진다.
2. new TypedArray(length)
- 인자로 들어온 length개의 요소를 갖는 TypedArray 생성 (ArrayBuffer도 함께 생성된다.)
// Uint8Array 생성: length == 4
const arrByLength = new Uint8Array(4);
console.log(arrByLength); // [0, 0, 0, 0]
* 요소가 4개인 Uint8Array를 생성한다.
* 내부적으로 4Byte(4개 요소 * 1byte)의 ArrayBuffer를 생성해 접근한다.
3. new TypedArray(TypedArray | Array | 유사배열객체)
- 인자로 들어온 배열과 동일한 요소의 TypedArray생성 (ArrayBuffer도 함께 생성된다.)
// Uint8Array 생성: [0, 1, 2, 3]와 동일한 배열을 생성
const arrByArray = new Uint8Array([0, 1, 2, 3]);
console.log(arrByArray); // [0, 1, 2, 3]
* 0, 1, 2, 3 을 요소 값으로 갖는 Uint8Array를 생성한다.
* 내부적으로 4Byte(4개 요소 * 1byte)의 ArrayBuffer를 생성해 접근한다.
# TypedArray와 ArrayBuffer의 관계 1
TypedArray는 ArrayBuffer의 이진 데이터를 읽고 쓰는 뷰 객체이다.
위에서 3번째 생성자로 생성한 arrByArray는 아래와 같다.
[0, 1, 2, 3]
하지만 실제 메모리에 저장된 바이너리 데이터인 ArrayBuffer는 아래와 같을 것이다.
[00000000 00000001 000000010 00000011]
(물론 실제 저장/배치 방식은 JS엔진마다 다르겠지만.)
이에 대해 자세히 살펴보자.
# TypedArray와 ArrayBuffer의 관계 2
먼저 4byte 크기의 ArrayBuffer를 생성해보자.
const arrayBuffer = new ArrayBuffer(4);
위의 코드는 4byte(32bit)의 바이너리 데이터가 저장된 ArrayBuffer객체를 만든다.
[00000000 00000000 00000000 00000000]
이 바이너리 데이터를 이용해 UInt8Arrray 배열을 생성해보자.
const arrayBuffer = new ArrayBuffer(4);
const typedArray = new Uint8Array(arrayBuffer);
Uint8Array는 ArrayBuffer의 바이너리 데이터를 1byte(8bit)씩 쪼개어 인식한다.
[0000 0000, 0000 0000, 0000 0000, 0000 0000]
각 요소들은 8bit 짜리 Unsinged Int이므로 0~255(2^8개) 사이의 값을 갖게 된다.
[0~255, 0~255, 0~255, 0~255]
즉, 4byte짜리 ArrayBuffer를 이용해 UInt8Arrray 배열을 만들면,
0~255 사이의 값을 갖는 요소 4개로 구성된 배열이 생성된다.
const arrayBuffer = new ArrayBuffer(4);
const typedArray = new Uint8Array(arrayBuffer);
console.log(typedArray); // [0, 0, 0, 0]
즉, 4byte짜리 바이너리 데이터를 1byte씩 쪼개어 양의 정수로 4개로 읽어들인 것이다.
이것이 TypedArray를 통해 ArrayBuffer에 접근하는 방식이다.
# TypedArray와 ArrayBuffer의 관계 3
TypedArray와 ArrayBuffer의 관계를 좀더 자세히 정리해보자.
먼저 4bytes를 사용하는 ArrayBuffer를 이용해 Uint8Array 배열을 생성해 보자.
// ArrayBuffer 생성: 4Byte의 바이너리 데이터
const arrayBuffer = new ArrayBuffer(4);
// [00000000 00000000 00000000 00000000]
// Uint8Array 생성
const uint8Array = new Uint8Array(arrayBuffer);
// [0, 0, 0, 0]
복습 차원에서 다시 한번 위 코드를 하나씩 살펴보자.
- Uint8Array는 8bit(1byte) 크기의 unsigned Int 값을 요소로 갖는 TypedArray 객체를 생성한다.
- 따라서 4byte짜리 ArrayBuffer로는 1byte짜리 요소를 4개를 가진 배열을 만들 수 있다.
- 8bit는 이진수 8개(0000 0000)로 표현할 수 있는 0~255(2의 8승) 사이의 숫자를 담을 수 있다.
- 즉, [0~255, 0~255, 0~255, 0~255] 와 같은 형태의 배열이 된다.
- 따라서 Uint8Array는 ArrayBuffer를 8bit 크기의 chunk로 쪼개어 배열로 인식하는 객체로 볼 수 있다.
이러한 원리로 4byte짜리 ArrayBuffer를 이용하면, 아래와 같은 여러 종류의 TypedArray를 만들 수 있다.
// 4Byte의 ArrayBuffer(바이너리 데이터) 생성
const arrayBuffer = new ArrayBuffer(4);
// [00000000 00000000 00000000 00000000]
// Uint8Array: 1Byte(8bit)짜리 unsigned Int 요소 4개를 갖는 배열
const uint8Array = new Uint8Array(arrayBuffer);
// [0~255, 0~255, 0~255, 0~255]
// Uint16Array: 2Byte(16bit)짜리 unsigned Int 요소 2개를 갖는 배열
const uint16Array = new Uint16Array(arrayBuffer);
// [0~65536, 0~65536]
// UInt32Array: 4Byte(32bit)짜리 unsigned Int 요소 1개를 갖는 배열
const uint32Array = new Uint32Array(arrayBuffer);
// [0~4294967296]
위의 코드는 여러 종류의 TypedArray 배열을 생성하지만,
내부의 메모리에 저장된 바이너리 데이터는 모두 4Bytes짜리 ArrayBuffer로 동일하다.
이번엔 16bytes 크기의 ArrayBuffer를 이용해 TypedArray를 만들어보자.
// 16Byte의 ArrayBuffer(바이너리 데이터) 생성
const arrayBuffer = new ArrayBuffer(16);
// 1byte(8bit)의 unsinged int 배열 생성
const uint8Array = new Uint8Array(arrayBuffer);
// 2byte(16bit)의 unsinged int 배열 생성
const uint16Array = new Uint16Array(arrayBuffer);
// 4byte(32bit)의 unsinged int 배열 생성
const uint32Array = new Uint32Array(arrayBuffer);
// 8byte(64bit)의 float 배열 생성
const float64Array = new Float64Array(arrayBuffer);
위의 코드 또한 여러 종류의 TypedArray 배열을 생성하지만,
내부의 메모리에 저장된 바이너리 데이터는 모두 16Bytes짜리 ArrayBuffer로 동일하다.
즉, 아래의 그림과 같은 상태가 된다.
# TypedArray와 ArrayBuffer의 관계 4
TypedArray를 이용하면 ArrayBuffer에 저장된 이진데이터를 일반 배열처럼 접근하여 사용할 수 있다.
// ArrayBuffer(4byte) 객체 생성
const arrayBuffer = new ArrayBuffer(4);
// typedArray 생성
const uint8array = new Uint8Array(arrayBuffer);
// 요소 값 변경
uint8array[0] = 10;
uint8array[1] = 20;
// 요소 값 확인
console.log(uint8array[0]); // 10
console.log(uint8array[0]); // 20
잊지 말아야할 점은 TypedArray는 내부적으로 메모리에 저장된 ArrayBuffer의 이진 데이터를 사용한다는 것이다.
그렇다면 만약 "동일한 ArrayBuffer"에 접근하는 "다른 타입의 TypedArray"를 여러 개 생성하면 어떻게 될까?
const arrayBuffer = new ArrayBuffer(4);
const uint8array = new Uint8Array(arrayBuffer);
const uint16array = new Uint16Array(arrayBuffer);
위의 두 배열을 만드는데 사용된 원본 바이너리 데이터는 arrayBuffer 하나로 동일하다.
[00000000 00000000 00000000 00000000]
uint8array와 uint16array의 차이는
동일한 데이터를 8bit(1byte)씩 쪼개어 인식할지, 16bit(2byte)씩 쪼개어 인식할지의 차이 밖에 없다.
uint8array == [0, 0, 0, 0] == [00000000, 00000000, 00000000, 00000000]
uint16array == [0, 0] == [0000000000000000, 0000000000000000]
즉, uint8array와 uint16array는 "동일한 메모리 공간"에 저장된 "동일한 바이너리 데이터"를 다른 chunk 단위로 쪼개어 접근할 뿐이다.
그럼 이제 응용이 가능하다.
Uint8Array는 0~255까지의 숫자를 요소로 갖는다.
Uint16Array는 0~65535까지의 숫자를 요소로 갖는다.
그럼 만약 0~255의 범위를 갖는 Uint8Array에 255가 넘는 숫자를 할당하면 어떻게 될까?
1. Uint8Array[0]에 값을 할당하는 경우
uint8array[0] = 254;
console.log(uint8array[0]); // [254, 0, 0, 0];
console.log(uint16array[0]); // [254, 0];
uint8array[0] = 255;
console.log(uint8array[0]); // [255, 0, 0, 0];
console.log(uint16array[0]); // [255, 0];
uint8array[0] = 256;
console.log(uint8array[0]); // [0, 0, 0, 0];
console.log(uint16array[0]); // [0, 0];
uint8array[0] = 257;
console.log(uint8array[0]); // [1, 0, 0, 0];
console.log(uint16array[0]); // [1, 0];
uint8array[0] = 258;
console.log(uint8array[0]); // [2, 0, 0, 0];
console.log(uint16array[0]); // [2, 0];
Uint8Array의 범위인 0~255를 넘어가면 한바퀴 돌아서 0부터 다시 시작한다.
따라서 256을 할당하면 0, 257을 할당하면 1, 258을 할당하면 2가 된다.
그런데 만약 0~65535의 범위를 갖는 Uint16Array에 255가 넘는 값을 할당하면...
Uint8Array에서는 어떤 일이 벌어질까?
2. Uint16Array[0]에 값을 할당하는 경우
uint16array[0] = 254;
console.log(uint8array[0]); // [254, 0, 0, 0];
console.log(uint16array[0]); // [254, 0];
uint16array[0] = 255;
console.log(uint8array[0]); // [255, 0, 0, 0];
console.log(uint16array[0]); // [255, 0];
uint16array[0] = 256;
console.log(uint8array[0]); // [0, 1, 0, 0];
console.log(uint16array[0]); // [256, 0];
uint16array[0] = 257;
console.log(uint8array[0]); // [1, 1, 0, 0];
console.log(uint16array[0]); // [257, 0];
uint16array[0] = 258;
console.log(uint8array[0]); // [2, 1, 0, 0];
console.log(uint16array[0]); // [258, 0];
Uint16Array은 0~65535 범위에 들어 오므로 255가 넘는 값도 정상적으로 할당된다.
그러나 Uint8Array은 0~255 범위를 초과하기 때문에 255만큼은 다음 인덱스[1]로 넘겨 1로 처리하고, 남은 수는 인덱스[0]에 표현한다.
하지만 arrayBuffer에 담긴 전체 이진데이터는 uint8array로 접근하든, uint16array로 접근하든 동일하다.
1byte 단위로 쪼개 보느냐, 2byte단위로 쪼개 보느냐의 차이만이 있을 뿐이다.
단, 세부적인 데이터 배치 방식은 자바스크립트 엔진마다 다를 수 있으므로 위와는 다른 결과가 나올 수도 있음을 참고하자.
중요한 것은 TypedArray는 ArrayBuffer에 저장된 바이너리 데이터에 접근하는 배열이라는 점이다.
# 메모리 확인 방법
위에서 다룬 내용을 크롬 브라우저의 개발자도구에서 확인해볼 수 있다.
TypedArray의 buffer항목에서 메모리 모양의 아이콘을 누르면,
Memory Inspector를 통해 바이너리 데이터가 저장된 구조를 확인할 수 있다.
# 최종 정리
1. Buffer: Raw Binay Data를 메모리 상에 임시로 저장하는 객체
2. ArrayBuffer: 자바스크립트에서 구현된 버퍼 객체로, 개발자가 정한 고정 크기의 메모리를 사용해 바이너리 데이터를 저장하는 객체
3. TypedArray: ArrayBuffer의 바이너리 데이터에 접근해 배열처럼 사용할 수 있게 해주는 ArrayBufferView 객체
# 자바스크립트의 Buffer 관련 레퍼런스 정리
- 자바스크립트의 Buffer: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays
- ArrayBuffer: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer
- ArrayBufferView: https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView
- TypedArray: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
- DataView: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView
이것으로 ArrayBuffer와 TypedArray에 대한 정리를 마친다.
'개발(Development) > JS(자바스크립트)' 카테고리의 다른 글
[Node.js] Multer 파일 업로드 관련 에러: MulterError: Unexpected field (0) | 2022.05.14 |
---|---|
자바스크립트 File API 파헤치기: Blob, File, FileReader, FileList, BlobURL (0) | 2021.12.01 |
[자바스크립트] HTML에서 특정 텍스트가 포함된 요소(태그)를 클릭하는 방법 (0) | 2021.10.18 |
[자바스크립트] 코드로 HTML에 다른 js파일을 로드하는 방법 (0) | 2021.07.09 |
[node.js] path 모듈 사용 방법 정리: 파일/디렉터리 경로 처리 (0) | 2021.07.09 |
댓글