Memperkenalkan proxy ES2015

Addy Osmani
Addy Osmani

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

{i>Proxy<i} memiliki banyak kegunaan, termasuk:

  • Intersep
  • Virtualisasi objek
  • Pengelolaan resource
  • Pembuatan 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 handler, yang dapat memodifikasi perilaku asli objek target dengan beberapa cara yang bermanfaat. Pengendali berisi metode trap 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 dalamnya menggunakan Proxy API. Ingat, parameter pertama yang diteruskan ke konstruktor adalah target (objek yang di-proxy-kan) dan yang 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);

Menjalankan kode di atas di Chrome 49, kita mendapatkan hal berikut:

get was called for: power  
"Flight"

Seperti yang dapat kita lihat dalam praktik, menjalankan properti get atau properti yang ditetapkan pada objek proxy dengan benar menghasilkan panggilan tingkat meta ke perangkap yang sesuai pada pengendali. Operasi pengendali mencakup pembacaan properti, penetapan properti, dan aplikasi fungsi, yang semuanya diteruskan ke perangkap yang sesuai.

Fungsi perangkap dapat, jika memilih, mengimplementasikan operasi secara arbitrer (mis., meneruskan operasi ke objek target). Inilah yang terjadi secara default jika jebakan 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 dapat dengan mudah melakukan proxy objek fungsi, jika fungsi merupakan 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 {i>proxy<i}

Identitas proxy dapat diamati menggunakan operator kesetaraan JavaScript (== dan ===). Seperti yang kita ketahui, saat diterapkan pada dua objek, operator ini akan membandingkan identitas objek. Contoh berikutnya menunjukkan perilaku ini. Membandingkan dua proxy yang berbeda akan menampilkan nilai salah (false) meskipun target yang mendasarinya 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 menempatkan 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 jebakan untuk semua operasi pada objek.

Kasus penggunaan

Seperti yang disebutkan, {i>Proxy<i} memiliki beragam kasus penggunaan. Banyak dari yang di atas, seperti kontrol akses dan pembuatan profil berada 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 objek tersebut harus berada di ruang alamat yang sama. Contohnya mencakup objek jarak jauh (yang mengemulasi objek di ruang lain) dan future transparan (mengemulasi hasil yang belum dikomputasi).

Proxy sebagai Pengendali

Kasus penggunaan yang cukup umum untuk pengendali proxy adalah melakukan pemeriksaan validasi atau kontrol akses sebelum melakukan operasi pada objek yang digabungkan. Hanya jika pemeriksaan berhasil, operasi akan diteruskan. Contoh validasi di bawah 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 mempertimbangkan semua operasi yang berbeda-beda dalam pengendali proxy. Bisa dibayangkan bahwa suatu implementasi harus menduplikasi pola pemeriksaan akses dan meneruskan operasi di setiap perangkap.

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

Ekstensi Objek

Kasus penggunaan umum lainnya untuk {i>proxy<i} adalah memperluas atau mendefinisikan ulang semantik operasi pada objek. Misalnya, Anda mungkin ingin pengendali mencatat operasi ke dalam log, memberi tahu observer, menampilkan pengecualian, bukan menampilkan yang tidak ditentukan, atau mengalihkan operasi ke target penyimpanan yang berbeda. Dalam kasus ini, menggunakan {i>proxy<i} dapat menyebabkan hasil yang sangat berbeda daripada menggunakan 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 bagus untuk {i>Proxy<i}. Daripada meneruskan objek target ke potongan kode yang tidak tepercaya, seseorang dapat meneruskan proxy-nya yang terbungkus 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 detasemen ini secara rekursif ke semua objek yang dapat dijangkau dari target asli yang ditentukan.

Menggunakan refleksi dengan {i>proxy<i}

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

Bahasa yang diketik secara statis seperti Python atau C# telah lama menawarkan API refleksi, tetapi JavaScript tidak benar-benar membutuhkannya sebagai bahasa dinamis. Orang dapat berpendapat bahwa ES5 sudah memiliki beberapa fitur refleksi, seperti Array.isArray() atau Object.getOwnPropertyDescriptor() yang akan dianggap sebagai refleksi dalam bahasa lain. ES2015 memperkenalkan Reflection API yang akan menampung metode mendatang untuk kategori ini, sehingga lebih mudah untuk dipahami. Hal ini masuk akal karena Objek dimaksudkan sebagai prototipe dasar, bukan bucket untuk metode refleksi.

Dengan menggunakan Reflect, kita dapat memperbaiki contoh Superhero sebelumnya untuk intersepsi lapangan yang tepat pada get dan mengatur jebakan 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

Output mana:

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

Contoh lain adalah ketika seseorang mungkin 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 dimodifikasi (hipotesis karena operasi simpan menjadi 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 dari Tagtree.

Objek Polyfilling.observe()

Meskipun kami berpisah dengan Object.observe(), sekarang Anda dapat mem-polyfill permintaan tersebut menggunakan Proxy ES2015. Simon Blackwell menulis shim berbasis Proxy Object.observe() baru-baru ini yang layak untuk Anda periksa. Erik Arvidsson juga menulis versi yang cukup spesifikasi lengkap sejak tahun 2012.

Dukungan browser

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

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

Bacaan lebih lanjut