Jelajahi pola Proksi JavaScript tingkat lanjut untuk intersepsi objek, validasi, dan perilaku dinamis. Pelajari cara meningkatkan kualitas, keamanan, dan pemeliharaan kode dengan contoh praktis.
Pola Proksi JavaScript: Intersepsi dan Validasi Objek Tingkat Lanjut
Objek Proksi JavaScript adalah fitur canggih yang memungkinkan Anda untuk mencegat dan menyesuaikan operasi dasar objek. Ini memungkinkan teknik metaprogramming tingkat lanjut, menawarkan kontrol yang lebih besar atas perilaku objek dan membuka kemungkinan untuk pola desain yang canggih. Artikel ini mengeksplorasi berbagai pola Proksi, menampilkan kasus penggunaannya dalam validasi, intersepsi, dan modifikasi perilaku dinamis. Kita akan mendalami contoh-contoh praktis untuk menunjukkan bagaimana Proksi dapat meningkatkan kualitas, keamanan, dan pemeliharaan kode dalam proyek JavaScript Anda.
Memahami Proksi JavaScript
Pada intinya, objek Proksi membungkus objek lain (target) dan mencegat operasi yang dilakukan pada target tersebut. Intersepsi ini ditangani oleh trap, yaitu metode yang mendefinisikan perilaku kustom untuk operasi tertentu seperti mendapatkan properti, menetapkan properti, atau memanggil fungsi. API Proksi menyediakan mekanisme yang fleksibel dan dapat diperluas untuk mengubah perilaku default objek.
Konsep Kunci
- Target: Objek asli yang dibungkus oleh Proksi.
- Handler: Objek yang berisi metode trap. Setiap trap sesuai dengan operasi tertentu.
- Trap: Metode di dalam handler yang mencegat dan menyesuaikan operasi objek. Trap umum termasuk
get,set,apply, danconstruct.
Membuat Proksi
Untuk membuat Proksi, Anda menggunakan konstruktor Proxy, dengan memberikan objek target dan objek handler sebagai argumen:
const target = {};
const handler = {
get: function(target, property, receiver) {
console.log(`Mengambil properti: ${property}`);
return Reflect.get(target, property, receiver);
}
};
const proxy = new Proxy(target, handler);
proxy.name = "John"; // Mencatat: Mengambil properti: name
console.log(proxy.name); // Mencatat: Mengambil properti: name, lalu John
Trap Proksi yang Umum
Proksi menawarkan berbagai trap untuk mencegat berbagai operasi. Berikut adalah beberapa trap yang paling umum digunakan:
get(target, property, receiver): Mencegat akses properti.set(target, property, value, receiver): Mencegat penetapan properti.has(target, property): Mencegat operatorin.deleteProperty(target, property): Mencegat operatordelete.apply(target, thisArg, argumentsList): Mencegat pemanggilan fungsi.construct(target, argumentsList, newTarget): Mencegat operatornew.getPrototypeOf(target): Mencegat metodeObject.getPrototypeOf().setPrototypeOf(target, prototype): Mencegat metodeObject.setPrototypeOf().isExtensible(target): Mencegat metodeObject.isExtensible().preventExtensions(target): Mencegat metodeObject.preventExtensions().getOwnPropertyDescriptor(target, property): Mencegat metodeObject.getOwnPropertyDescriptor().defineProperty(target, property, descriptor): Mencegat metodeObject.defineProperty().ownKeys(target): Mencegat metodeObject.getOwnPropertyNames()danObject.getOwnPropertySymbols().
Pola Proksi
Sekarang, mari kita jelajahi beberapa pola Proksi praktis dan aplikasinya:
1. Proksi Validasi
Proksi Validasi memberlakukan batasan pada penetapan properti. Ini mencegat trap set untuk memvalidasi nilai baru sebelum mengizinkan penetapan dilanjutkan.
Contoh: Memvalidasi input pengguna dalam formulir.
const user = {};
const validator = {
set: function(target, property, value) {
if (property === 'age') {
if (!Number.isInteger(value) || value < 0 || value > 120) {
throw new Error('Usia tidak valid. Usia harus berupa bilangan bulat antara 0 dan 120.');
}
}
target[property] = value;
return true; // Menandakan keberhasilan
}
};
const proxy = new Proxy(user, validator);
proxy.name = 'Alice';
proxy.age = 30;
console.log(user);
try {
proxy.age = 'invalid'; // Melemparkan error
} catch (error) {
console.error(error.message);
}
Dalam contoh ini, trap set memeriksa apakah properti age adalah bilangan bulat antara 0 dan 120. Jika validasi gagal, sebuah error akan dilemparkan, mencegah nilai yang tidak valid untuk ditetapkan.
Contoh Global: Pola validasi ini penting untuk memastikan integritas data dalam aplikasi global di mana input pengguna mungkin berasal dari berbagai sumber dan budaya. Misalnya, validasi kode pos dapat sangat bervariasi antar negara. Proksi validasi dapat diadaptasi untuk mendukung aturan validasi yang berbeda berdasarkan lokasi pengguna.
const address = {};
const addressValidator = {
set: function(target, property, value) {
if (property === 'postalCode') {
// Contoh: Mengasumsikan validasi kode pos AS yang sederhana
if (!/^[0-9]{5}(?:-[0-9]{4})?$/.test(value)) {
throw new Error('Kode pos AS tidak valid.');
}
}
target[property] = value;
return true;
}
};
const addressProxy = new Proxy(address, addressValidator);
addressProxy.postalCode = "12345-6789"; // Valid
try {
addressProxy.postalCode = "abcde"; // Tidak Valid
} catch(e) {
console.log(e);
}
// Untuk aplikasi yang lebih internasional, Anda akan menggunakan pustaka validasi yang lebih canggih
// yang dapat memvalidasi kode pos berdasarkan negara pengguna.
2. Proksi Pencatatan (Logging)
Proksi Pencatatan mencegat akses dan penetapan properti untuk mencatat operasi-operasi ini. Ini berguna untuk debugging dan audit.
Contoh: Mencatat akses dan modifikasi properti.
const data = {
value: 10
};
const logger = {
get: function(target, property) {
console.log(`Mengambil properti: ${property}`);
return target[property];
},
set: function(target, property, value) {
console.log(`Menetapkan properti: ${property} ke ${value}`);
target[property] = value;
return true;
}
};
const proxy = new Proxy(data, logger);
console.log(proxy.value); // Mencatat: Mengambil properti: value, lalu 10
proxy.value = 20; // Mencatat: Menetapkan properti: value ke 20
Trap get dan set mencatat properti yang diakses atau dimodifikasi, memberikan jejak interaksi objek.
Contoh Global: Di perusahaan multinasional, proksi pencatatan dapat digunakan untuk mengaudit akses dan modifikasi data yang dilakukan oleh karyawan di lokasi yang berbeda. Ini sangat penting untuk tujuan kepatuhan dan keamanan. Zona waktu mungkin perlu dipertimbangkan dalam informasi pencatatan.
const employeeData = {
name: "John Doe",
salary: 50000
};
const auditLogger = {
get: function(target, property) {
const timestamp = new Date().toISOString();
console.log(`${timestamp} - [GET] Mengakses properti: ${property}`);
return target[property];
},
set: function(target, property, value) {
const timestamp = new Date().toISOString();
console.log(`${timestamp} - [SET] Menetapkan properti: ${property} ke ${value}`);
target[property] = value;
return true;
}
};
const proxiedEmployee = new Proxy(employeeData, auditLogger);
proxiedEmployee.name; // Mencatat stempel waktu dan akses ke 'name'
proxiedEmployee.salary = 60000; // Mencatat stempel waktu dan modifikasi 'salary'
3. Proksi Hanya-Baca (Read-Only)
Proksi Hanya-Baca mencegah penetapan properti. Ini mencegat trap set dan melemparkan error jika ada upaya untuk memodifikasi properti.
Contoh: Membuat objek menjadi tidak dapat diubah (immutable).
const config = {
apiUrl: 'https://api.example.com'
};
const readOnly = {
set: function(target, property, value) {
throw new Error(`Tidak dapat menetapkan properti: ${property}. Objek bersifat hanya-baca.`);
}
};
const proxy = new Proxy(config, readOnly);
console.log(proxy.apiUrl);
try {
proxy.apiUrl = 'https://newapi.example.com'; // Melemparkan error
} catch (error) {
console.error(error.message);
}
Setiap upaya untuk menetapkan properti pada proksi akan menghasilkan error, memastikan bahwa objek tetap tidak dapat diubah.
Contoh Global: Pola ini berguna untuk melindungi file konfigurasi yang tidak boleh diubah saat runtime, terutama dalam aplikasi yang didistribusikan secara global. Mengubah konfigurasi secara tidak sengaja di satu wilayah dapat mempengaruhi seluruh sistem.
const globalSettings = {
defaultLanguage: "en",
currency: "USD",
timeZone: "UTC"
};
const immutableHandler = {
set: function(target, property, value) {
throw new Error(`Tidak dapat memodifikasi properti hanya-baca: ${property}`);
}
};
const immutableSettings = new Proxy(globalSettings, immutableHandler);
console.log(immutableSettings.defaultLanguage); // menghasilkan 'en'
// Mencoba mengubah nilai akan melemparkan error
// immutableSettings.defaultLanguage = "fr"; // melemparkan Error: Tidak dapat memodifikasi properti hanya-baca: defaultLanguage
4. Proksi Virtual
Proksi Virtual mengontrol akses ke sumber daya yang mungkin mahal untuk dibuat atau diambil. Ini dapat menunda pembuatan sumber daya hingga benar-benar dibutuhkan.
Contoh: Memuat gambar secara malas (lazy loading).
const image = {
display: function() {
console.log('Menampilkan gambar');
}
};
const virtualProxy = {
get: function(target, property) {
if (property === 'display') {
console.log('Membuat gambar...');
const realImage = {
display: function() {
console.log('Menampilkan gambar asli');
}
};
target.display = realImage.display;
return realImage.display;
}
return target[property];
}
};
const proxy = new Proxy(image, virtualProxy);
// Gambar tidak dibuat sampai display dipanggil.
proxy.display(); // Mencatat: Membuat gambar..., lalu Menampilkan gambar asli
Objek gambar asli hanya dibuat ketika metode display dipanggil, menghindari konsumsi sumber daya yang tidak perlu.
Contoh Global: Pertimbangkan situs web e-commerce global yang menyajikan gambar produk. Dengan menggunakan Proksi Virtual, gambar dapat dimuat hanya ketika terlihat oleh pengguna, mengoptimalkan penggunaan bandwidth dan meningkatkan waktu muat halaman, terutama bagi pengguna dengan koneksi internet lambat di berbagai wilayah.
const product = {
loadImage: function() {
console.log("Memuat gambar resolusi tinggi...");
// Mensimulasikan pemuatan gambar besar
setTimeout(() => {
console.log("Gambar dimuat");
this.displayImage();
}, 2000);
},
displayImage: function() {
console.log("Menampilkan gambar");
}
};
const lazyLoadProxy = {
get: function(target, property) {
if (property === "displayImage") {
// Alih-alih memuat segera, tunda pemuatan
console.log("Permintaan untuk menampilkan gambar diterima. Memuat...");
target.loadImage();
return function() { /* Sengaja Kosong */ }; // Kembalikan fungsi kosong untuk mencegah eksekusi segera
}
return target[property];
}
};
const proxiedProduct = new Proxy(product, lazyLoadProxy);
// Memanggil displayImage memicu proses lazy loading
proxiedProduct.displayImage();
5. Proksi yang Dapat Dicabut (Revocable)
Proksi yang Dapat Dicabut memungkinkan Anda untuk mencabut proksi kapan saja, menjadikannya tidak dapat digunakan. Ini berguna untuk skenario yang sensitif terhadap keamanan di mana Anda perlu mengontrol akses ke objek.
Contoh: Memberikan akses sementara ke sumber daya.
const target = {
secret: 'Ini adalah rahasia'
};
const handler = {
get: function(target, property) {
console.log('Mengakses properti rahasia');
return target[property];
}
};
const { proxy, revoke } = Proxy.revocable(target, handler);
console.log(proxy.secret); // Mencatat: Mengakses properti rahasia, lalu Ini adalah rahasia
revoke();
try {
console.log(proxy.secret); // Melemparkan TypeError
} catch (error) {
console.error(error.message); // Mencatat: Cannot perform 'get' on a proxy that has been revoked
}
Metode Proxy.revocable() membuat proksi yang dapat dicabut. Memanggil fungsi revoke() membuat proksi tidak dapat digunakan, mencegah akses lebih lanjut ke objek target.
Contoh Global: Dalam sistem yang didistribusikan secara global, Anda mungkin menggunakan proksi yang dapat dicabut untuk memberikan akses sementara ke data sensitif ke layanan yang berjalan di wilayah tertentu. Setelah waktu tertentu, proksi dapat dicabut untuk mencegah akses yang tidak sah.
const sensitiveData = {
apiKey: "SUPER_SECRET_KEY"
};
const handler = {
get: function(target, property) {
console.log("Mengakses data sensitif");
return target[property];
}
};
const { proxy: dataProxy, revoke: revokeAccess } = Proxy.revocable(sensitiveData, handler);
// Izinkan akses selama 5 detik
setTimeout(() => {
revokeAccess();
console.log("Akses dicabut");
}, 5000);
// Mencoba mengakses data
console.log(dataProxy.apiKey); // Mencatat Kunci API
// Setelah 5 detik, ini akan melemparkan error
setTimeout(() => {
try {
console.log(dataProxy.apiKey); // Melemparkan: TypeError: Cannot perform 'get' on a proxy that has been revoked
} catch (error) {
console.error(error);
}
}, 6000);
6. Proksi Konversi Tipe
Proksi Konversi Tipe mencegat akses properti untuk secara otomatis mengonversi nilai yang dikembalikan ke tipe tertentu. Ini dapat berguna untuk bekerja dengan data dari sumber berbeda yang mungkin memiliki tipe yang tidak konsisten.
Contoh: Mengonversi nilai string menjadi angka.
const data = {
price: '10.99',
quantity: '5'
};
const typeConverter = {
get: function(target, property) {
const value = target[property];
if (typeof value === 'string' && !isNaN(Number(value))) {
return Number(value);
}
return value;
}
};
const proxy = new Proxy(data, typeConverter);
console.log(proxy.price + 1); // Mencatat: 11.99 (angka)
console.log(proxy.quantity * 2); // Mencatat: 10 (angka)
Trap get memeriksa apakah nilai properti adalah string yang dapat dikonversi menjadi angka. Jika ya, ia mengonversi nilai tersebut menjadi angka sebelum mengembalikannya.
Contoh Global: Ketika berhadapan dengan data yang berasal dari API dengan konvensi pemformatan yang berbeda (misalnya, format tanggal atau simbol mata uang yang berbeda), Proksi Konversi Tipe dapat memastikan konsistensi data di seluruh aplikasi Anda, terlepas dari sumbernya. Misalnya, menangani format tanggal yang berbeda dan mengonversinya semua ke format ISO 8601.
const apiData = {
dateUS: "12/31/2023",
dateEU: "31/12/2023"
};
const dateFormatConverter = {
get: function(target, property) {
let value = target[property];
if (property.startsWith("date")) {
// Mencoba mengonversi format tanggal AS dan UE ke ISO 8601
if (property === "dateUS") {
const [month, day, year] = value.split("/");
value = `${year}-${month}-${day}`;
} else if (property === "dateEU") {
const [day, month, year] = value.split("/");
value = `${year}-${month}-${day}`;
}
return value;
}
return value;
}
};
const proxiedApiData = new Proxy(apiData, dateFormatConverter);
console.log(proxiedApiData.dateUS); // Menghasilkan: 2023-12-31
console.log(proxiedApiData.dateEU); // Menghasilkan: 2023-12-31
Praktik Terbaik Menggunakan Proksi
- Gunakan Proksi dengan Bijaksana: Proksi dapat menambah kompleksitas pada kode Anda. Gunakan hanya jika memberikan manfaat yang signifikan, seperti validasi yang lebih baik, pencatatan, atau kontrol atas perilaku objek.
- Pertimbangkan Kinerja: Trap proksi dapat menimbulkan overhead. Lakukan profil pada kode Anda untuk memastikan bahwa Proksi tidak berdampak negatif pada kinerja, terutama di bagian yang kritis terhadap kinerja.
- Tangani Error dengan Baik: Pastikan metode trap Anda menangani error dengan tepat, memberikan pesan error yang informatif bila diperlukan.
- Gunakan API Reflect: API
Reflectmenyediakan metode yang mencerminkan perilaku default operasi objek. Gunakan metodeReflectdi dalam metode trap Anda untuk mendelegasikan ke perilaku asli bila sesuai. Ini memastikan bahwa trap Anda tidak merusak fungsionalitas yang ada. - Dokumentasikan Proksi Anda: Dokumentasikan dengan jelas tujuan dan perilaku Proksi Anda, termasuk trap yang digunakan dan batasan yang diberlakukan. Ini akan membantu pengembang lain memahami dan memelihara kode Anda.
Kesimpulan
Proksi JavaScript adalah alat yang ampuh untuk manipulasi dan intersepsi objek tingkat lanjut. Dengan memahami dan menerapkan berbagai pola Proksi, Anda dapat meningkatkan kualitas, keamanan, dan pemeliharaan kode. Mulai dari memvalidasi input pengguna hingga mengontrol akses ke sumber daya sensitif, Proksi menawarkan mekanisme yang fleksibel dan dapat diperluas untuk menyesuaikan perilaku objek. Saat Anda menjelajahi kemungkinan Proksi, ingatlah untuk menggunakannya dengan bijaksana dan mendokumentasikan kode Anda secara menyeluruh.
Contoh-contoh yang diberikan menunjukkan cara menggunakan Proksi JavaScript untuk memecahkan masalah dunia nyata dalam konteks global. Dengan memahami dan menerapkan pola-pola ini, Anda dapat membuat aplikasi yang lebih kuat, aman, dan dapat dipelihara yang memenuhi kebutuhan basis pengguna yang beragam. Ingatlah untuk selalu mempertimbangkan implikasi global dari kode Anda dan menyesuaikan solusi Anda dengan persyaratan spesifik dari berbagai wilayah dan budaya.