ES2015 proxy'leriyle tanışın

Addy Osmani
Addy Osmani

ES2015 Proxy'leri (Chrome 49 ve sonraki sürümlerinde) JavaScript'e bir müdahale API'si sağlayarak hedef nesnedeki tüm işlemleri yakalamamıza veya müdahale etmemize ve bu hedefin çalışma şeklini değiştirmemize olanak tanır.

Proxy'lerin çok sayıda kullanımı vardır. Örneğin:

  • Pas Arası
  • Nesne sanallaştırma
  • Kaynak yönetimi
  • Hata ayıklama için profil oluşturma veya günlük kaydı
  • Güvenlik ve erişim denetimi
  • Nesne kullanımı sözleşmeleri

Proxy API, tanımlanmış bir hedef nesne ve bir işleyici nesnesi alan bir Proxy oluşturucu içerir.

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

Proxy'nin davranışı, işleyici tarafından kontrol edilir ve bu da hedef nesnesinin orijinal davranışını birçok kullanışlı şekilde değiştirebilir. İşleyici, proxy'de ilgili işlem gerçekleştirilirken çağrılan isteğe bağlı trap yöntemleri (ör..get(), .set(), .apply()) içerir.

Pas Arası

Düz bir nesne alıp Proxy API'yi kullanarak buna bir müdahale ara katman yazılımı ekleyerek başlayalım. Oluşturucuya iletilen ilk parametrenin hedef (proxy'si uygulanan nesne), ikinci parametrenin de işleyici (proxy'nin kendisi) olduğunu unutmayın. Oyuncularımız, set bırakanlarımız veya diğer davranışlarımız için kancalar ekleyebiliriz.

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);

Chrome 49'da yukarıdaki kodu çalıştırdığımızda şunu elde ederiz:

get was called for: power  
"Flight"

Uygulamada görebileceğiniz gibi, "get" veya "özellik ayarı proxy nesnesinde" doğru bir şekilde gerçekleştirildiğinde, işleyicide karşılık gelen tuzağa meta düzeyde bir çağrı yapılır. İşleyici işlemleri, özellik okumaları, özellik atama ve işlev uygulamasını içerir. Bunların tümü ilgili tuzağa yönlendirilir.

Tuzak işlevi, isterse rastgele bir işlem uygulayabilir (ör.işlemi hedef nesneye yönlendirebilir). Bir tuzak belirlenmezse varsayılan olarak bu durum gerçekleşir. Örneğin, aşağıda yalnızca bunu yapan bir işlemsiz yönlendirme proxy'si verilmiştir:

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);

Düz nesneler için proxy kullanmayı inceledik, ancak bir işlevin hedefimiz olduğu bir işlev nesnesine proxy uygulamak kadar kolay bir işlem yapabiliriz. Bu sefer handler.apply() tuzağını kullanacağız:

// 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

Proxy'leri tanımlama

Proxy kimliği, JavaScript eşitlik operatörleri (== ve ===) kullanılarak gözlemlenebilir. Bildiğimiz gibi, iki nesneye uygulandığında bu operatörler nesne kimliklerini karşılaştırır. Bir sonraki örnek bu davranışı göstermektedir. Temel hedefler aynı olsa da iki farklı proxy'nin karşılaştırılması "false" değerini döndürür. Benzer şekilde, hedef nesne, proxy'lerinin herhangi birinden farklıdır:

// Continuing previous example

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

İdeal olarak, bir proxy'yi proxy olmayan bir nesneden ayırt edememeniz gerekir. Böylece, proxy yerleştirmek uygulamanızın sonucunu gerçekten etkilemez. Proxy API'nin bir nesnenin proxy olup olmadığını kontrol etmenin bir yolunu sunmamasının ve nesneler üzerindeki tüm işlemler için tuzaklar sunmamasının bir nedeni budur.

Kullanım alanları

Daha önce de belirtildiği gibi, proxy'lerin çeşitli kullanım alanları vardır. Erişim denetimi ve profil oluşturma gibi yukarıdakilerin birçoğu, Genel sarmalayıcılar bölümünde yer alır: Aynı adresteki "boşlukta" diğer nesneleri sarmalayan proxy'ler. Sanallaştırmadan da bahsedildi. Sanal nesneler, diğer nesnelerin aynı adres alanında olmalarına gerek kalmadan diğer nesneleri emüle eden proxy'lerdir. Örnekler arasında uzak nesneler (diğer alanlardaki nesneleri emüle eden) ve şeffaf gelecekler (henüz hesaplanmamış sonuçlar emülasyonu) sayılabilir.

İşleyici olarak proxy'ler

Proxy işleyicilerin oldukça yaygın bir kullanım alanı, sarmalanmış bir nesne üzerinde işlem gerçekleştirmeden önce doğrulama veya erişim denetimi kontrolleri yapmaktır. İşlem ancak kontrol başarılı olursa yönlendirilir. Aşağıdaki doğrulama örneği bunu gösterir:

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

Bu kalıbın daha karmaşık örneklerinde, proxy işleyicilerin müdahale edebileceği tüm farklı işlemler göz önünde bulundurulabilir. Bir uygulamanın, erişim kontrolü kalıbını kopyalamak ve her tuzakta işlemi yönlendirmesi gerektiğini düşünebilirsiniz.

Her işlemin farklı şekilde yönlendirilmesi gerekebileceği için bunu kolayca soyutlamak zor olabilir. Mükemmel bir senaryoda, tüm işlemler tek bir tuzak üzerinden eşit şekilde dönüşüm hunisine dönüştürülebiliyorsa işleyicinin doğrulama denetimini tek tuzakta yalnızca bir kez gerçekleştirmesi gerekir. Bunu, proxy işleyicinin kendisini bir proxy olarak uygulayarak yapabilirsiniz. Bu, maalesef bu makalenin kapsamı dışındadır.

Nesne Uzantısı

Proxy'ler için bir başka yaygın kullanım alanı da nesneler üzerindeki işlemlerin anlamını genişletmek veya yeniden tanımlamaktır. Örneğin, bir işleyicinin işlemleri günlüğe kaydetmesini, gözlemcilere bildirmesini, tanımlanmamış döndürmek yerine istisnalar atmasını veya işlemleri depolama için farklı hedeflere yönlendirmesini isteyebilirsiniz. Bu durumlarda, proxy kullanmak hedef nesneyi kullanmaktan çok farklı bir sonuca yol açabilir.

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

Erişim Kontrolü

Proxy'ler için bir başka iyi kullanım alanı da erişim denetimidir. Hedef nesneyi güvenilmeyen bir kod parçasına iletmek yerine, proxy'sini bir tür koruyucu zara sarmalanmış şekilde iletebilirsiniz. Uygulama, güvenilmeyen kodun belirli bir görevi tamamladığını tespit ettikten sonra, proxy'yi hedefinden ayıran referansı iptal edebilir. Membran, bu ayırma işlemini yinelemeli bir şekilde, tanımlanan orijinal hedeften erişilebilen tüm nesnelere genişletir.

Proxy'lerle yansıma kullanma

Reffact, proxy'lerle çalışmak için çok yararlı olan, müdahale edilebilir JavaScript işlemleri için yöntemler sağlayan yeni bir yerleşik nesnedir. Reffact yöntemleri, proxy işleyicilerin yöntemleriyle aynıdır.

Python veya C# gibi statik olarak yazılan diller uzun zamandır bir yansıma API'si sunuyordu, ancak JavaScript'in dinamik bir dil olmasına gerçekten ihtiyacı yoktu. ES5'in Array.isArray() veya Object.getOwnPropertyDescriptor() gibi diğer dillerde yansıma olarak değerlendirilebilecek birçok yansıma özelliğine sahip olduğu söylenebilir. ES2015, bu kategori için gelecekte yararlanılacak yöntemleri barındıracak ve akıl yürütmeyi kolaylaştıracak bir Reffaction API'yi kullanıma sunar. Bu, Object'in düşünme yöntemlerine yönelik bir kova yerine temel bir prototip olması nedeniyle mantıklıdır.

Yansıtma'yı kullanarak önceki Süper Kahraman örneğimizi kullanarak, tuzakları yakalayıp harekete geçerken topladığımız sahayı doğru şekilde müdahale edebilmek için aşağıdaki noktaları iyileştirebiliriz:

// 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

Hangi çıktılar?

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

Diğer bir örnek de şudur:

  • Belirli bir mantıkla çalışmak istediğimiz her defasında yeni bir proxy'yi manuel olarak oluşturmaktan kaçınmak için bir proxy tanımını özel oluşturucu içine sarmalayın.

  • Değişiklikleri "kaydetme" özelliğini ekleyin ancak bunu yalnızca veriler gerçekten değiştirilmişse (kayıt işleminin çok pahalı olması nedeniyle olabilir) ekleyin.

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();

Daha fazla Reffact API örneği için Tagtree'nin ES6 Proxy'leri sayfasına bakın.

Polyfilling Object.observe()

Object.observe() ürününe güle güle desek de ES2015 Proxy'leri kullanarak bunları çoklu doldurma işlemi gerçekleştirilebilir. Simon Blackwell yakın zamanda göz atmanız gereken bir Proxy tabanlı Object.observe() shim yazdı. Erik Arvidsson, 2012'de oldukça eksiksiz bir spesifikasyon sürümü de yazdı.

Tarayıcı desteği

ES2015 Proxy'leri Chrome 49, Opera, Microsoft Edge ve Firefox'ta desteklenir. Safari, bu özellikle ilgili herkese açık ve karışık sinyaller almış olsa da iyimser olmaya devam ediyoruz. Refctor; Chrome, Opera ve Firefox'ta kullanılabilir ve Microsoft Edge için geliştirilme aşamasındadır.

Google, Proxy için sınırlı çoklu dolgu yayınladı. Bu, yalnızca bir Proxy oluşturulduğu sırada bilinen özellikleri proxy oluşturabildiği için yalnızca genel sarmalayıcılar için kullanılabilir.

Daha fazla bilgi