Memperkenalkan proxy ES2015

Addy Osmani
Addy Osmani

Proxy ES2015 (di Chrome 49 dan yang lebih baru) menyediakan JavaScript dengan API intervensi, yang memungkinkan kita menjebak atau mencegat semua operasi pada objek target dan mengubah cara target ini beroperasi.

Proxy memiliki banyak kegunaan, termasuk:

  • Intersep
  • Virtualisasi objek
  • Pengelolaan resource
  • Membuat profil atau logging untuk proses debug
  • Keamanan dan kontrol akses
  • Kontrak untuk penggunaan objek

Proxy API berisi Konstruktor proxy yang mengambil objek target yang ditetapkan dan objek pengendali.

var target = { /* some properties */ };
var handler = { /* trap functions */ };
var proxy = new Proxy(target, handler);

Perilaku proxy dikontrol oleh pengendali, yang dapat mengubah perilaku asli objek target dengan beberapa cara yang berguna. Pengendali berisi metode perangkap opsional (misalnya .get(), .set(), .apply()) yang dipanggil saat operasi yang sesuai dilakukan pada proxy.

Intersep

Mari kita mulai dengan mengambil objek biasa dan menambahkan beberapa middleware intersepsi ke objek tersebut menggunakan Proxy API. Ingat, parameter pertama yang diteruskan ke konstruktor adalah target (objek yang di-proxy) dan parameter kedua adalah pengendali (proxy itu sendiri). Di sinilah kita dapat menambahkan hook untuk pengambil, penyetel, atau perilaku lainnya.

var target = {};

var superhero = new Proxy(target, {
    get: function(target, name, receiver) {
        console.log('get was called for:', name);
        return target[name];
    }
});

superhero.power = 'Flight';
console.log(superhero.power);

Dengan menjalankan kode di atas di Chrome 49, kita mendapatkan hal berikut:

get was called for: power  
"Flight"

Seperti yang dapat kita lihat dalam praktiknya, melakukan pengambilan properti atau penetapan properti pada objek proxy dengan benar akan menghasilkan panggilan tingkat meta ke trap yang sesuai pada pengendali. Operasi pengendali mencakup pembacaan properti, penetapan properti, dan penerapan fungsi, yang semuanya diteruskan ke trap yang sesuai.

Fungsi trap dapat, jika memilih, menerapkan operasi secara arbitrer (misalnya meneruskan operasi ke objek target). Hal ini memang terjadi secara default jika perangkap tidak ditentukan. Misalnya, berikut adalah proxy penerusan tanpa pengoperasian yang melakukan hal ini:

var target = {};

var proxy = new Proxy(target, {});
    // operation forwarded to the target
proxy.paul = 'irish';
// 'irish'. The operation has been  forwarded
console.log(target.paul);

Kita baru saja melihat proxy objek biasa, tetapi kita juga dapat dengan mudah membuat proxy objek fungsi, dengan fungsi sebagai target kita. Kali ini kita akan menggunakan perangkap handler.apply():

// Proxying a function object
function sum(a, b) {
    return a + b;
}

var handler = {
    apply: function(target, thisArg, argumentsList) {
        console.log(`Calculate sum: ${argumentsList}`);
        return target.apply(thisArg, argumentsList);
    }
};

var proxy = new Proxy(sum, handler);
proxy(1, 2);
// Calculate sum: 1, 2
// 3

Mengidentifikasi proxy

Identitas proxy dapat diamati menggunakan operator persamaan JavaScript (== dan ===). Seperti yang kita ketahui, saat diterapkan ke dua objek, operator ini akan membandingkan identitas objek. Contoh berikutnya menunjukkan perilaku ini. Membandingkan dua proxy yang berbeda akan menampilkan nilai salah meskipun target dasarnya sama. Dengan cara yang sama, objek target berbeda dari proxy-nya:

// Continuing previous example

var proxy2 = new Proxy (sum, handler);
(proxy==proxy2); // false
(proxy==sum); // false

Idealnya, Anda tidak dapat membedakan proxy dari objek non-proxy sehingga penerapan proxy tidak benar-benar memengaruhi hasil aplikasi Anda. Ini adalah salah satu alasan Proxy API tidak menyertakan cara untuk memeriksa apakah objek adalah proxy atau menyediakan perangkap untuk semua operasi pada objek.

Kasus penggunaan

Seperti yang disebutkan, Proxy memiliki berbagai kasus penggunaan. Banyak dari hal-hal di atas, seperti kontrol akses dan pembuatan profil, termasuk dalam Wrapper generik: proxy yang menggabungkan objek lain dalam "ruang" alamat yang sama. Virtualisasi juga disebutkan. Objek virtual adalah proxy yang mengemulasi objek lain tanpa harus berada di ruang alamat yang sama. Contohnya mencakup objek jarak jauh (yang mengemulasi objek di ruang lain) dan masa depan transparan (mengemulasi hasil yang belum dikomputasi).

Proxy sebagai Pengendali

Kasus penggunaan yang cukup umum untuk pengendali proxy adalah melakukan validasi atau pemeriksaan kontrol akses sebelum melakukan operasi pada objek yang digabungkan. Operasi hanya diteruskan jika pemeriksaan berhasil. Contoh validasi di bawah ini menunjukkan hal ini:

var validator = {
    set: function(obj, prop, value) {
    if (prop === 'yearOfBirth') {
        if (!Number.isInteger(value)) {
        throw new TypeError('The yearOfBirth is not an integer');
        }

        if (value > 3000) {
        throw new RangeError('The yearOfBirth seems invalid');
        }
    }

    // The default behavior to store the value
    obj[prop] = value;
    }
};

var person = new Proxy({}, validator);

person.yearOfBirth = 1986;
console.log(person.yearOfBirth); // 1986
person.yearOfBirth = 'eighties'; // Throws an exception
person.yearOfBirth = 3030; // Throws an exception

Contoh yang lebih kompleks dari pola ini mungkin memperhitungkan semua operasi yang berbeda yang dapat dicegat pengendali proxy. Kita dapat membayangkan implementasi yang harus menduplikasi pola pemeriksaan akses dan meneruskan operasi di setiap perangkap.

Hal ini dapat menjadi rumit untuk diringkas dengan mudah, mengingat setiap operasi mungkin harus diteruskan secara berbeda. Dalam skenario yang sempurna, jika semua operasi dapat disalurkan secara seragam melalui satu perangkap saja, pengendali hanya perlu melakukan pemeriksaan validasi sekali dalam satu perangkap. Anda dapat melakukannya dengan menerapkan pengendali proxy itu sendiri sebagai proxy. Sayangnya, hal ini berada di luar cakupan artikel ini.

Ekstensi Objek

Kasus penggunaan umum lainnya untuk proxy adalah memperluas atau mendefinisikan ulang semantik operasi pada objek. Misalnya, Anda mungkin ingin pengendali mencatat operasi, memberi tahu pengamat, menampilkan pengecualian, bukan menampilkan undefined, atau mengalihkan operasi ke target yang berbeda untuk penyimpanan. Dalam kasus ini, penggunaan proxy dapat menyebabkan hasil yang sangat berbeda dibandingkan dengan penggunaan objek target.

function extend(sup,base) {

    var descriptor = Object.getOwnPropertyDescriptor(base.prototype,"constructor");

    base.prototype = Object.create(sup.prototype);

    var handler = {
    construct: function(target, args) {
        var obj = Object.create(base.prototype);
        this.apply(target,obj, args);
        return obj;
    },

    apply: function(target, that, args) {
        sup.apply(that,args);
        base.apply(that,args);
    }
    };

    var proxy = new Proxy(base, handler);
    descriptor.value = proxy;
    Object.defineProperty(base.prototype, "constructor", descriptor);
    return proxy;
}

var Vehicle = function(name){
    this.name = name;
};

var Car = extend(Vehicle, function(name, year) {
    this.year = year;
});

Car.prototype.style = "Saloon";

var Tesla = new Car("Model S", 2016);

console.log(Tesla.style); // "Saloon"
console.log(Tesla.name); // "Model S"
console.log(Tesla.year);  // 2016

Kontrol Akses

Kontrol akses adalah kasus penggunaan lain yang baik untuk Proxy. Daripada meneruskan objek target ke bagian kode yang tidak tepercaya, seseorang dapat meneruskan proxy-nya yang digabungkan dalam semacam membran pelindung. Setelah aplikasi menganggap bahwa kode tidak tepercaya telah menyelesaikan tugas tertentu, aplikasi dapat mencabut referensi yang melepaskan proxy dari targetnya. Membran akan memperluas pemisahan ini secara rekursif ke semua objek yang dapat dijangkau dari target asli yang ditentukan.

Menggunakan refleksi dengan proxy

Reflect adalah objek bawaan baru yang menyediakan metode untuk operasi JavaScript yang dapat dicegat, yang sangat berguna untuk menggunakan Proxy. Faktanya, metode Reflect sama dengan metode pengendali proxy.

Bahasa dengan jenis statis seperti Python atau C# telah lama menawarkan API refleksi, tetapi JavaScript tidak terlalu membutuhkannya karena merupakan bahasa dinamis. Kita dapat berargumen bahwa ES5 sudah memiliki cukup banyak fitur refleksi, seperti Array.isArray() atau Object.getOwnPropertyDescriptor() yang akan dianggap sebagai refleksi dalam bahasa lain. ES2015 memperkenalkan Reflection API yang akan menyimpan metode mendatang untuk kategori ini, sehingga lebih mudah untuk dipahami. Hal ini masuk akal karena Object dimaksudkan sebagai prototipe dasar, bukan bucket untuk metode refleksi.

Dengan menggunakan Reflect, kita dapat meningkatkan contoh Superhero sebelumnya untuk intersepsi kolom yang tepat pada perangkap get dan set sebagai berikut:

// Field interception with Proxy and the Reflect API

var pioneer = new Proxy({}, {
    get: function(target, name, receiver) {
        console.log(`get called for field: ${name}`);
        return Reflect.get(target, name, receiver);
    },

    set: function(target, name, value, receiver) {
        console.log(`set called for field: ${name} and value: ${value}`);
        return Reflect.set(target, name, value, receiver);
    }
});

pioneer.firstName = 'Grace';
pioneer.secondName = 'Hopper';
// Grace
pioneer.firstName

Yang menghasilkan:

set called for field: firstName and value: Grace
set called for field: secondName and value: Hopper
get called for field: firstName

Contoh lainnya adalah saat seseorang ingin:

  • Gabungkan definisi proxy di dalam konstruktor kustom untuk menghindari pembuatan proxy baru secara manual setiap kali kita ingin menggunakan logika tertentu.

  • Menambahkan kemampuan untuk 'menyimpan' perubahan, tetapi hanya jika data benar-benar telah diubah (secara hipotetis karena operasi penyimpanan sangat mahal).

function Customer() {

    var proxy = new Proxy({
    save: function(){
        if (!this.dirty){
        return console.log('Not saving, object still clean');
        }
        console.log('Trying an expensive saving operation: ', this.changedProperties);
    },

    }, {

    set: function(target, name, value, receiver) {
        target.dirty = true;
        target.changedProperties = target.changedProperties || [];

        if(target.changedProperties.indexOf(name) == -1){
        target.changedProperties.push(name);
        }
        return Reflect.set(target, name, value, receiver);
    }

    });

    return proxy;
}


var customer = new Customer();

customer.name = 'seth';
customer.surname = 'thompson';
// Trying an expensive saving operation:  ["name", "surname"]
customer.save();

Untuk contoh Reflect API lainnya, lihat Proxy ES6 oleh Tagtree.

Mengisi ulang Object.observe()

Meskipun kami mengucapkan selamat tinggal kepada Object.observe(), kini Anda dapat melakukan polyfill menggunakan Proksi ES2015. Simon Blackwell baru-baru ini menulis shim Object.observe() berbasis Proxy yang patut dicoba. Erik Arvidsson juga menulis versi yang cukup spesifikasi lengkap pada tahun 2012.

Dukungan browser

Proksi ES2015 didukung di Chrome 49, Opera, Microsoft Edge, dan Firefox. Safari memiliki sinyal publik yang beragam terhadap fitur ini, tetapi kami tetap optimis. Reflect tersedia di Chrome, Opera, dan Firefox serta sedang dalam pengembangan untuk Microsoft Edge.

Google telah merilis polyfill terbatas untuk Proxy. Ini hanya dapat digunakan untuk wrapper generik, karena hanya dapat melakukan proxy properti yang diketahui pada saat Proxy dibuat.

Bacaan lebih lanjut