Bab 2: Binary Data & Files

3 menit baca

2.1 ArrayBuffer & Binary Arrays

Apa Itu Binary Data?

Semua data di komputer pada dasarnya adalah angka biner (0 dan 1). Teks, gambar, video — semuanya disimpan sebagai deretan byte. JavaScript punya cara khusus untuk bekerja dengan data mentah ini.

Analogi: Kalau String itu buku yang bisa kamu baca, ArrayBuffer itu gulungan pita kaset — data mentah yang butuh "pemutar" khusus untuk dibaca.

ArrayBuffer — Wadah Data Mentah

javascript
// Buat buffer 16 byte (16 slot, masing-masing 1 byte)
const buffer = new ArrayBuffer(16);

console.log(buffer.byteLength); // 16

// ❌ Tidak bisa langsung akses isinya!
// buffer[0] = 42;  // Error! ArrayBuffer bukan array biasa

ArrayBuffer itu cuma wadah kosong. Untuk baca/tulis, kamu butuh "view" (kacamata).

TypedArray — Kacamata untuk Baca Buffer

javascript
const buffer = new ArrayBuffer(16); // 16 byte

// Lihat sebagai array angka 32-bit (4 byte per angka)
const view32 = new Uint32Array(buffer);
console.log(view32.length); // 4 (16 byte ÷ 4 byte = 4 angka)

// Lihat sebagai array angka 8-bit (1 byte per angka)
const view8 = new Uint8Array(buffer);
console.log(view8.length); // 16 (16 byte ÷ 1 byte = 16 angka)

// Tulis data
view8[0] = 255;
view8[1] = 128;

// View yang berbeda melihat DATA YANG SAMA!
console.log(view32[0]); // 33023 (karena 255 + 128*256 dalam little-endian)

Jenis-Jenis TypedArray

javascript
// Integer (bilangan bulat)
new Uint8Array(buffer);    // 0 sampai 255 (1 byte, tanpa tanda)
new Int8Array(buffer);     // -128 sampai 127 (1 byte, bertanda)
new Uint16Array(buffer);   // 0 sampai 65535 (2 byte)
new Int16Array(buffer);    // -32768 sampai 32767 (2 byte)
new Uint32Array(buffer);   // 0 sampai 4294967295 (4 byte)
new Int32Array(buffer);    // -2147483648 sampai 2147483647 (4 byte)

// Float (desimal)
new Float32Array(buffer);  // angka desimal 32-bit
new Float64Array(buffer);  // angka desimal 64-bit (presisi tinggi)

// Khusus
new Uint8ClampedArray(buffer); // 0-255, nilai di luar di-clamp (untuk pixel gambar)

Membuat TypedArray Langsung

javascript
// Dari array biasa
const arr = new Uint8Array([72, 101, 108, 108, 111]);
console.log(arr[0]); // 72

// Dari panjang (diisi 0)
const zeros = new Uint8Array(5);
console.log(zeros); // Uint8Array [0, 0, 0, 0, 0]

// Dari TypedArray lain (copy)
const copy = new Uint8Array(arr);
copy[0] = 0;
console.log(arr[0]); // 72 (original tidak berubah)

DataView — View Fleksibel

javascript
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);

// Tulis di posisi tertentu dengan tipe tertentu
view.setUint8(0, 255);          // byte ke-0 = 255
view.setUint16(1, 1000);        // byte ke-1&2 = 1000 (big-endian)
view.setFloat32(4, 3.14);       // byte ke-4,5,6,7 = 3.14

// Baca
console.log(view.getUint8(0));      // 255
console.log(view.getUint16(1));     // 1000
console.log(view.getFloat32(4));    // 3.140000104904175

Kapan pakai DataView? Kalau kamu baca format file yang campur-campur tipe data (misal: header file BMP, protocol jaringan).

Contoh Praktis: Manipulasi Pixel Gambar

javascript
// Ambil data pixel dari canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 100;
canvas.height = 100;

// Gambar sesuatu
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 100, 100);

// Ambil pixel data (Uint8ClampedArray)
const imageData = ctx.getImageData(0, 0, 100, 100);
const pixels = imageData.data; // [R, G, B, A, R, G, B, A, ...]

// Ubah semua pixel jadi grayscale
for (let i = 0; i < pixels.length; i += 4) {
  const avg = (pixels[i] + pixels[i+1] + pixels[i+2]) / 3;
  pixels[i] = avg;     // R
  pixels[i+1] = avg;   // G
  pixels[i+2] = avg;   // B
  // pixels[i+3] = alpha (biarkan)
}

// Taruh kembali
ctx.putImageData(imageData, 0, 0);
⚠️Jebakan!
  1. ArrayBuffer tidak bisa di-resize: Sekali dibuat, ukurannya tetap
  2. TypedArray overflow: new Uint8Array([256]) → hasilnya 0 (overflow, bukan error!)
  3. Endianness: DataView default big-endian, TypedArray pakai system endianness

2.2 TextDecoder & TextEncoder

Mengubah Binary ↔ Teks

Komputer simpan teks sebagai angka (encoding). TextDecoder dan TextEncoder adalah jembatan antara binary data dan string.

Analogi: TextDecoder itu penerjemah yang bisa baca kode morse dan ubah jadi bahasa manusia. TextEncoder sebaliknya.

TextDecoder — Binary ke String

javascript
// Dari Uint8Array ke String
const bytes = new Uint8Array([72, 101, 108, 108, 111]);
const decoder = new TextDecoder(); // default: UTF-8

const text = decoder.decode(bytes);
console.log(text); // "Hello"
javascript
// Decode sebagian buffer
const buffer = new ArrayBuffer(100);
const view = new Uint8Array(buffer, 0, 5); // ambil 5 byte pertama
view.set([72, 105, 33, 33, 33]);

const decoder = new TextDecoder();
console.log(decoder.decode(view)); // "Hi!!!"
javascript
// Encoding lain (misal: Windows-1251 untuk Cyrillic)
const win1251bytes = new Uint8Array([207, 240, 232, 226, 229, 242]);
const decoder = new TextDecoder('windows-1251');
console.log(decoder.decode(win1251bytes)); // "Привет"

TextDecoder Stream (untuk data besar)

javascript
// Kalau data datang sepotong-sepotong (streaming)
const decoder = new TextDecoder('utf-8', { stream: true });

// Chunk 1: karakter UTF-8 terpotong di tengah
const chunk1 = new Uint8Array([0xE2, 0x82]); // setengah simbol €
console.log(decoder.decode(chunk1, { stream: true })); // "" (belum lengkap)

// Chunk 2: sisa karakter
const chunk2 = new Uint8Array([0xAC]); // sisa simbol €
console.log(decoder.decode(chunk2)); // "€" (sekarang lengkap!)

TextEncoder — String ke Binary

javascript
const encoder = new TextEncoder(); // selalu UTF-8

const bytes = encoder.encode("Hello");
console.log(bytes); // Uint8Array [72, 101, 108, 108, 111]

// Emoji jadi multi-byte
const emoji = encoder.encode("😀");
console.log(emoji); // Uint8Array [240, 159, 152, 128] (4 byte!)
console.log(emoji.length); // 4
javascript
// encodeInto — tulis langsung ke buffer yang sudah ada
const encoder = new TextEncoder();
const buffer = new Uint8Array(10);

const result = encoder.encodeInto("Hi!", buffer);
console.log(result); // { read: 3, written: 3 }
console.log(buffer); // Uint8Array [72, 105, 33, 0, 0, 0, 0, 0, 0, 0]

Contoh Praktis: Kirim Teks via WebSocket sebagai Binary

javascript
const encoder = new TextEncoder();
const decoder = new TextDecoder();

// Kirim
const message = "Halo dari JavaScript!";
const encoded = encoder.encode(message);
websocket.send(encoded.buffer); // kirim sebagai ArrayBuffer

// Terima
websocket.onmessage = (event) => {
  const received = new Uint8Array(event.data);
  const text = decoder.decode(received);
  console.log(text); // "Halo dari JavaScript!"
};
⚠️Jebakan!
  1. TextEncoder HANYA UTF-8: Tidak bisa encode ke encoding lain
  2. Emoji = multi-byte: "😀".length = 2 (JS string), tapi encode jadi 4 byte
  3. Stream mode: Jangan lupa { stream: true } kalau data datang sepotong-potong

2.3 Blob

Apa Itu Blob?

Blob (Binary Large Object) adalah wadah untuk data binary yang punya tipe MIME. Bedanya dengan ArrayBuffer: Blob itu "file virtual" yang siap dipakai browser.

Analogi: Kalau ArrayBuffer itu bahan mentah (tepung, gula, telur), Blob itu kue yang sudah jadi dan dikemas — siap disajikan.

Membuat Blob

javascript
// Dari string
const textBlob = new Blob(
  ["Halo dunia!"],           // array of parts
  { type: 'text/plain' }    // MIME type
);
console.log(textBlob.size); // 11 byte
console.log(textBlob.type); // "text/plain"

// Dari beberapa bagian
const htmlBlob = new Blob(
  ['<html>', '<body>', '<h1>Halo!</h1>', '</body>', '</html>'],
  { type: 'text/html' }
);

// Dari ArrayBuffer
const buffer = new Uint8Array([72, 101, 108, 108, 111]);
const binBlob = new Blob([buffer], { type: 'application/octet-stream' });

// Campuran
const mixBlob = new Blob(
  ["Header\n", buffer, "\nFooter"],
  { type: 'text/plain' }
);
javascript
// Buat file dan download tanpa server!
const data = JSON.stringify({ nama: "Yazid", skill: "JavaScript" }, null, 2);
const blob = new Blob([data], { type: 'application/json' });

// Buat URL temporary
const url = URL.createObjectURL(blob);
// url = "blob:http://localhost/550e8400-e29b-41d4-a716-446655440000"

// Buat link download
const link = document.createElement('a');
link.href = url;
link.download = 'profil.json'; // nama file saat download
link.textContent = 'Download Profil';
document.body.appendChild(link);

// PENTING: Revoke URL setelah selesai (bebaskan memori)
link.onclick = () => {
  setTimeout(() => URL.revokeObjectURL(url), 1000);
};

Blob sebagai Image Source

javascript
// Tampilkan gambar dari Blob
const response = await fetch('https://example.com/photo.jpg');
const imageBlob = await response.blob();

const img = document.createElement('img');
img.src = URL.createObjectURL(imageBlob);
document.body.appendChild(img);

// Jangan lupa revoke saat img sudah load
img.onload = () => URL.revokeObjectURL(img.src);

Blob ke Base64

javascript
// Konversi Blob ke base64 (untuk embed di HTML/CSS)
function blobToBase64(blob) {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result);
    reader.readAsDataURL(blob);
  });
}

const blob = new Blob(['Hello!'], { type: 'text/plain' });
const base64 = await blobToBase64(blob);
console.log(base64); // "data:text/plain;base64,SGVsbG8h"

Slice Blob

javascript
// Potong blob (berguna untuk upload file besar sepotong-potong)
const bigBlob = new Blob(["A".repeat(1000000)]); // 1MB

const chunk1 = bigBlob.slice(0, 500000);        // 500KB pertama
const chunk2 = bigBlob.slice(500000, 1000000);   // 500KB kedua

console.log(chunk1.size); // 500000
console.log(chunk2.size); // 500000
⚠️Jebakan!
  1. Lupa URL.revokeObjectURL(): Memory leak! Blob URL tetap di memori sampai di-revoke
  2. Blob immutable: Sekali dibuat, tidak bisa diubah. Harus buat Blob baru
  3. Base64 lebih besar: Encoding base64 menambah ~33% ukuran. Pakai Blob URL kalau bisa

2.4 File & FileReader

Apa Itu File?

File adalah Blob yang punya nama dan tanggal modifikasi. Biasanya didapat dari <input type="file"> atau drag & drop.

Analogi: Kalau Blob itu "data dalam amplop", File itu "amplop yang ada label nama dan tanggal".

Mendapatkan File dari Input

html
<input type="file" id="fileInput" multiple>
javascript
const input = document.getElementById('fileInput');

input.addEventListener('change', (event) => {
  const files = event.target.files; // FileList (mirip array)

  for (const file of files) {
    console.log(`Nama: ${file.name}`);
    console.log(`Ukuran: ${file.size} byte`);
    console.log(`Tipe: ${file.type}`);
    console.log(`Terakhir diubah: ${new Date(file.lastModified)}`);
  }
});

Membuat File Manual

javascript
const file = new File(
  ["Isi file saya"],           // konten (array of parts)
  "catatan.txt",               // nama file
  { type: "text/plain", lastModified: Date.now() }
);

console.log(file.name); // "catatan.txt"
console.log(file.size); // 15

FileReader — Baca Isi File

javascript
const input = document.getElementById('fileInput');

input.addEventListener('change', (event) => {
  const file = event.target.files[0];
  const reader = new FileReader();

  // Event saat selesai baca
  reader.onload = () => {
    console.log('Isi file:', reader.result);
  };

  // Event saat error
  reader.onerror = () => {
    console.error('Gagal baca:', reader.error);
  };

  // Mulai baca (pilih salah satu metode)
  reader.readAsText(file);           // → string
  // reader.readAsDataURL(file);     // → base64 data URL
  // reader.readAsArrayBuffer(file); // → ArrayBuffer
});

Contoh: Preview Gambar Sebelum Upload

html
<input type="file" id="imageInput" accept="image/*">
<img id="preview" style="max-width: 300px;">
javascript
const imageInput = document.getElementById('imageInput');
const preview = document.getElementById('preview');

imageInput.addEventListener('change', (event) => {
  const file = event.target.files[0];

  if (!file.type.startsWith('image/')) {
    alert('Pilih file gambar!');
    return;
  }

  const reader = new FileReader();
  reader.onload = () => {
    preview.src = reader.result; // data URL langsung jadi src
  };
  reader.readAsDataURL(file);
});

Contoh: Baca File CSV

javascript
function parseCSV(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();

    reader.onload = () => {
      const lines = reader.result.split('\n');
      const headers = lines[0].split(',');
      const data = [];

      for (let i = 1; i < lines.length; i++) {
        if (!lines[i].trim()) continue;
        const values = lines[i].split(',');
        const row = {};
        headers.forEach((h, idx) => {
          row[h.trim()] = values[idx]?.trim();
        });
        data.push(row);
      }

      resolve(data);
    };

    reader.onerror = () => reject(reader.error);
    reader.readAsText(file);
  });
}

// Penggunaan
input.addEventListener('change', async (e) => {
  const data = await parseCSV(e.target.files[0]);
  console.log(data);
  // [{ nama: "Yazid", umur: "25" }, { nama: "Budi", umur: "30" }]
});

Drag & Drop File

html
<div id="dropZone" style="width:300px; height:200px; border:2px dashed #ccc; text-align:center; line-height:200px;">
  Drop file di sini
</div>
javascript
const dropZone = document.getElementById('dropZone');

// Cegah browser buka file
dropZone.addEventListener('dragover', (e) => {
  e.preventDefault();
  dropZone.style.borderColor = 'blue';
});

dropZone.addEventListener('dragleave', () => {
  dropZone.style.borderColor = '#ccc';
});

dropZone.addEventListener('drop', (e) => {
  e.preventDefault();
  dropZone.style.borderColor = '#ccc';

  const files = e.dataTransfer.files;
  for (const file of files) {
    console.log(`Dropped: ${file.name} (${file.size} bytes)`);
  }
});

Progress Event (untuk file besar)

javascript
const reader = new FileReader();

reader.onprogress = (event) => {
  if (event.lengthComputable) {
    const percent = Math.round((event.loaded / event.total) * 100);
    console.log(`Loading: ${percent}%`);
  }
};

reader.onload = () => console.log('Selesai!');
reader.readAsArrayBuffer(bigFile);
⚠️Jebakan!
  1. FileReader async: reader.result baru ada SETELAH onload — bukan langsung setelah readAsText()
  2. File besar = lambat: Untuk file > 100MB, pertimbangkan slice() dan baca sepotong-potong
  3. Security: Browser tidak kasih path asli file (file.name cuma nama, bukan full path)

🏆 Challenge

Buat "File Analyzer" sederhana:

  1. Input file (accept semua tipe)
  2. Tampilkan: nama, ukuran (format KB/MB), tipe MIME
  3. Kalau file teks (.txt, .csv, .json) → tampilkan 100 karakter pertama
  4. Kalau file gambar → tampilkan preview
  5. Kalau file lain → tampilkan hex dump 16 byte pertama
javascript
// Hint:
// - Cek file.type untuk tentukan cara baca
// - readAsText untuk teks, readAsDataURL untuk gambar
// - readAsArrayBuffer + Uint8Array untuk hex dump
// - Untuk format ukuran: (size / 1024).toFixed(2) + ' KB'

Sudah paham materi ini?

Tandai sebagai selesai untuk melacak progress-mu.