Bab 2: Binary Data & Files
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
// 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 biasaArrayBuffer itu cuma wadah kosong. Untuk baca/tulis, kamu butuh "view" (kacamata).
TypedArray — Kacamata untuk Baca Buffer
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
// 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
// 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
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.140000104904175Kapan pakai DataView? Kalau kamu baca format file yang campur-campur tipe data (misal: header file BMP, protocol jaringan).
Contoh Praktis: Manipulasi Pixel Gambar
// 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);- ArrayBuffer tidak bisa di-resize: Sekali dibuat, ukurannya tetap
- TypedArray overflow:
new Uint8Array([256])→ hasilnya0(overflow, bukan error!) - 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
// 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"// 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!!!"// 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)
// 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
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// 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
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!"
};- TextEncoder HANYA UTF-8: Tidak bisa encode ke encoding lain
- Emoji = multi-byte:
"😀".length= 2 (JS string), tapi encode jadi 4 byte - 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
// 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' }
);Blob URL — Buat Link Download
// 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
// 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
// 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
// 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- Lupa
URL.revokeObjectURL(): Memory leak! Blob URL tetap di memori sampai di-revoke - Blob immutable: Sekali dibuat, tidak bisa diubah. Harus buat Blob baru
- 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
<input type="file" id="fileInput" multiple>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
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); // 15FileReader — Baca Isi File
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
<input type="file" id="imageInput" accept="image/*">
<img id="preview" style="max-width: 300px;">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
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
<div id="dropZone" style="width:300px; height:200px; border:2px dashed #ccc; text-align:center; line-height:200px;">
Drop file di sini
</div>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)
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);- FileReader async:
reader.resultbaru ada SETELAHonload— bukan langsung setelahreadAsText() - File besar = lambat: Untuk file > 100MB, pertimbangkan
slice()dan baca sepotong-potong - Security: Browser tidak kasih path asli file (
file.namecuma nama, bukan full path)
🏆 Challenge
Buat "File Analyzer" sederhana:
- Input file (accept semua tipe)
- Tampilkan: nama, ukuran (format KB/MB), tipe MIME
- Kalau file teks (.txt, .csv, .json) → tampilkan 100 karakter pertama
- Kalau file gambar → tampilkan preview
- Kalau file lain → tampilkan hex dump 16 byte pertama
// 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.