ES2015 proxy'leri (Chrome 49 ve sonraki sürümlerde), JavaScript'e bir müdahale API'si sağlar. Bu API, bir hedef nesnenin tüm işlemlerini yakalamamıza veya durdurmamıza ve bu hedefin çalışma şeklini değiştirmemize olanak tanır.
Proxy'lerin çok sayıda kullanım alanı 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ıyla ilgili sözleşmeler
Proxy API, belirli bir hedef nesnesi ve işleyici nesnesi alan bir Proxy oluşturucu içerir.
var target = { /* some properties */ };
var handler = { /* trap functions */ };
var proxy = new Proxy(target, handler);
Bir proxy'nin davranışı, hedef nesnesinin orijinal davranışını oldukça faydalı şekillerde değiştirebilen işleyici tarafından kontrol edilir. İşleyici, proxy'de ilgili işlem gerçekleştirildiğinde çağrılan isteğe bağlı tetikleyici yöntemleri (ör..get()
, .set()
, .apply()
) içerir.
Pas Arası
Öncelikle, Proxy API'yi kullanarak basit bir nesne alıp bu nesneye bazı müdahale aracıları ekleyelim. Oluşturucuya iletilen ilk parametrenin hedef (proxy'nin kullanıldığı nesne) ve ikinci parametrenin işleyici (proxy'nin kendisi) olduğunu unutmayın. Burada, alıcılarımız, ayarlayıcılarımız veya diğer davranışlarımız için kanca 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);
Yukarıdaki kodu Chrome 49'da çalıştırdığımızda aşağıdaki sonucu elde ederiz:
get was called for: power
"Flight"
Uygulamada görebileceğimiz gibi, proxy nesnesinde mülk alma veya mülk ayarlama işlemimizi doğru şekilde gerçekleştirmek, işleyicideki ilgili tuzağa meta düzeyinde bir çağrı yapılmasına neden oldu. İşleyici işlemleri, mülk okuma, mülk atama ve işlev uygulama işlemlerini içerir. Bunların tümü ilgili tuzağa yönlendirilir.
Tuzak işlevi, isterse bir işlemi keyfi olarak uygulayabilir (ör.işlemi hedef nesneye yönlendirebilir). Bir tuzak belirtilmezse varsayılan olarak bu olur. Örneğin, aşağıdaki tam olarak bunu yapan işlemsiz bir yönlendirme proxy'sidir:
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);
Basit nesnelerin proxy'sini oluşturmayı inceledik, ancak hedefimizin bir işlev olduğu bir işlev nesnesine de proxy oluşturabiliriz. Bu kez 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
JavaScript eşitlik operatörleri (==
ve ===
) kullanılarak bir proxy'nin kimliği gözlemlenebilir. Bu operatörlerin iki nesneye uygulandığında nesne kimliklerini karşılaştırdığını biliyoruz. Aşağıdaki örnekte bu davranış gösterilmektedir. Temel hedefler aynı olsa bile iki farklı proxy'nin karşılaştırılması yanlış değerini döndürür. Benzer şekilde, hedef nesne, proxy'lerinden 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, bir proxy'nin kullanılması uygulamanızın sonucunu etkilemez. Proxy API'nin bir nesnenin proxy olup olmadığını kontrol etmenin bir yolunu içermemesi ve nesnelerdeki tüm işlemler için tuzak sağlamamasının bir nedeni de budur.
Kullanım alanları
Daha önce de belirtildiği gibi, proxy'lerin çok çeşitli kullanım alanları vardır. Erişim kontrolü ve profil oluşturma gibi yukarıdakilerin çoğu Genel sarmalayıcılar kapsamındadır: Diğer nesneleri aynı adres "bölgesine" sarmalayan proxy'ler. Sanallaştırmadan da bahsedildi. Sanal nesneler, diğer nesnelerin aynı adres alanında olması gerekmeden bu nesneleri taklit eden proxy'lerdir. Örneğin, uzak nesneler (diğer alanlardaki nesneleri taklit eden) ve şeffaf gelecekler (henüz hesaplanmamış sonuçları taklit eden) bu tür nesnelere örnek olarak verilebilir.
İşleyici olarak proxy'ler
Proxy işleyicilerin yaygın kullanım alanlarından biri, sarmalanmış bir nesnede işlem yapmadan önce doğrulama veya erişim denetimi kontrolleri gerçekleştirmektir. İşlem yalnızca kontrol başarılı olursa yönlendirilir. Aşağıdaki doğrulama örneği bunu göstermektedir:
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 örnekleri, proxy işleyicilerin müdahale edebileceği tüm farklı işlemleri hesaba katabilir. Erişim kontrolünün kalıbını kopyalamalı ve her tuzakta işlemi yönlendirmesi gereken bir uygulama hayal edilebilir.
Her işlemin farklı şekilde yönlendirilmesi gerekebileceğinden, bu işlemi kolayca soyutlamak zor olabilir. Mükemmel bir senaryoda, tüm işlemler tek bir tuzak üzerinden tek tip olarak yönlendirilebilirse işleyicinin doğrulama kontrolünü tek bir tuzakta yalnızca bir kez yapması gerekir. Bunu, proxy işleyicinin kendisini proxy olarak uygulayarak yapabilirsiniz. Maalesef bu konu bu makalenin kapsamı dışındadır.
Nesne Uzantısı
Vekil sunucuların yaygın bir diğer kullanım alanı, nesnelerdeki işlemlerin anlamını genişletmek veya yeniden tanımlamaktır. Örneğin, bir işleyicinin işlemleri günlüğe kaydetmesini, gözlemcileri bilgilendirmesini, undefined 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 Denetimi
Erişim denetimi, proxy'lerin bir başka iyi kullanım alanıdır. Bir hedef nesneyi güvenilmeyen bir kod parçasına iletmek yerine, proxy'sini bir tür koruyucu zarfa sararak iletebilirsiniz. Uygulama, güvenilmeyen kodun belirli bir görevi tamamladığını belirlediğinde, proxy'yi hedefinden ayıran referansı iptal edebilir. Membran, bu ayırmayı, tanımlanan orijinal hedeften erişilebilen tüm nesnelere yinelemeli olarak genişletir.
Yansıtmayı proxy'lerle kullanma
Reflect, 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. Aslında Reflect yöntemleri, proxy işleyiciler ile aynıdır.
Python veya C# gibi statik olarak yazılmış diller uzun zamandır bir yansıma API'si sunmaktadır. Ancak JavaScript, dinamik bir dil olduğu için buna gerçekten ihtiyaç duymamıştır. ES5'te, diğer dillerde yansıma olarak kabul edilecek Array.isArray()
veya Object.getOwnPropertyDescriptor()
gibi oldukça fazla yansıma özelliğinin zaten bulunduğu söylenebilir. ES2015, bu kategoriye yönelik gelecekteki yöntemleri barındıracak ve bu yöntemlerin daha kolay anlaşılmasını sağlayacak bir Yansıtma API'si sunar. Object, yansıma yöntemleri için bir kap yerine temel bir prototip olarak tasarlandığından bu durum mantıklıdır.
Reflect'i kullanarak, get ve set tuzaklarımızda doğru alan müdahalesi için önceki Süper Kahraman örneğimizi aşağıdaki gibi 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 çıkışlar:
set called for field: firstName and value: Grace
set called for field: secondName and value: Hopper
get called for field: firstName
Başka bir örnek de şudur:
Belirli bir mantıkla çalışmak istediğimizde her seferinde manuel olarak yeni bir proxy oluşturmak zorunda kalmamak için proxy tanımını özel bir kurucu içine sarmalayın.
Değişiklikleri "kaydetme" özelliğini ekleyin ancak yalnızca veriler gerçekten değiştirilmişse (kaydetme işleminin çok pahalı olması nedeniyle varsayımsal olarak) bunu yapın.
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 Reflect API örneği için Tagtree'nin ES6 Proxies sayfasına bakın.
Object.observe() işlevini doldurma
Object.observe()
'ye elveda demiş olsak da artık ES2015 proxy'lerini kullanarak bu öğeleri çoklu dolgu ile doldurabilirsiniz. Simon Blackwell kısa süre önce Proxy tabanlı bir Object.observe() shim yazdı. Bu makaleye göz atmanızı öneririz. Erik Arvidsson da 2012'de oldukça tam özellikli bir sürüm yazdı.
Tarayıcı desteği
ES2015 proxy'leri Chrome 49, Opera, Microsoft Edge ve Firefox'ta desteklenir. Safari'de bu özellikle ilgili olarak herkese açık olarak paylaşılan sinyaller karmaşık olsa da iyimser olmaya devam ediyoruz. Reflect, Chrome, Opera ve Firefox'ta kullanılabilir. Microsoft Edge için geliştirilme aşamasındadır.
Google, Proxy için sınırlı bir polyfill yayınladı. Yalnızca proxy oluşturulduğu sırada bilinen mülkleri proxy olarak kullanabildiğinden bu yöntem yalnızca genel sarmalayıcılar için kullanılabilir.
Daha fazla bilgi
- ES6'te proxy'ler ve Reflect
- MDN: ES6 Proxies
- ES6 Proxy'leri ve TagTree'de Yansıtma
- MDN: Yansıtma Nesnesi
- ES6 Yansıtma hakkında ayrıntılı bilgi
- Proxy'ler: Nesne Yönelimli Güçlü Ara API'ler İçin Tasarım İlkeleri
- 2ality: Metaprogramming with ES6
- Reflect'i kullanarak ES6'da meta programlama
- ES6 everyday Reflect