पेश है ES2015 प्रॉक्सी

ES2015 प्रॉक्सी (Chrome 49 और उसके बाद के वर्शन में), JavaScript को इंटरसेप्शन एपीआई उपलब्ध कराते हैं. इससे, हम किसी टारगेट ऑब्जेक्ट पर किए जाने वाले सभी ऑपरेशन को ट्रैप या इंटरसेप्ट कर सकते हैं. साथ ही, इस टारगेट के काम करने के तरीके में बदलाव कर सकते हैं.

प्रॉक्सी का इस्तेमाल कई कामों के लिए किया जा सकता है. जैसे:

  • इंटरसेप्शन
  • ऑब्जेक्ट वर्चुअलाइज़ेशन
  • संसाधन प्रबंधन
  • डीबग करने के लिए प्रोफ़ाइलिंग या लॉगिंग
  • सुरक्षा और ऐक्सेस कंट्रोल
  • ऑब्जेक्ट के इस्तेमाल के लिए समझौते

Proxy API में एक Proxy constructor होता है, जो तय किए गए टारगेट ऑब्जेक्ट और हैंडलर ऑब्जेक्ट को लेता है.

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

प्रॉक्सी के व्यवहार को हैंडलर कंट्रोल करता है. यह टारगेट ऑब्जेक्ट के मूल व्यवहार में कई काम के तरीकों से बदलाव कर सकता है. हैंडलर में ट्रैप करने के वैकल्पिक तरीके (जैसे, .get(), .set(), .apply()) होते हैं.इन्हें तब कॉल किया जाता है, जब प्रॉक्सी पर उससे जुड़ी कार्रवाई की जाती है.

इंटरसेप्शन

आइए, एक साधारण ऑब्जेक्ट लेकर शुरू करें और Proxy API का इस्तेमाल करके, उसमें कुछ इंटरसेप्शन मिडलवेयर जोड़ें. याद रखें, कन्स्ट्रक्टर में पास किया गया पहला पैरामीटर टारगेट (प्रॉक्सी किया जा रहा ऑब्जेक्ट) होता है और दूसरा हैंडलर (प्रॉक्सी) होता है. यहां हम अपने गेट्टर, सेटर या अन्य व्यवहार के लिए हुक जोड़ सकते हैं.

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 में ऊपर दिया गया कोड चलाने पर, हमें यह नतीजा मिलता है:

get was called for: power  
"Flight"

जैसा कि हम प्रैक्टिस में देख सकते हैं, प्रॉक्सी ऑब्जेक्ट पर प्रॉपर्टी का इस्तेमाल करने या प्रॉपर्टी सेट करने की सही प्रोसेस के बाद, हैंडलर पर उससे जुड़े ट्रैप को मेटा-लेवल कॉल किया जाता है. हैंडलर ऑपरेशन में प्रॉपर्टी पढ़ना, प्रॉपर्टी असाइन करना, और फ़ंक्शन लागू करना शामिल है. ये सभी ऑपरेशन, उससे जुड़े ट्रैप पर फ़ॉरवर्ड किए जाते हैं.

ट्रैप फ़ंक्शन, अपनी पसंद के मुताबिक कोई कार्रवाई लागू कर सकता है.जैसे, कार्रवाई को टारगेट ऑब्जेक्ट पर फ़ॉरवर्ड करना. अगर कोई ट्रैप तय नहीं किया जाता है, तो डिफ़ॉल्ट रूप से यही होता है. उदाहरण के लिए, यहां एक नो-ऑपरेशन फ़ॉरवर्डिंग प्रॉक्सी है, जो सिर्फ़ यही काम करती है:

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

हमने सिर्फ़ सामान्य ऑब्जेक्ट को प्रॉक्सी करने के बारे में देखा है. हालांकि, हम आसानी से किसी फ़ंक्शन ऑब्जेक्ट को भी प्रॉक्सी कर सकते हैं, जहां फ़ंक्शन हमारा टारगेट है. इस बार हम 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

प्रॉक्सी की पहचान करना

JavaScript के बराबरी वाले ऑपरेटर (== और ===) का इस्तेमाल करके, किसी प्रॉक्सी की पहचान की जा सकती है. जैसा कि हम जानते हैं कि दो ऑब्जेक्ट पर लागू होने पर, ये ऑपरेटर ऑब्जेक्ट की पहचान की तुलना करते हैं. अगले उदाहरण में इस व्यवहार के बारे में बताया गया है. दो अलग-अलग प्रॉक्सी की तुलना करने पर, गलत नतीजा मिलता है. भले ही, दोनों प्रॉक्सी के टारगेट एक जैसे हों. इसी तरह, टारगेट ऑब्जेक्ट अपने किसी भी प्रॉक्सी से अलग होता है:

// Continuing previous example

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

आम तौर पर, आपको किसी प्रॉक्सी ऑब्जेक्ट और नॉन-प्रॉक्सी ऑब्जेक्ट में अंतर नहीं करना चाहिए, ताकि प्रॉक्सी का इस्तेमाल करने से आपके ऐप्लिकेशन के नतीजे पर असर न पड़े. इस वजह से, Proxy API में यह जांचने का कोई तरीका शामिल नहीं है कि कोई ऑब्जेक्ट प्रॉक्सी है या नहीं. साथ ही, यह ऑब्जेक्ट पर किए जाने वाले सभी ऑपरेशन के लिए ट्रैप भी उपलब्ध नहीं कराता.

उपयोग के उदाहरण

जैसा कि बताया गया है, प्रॉक्सी का इस्तेमाल कई तरह से किया जा सकता है. ऊपर बताए गए कई विकल्प, सामान्य रैपर के दायरे में आते हैं. जैसे, ऐक्सेस कंट्रोल और प्रोफ़ाइलिंग. ये ऐसे प्रॉक्सी होते हैं जो दूसरे ऑब्जेक्ट को एक ही पते "स्पेस" में रैप करते हैं. वर्चुअलाइज़ेशन का भी ज़िक्र किया गया था. वर्चुअल ऑब्जेक्ट, ऐसे प्रॉक्सी होते हैं जो अन्य ऑब्जेक्ट की नकल करते हैं. इसके लिए, ज़रूरी नहीं है कि वे ऑब्जेक्ट एक ही पते के स्पेस में हों. उदाहरण के लिए, रिमोट ऑब्जेक्ट (जो अन्य स्पेस में मौजूद ऑब्जेक्ट को एमुलेट करते हैं) और ट्रांसफ़र के बाद दिखने वाले नतीजे (जिन नतीजों का हिसाब अभी तक नहीं लगाया गया है).

हैंडलर के तौर पर प्रॉक्सी

प्रॉक्सी हैंडलर के लिए, रैप किए गए ऑब्जेक्ट पर कोई कार्रवाई करने से पहले, पुष्टि करने या ऐक्सेस कंट्रोल की जांच करने का इस्तेमाल बहुत आम है. जांच पूरी होने के बाद ही कार्रवाई को आगे बढ़ाया जाता है. पुष्टि करने के इस उदाहरण में यह दिखाया गया है:

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

इस पैटर्न के ज़्यादा जटिल उदाहरणों में, उन सभी अलग-अलग ऑपरेशन को ध्यान में रखा जा सकता है जिन्हें प्रॉक्सी हैंडलर इंटरसेप्ट कर सकते हैं. किसी को भी यह कल्पना हो सकती है कि लागू करने के लिए, ऐक्सेस की जांच करने के पैटर्न को डुप्लीकेट करना होगा और हर ट्रैप में ऑपरेशन को फ़ॉरवर्ड करना होगा.

इसे आसानी से समझना मुश्किल हो सकता है, क्योंकि हर ऑपरेशन को अलग-अलग फ़ॉरवर्ड करना पड़ सकता है. अगर सभी ऑपरेशन को एक ही ट्रैप के ज़रिए एक जैसा किया जा सकता है, तो हैंडलर को सिर्फ़ एक ट्रैप में पुष्टि की जांच करनी होगी. ऐसा करने के लिए, प्रॉक्सी हैंडलर को प्रॉक्सी के तौर पर लागू करें. माफ़ करें, इस लेख में इस बारे में नहीं बताया गया है.

ऑब्जेक्ट एक्सटेंशन

प्रॉक्सी के इस्तेमाल का एक और सामान्य उदाहरण, ऑब्जेक्ट पर किए जाने वाले ऑपरेशन के सेमेटिक्स को बढ़ाना या फिर से तय करना है. उदाहरण के लिए, हो सकता है कि आपको किसी हैंडलर से ऑपरेशन को लॉग करने, ऑब्ज़र्वर को सूचना देने, 'तय नहीं किया गया' दिखाने के बजाय अपवाद दिखाने या स्टोरेज के लिए ऑपरेशन को अलग-अलग टारगेट पर रीडायरेक्ट करने की ज़रूरत हो. इन मामलों में, प्रॉक्सी का इस्तेमाल करने से टारगेट ऑब्जेक्ट का इस्तेमाल करने के मुकाबले बहुत अलग नतीजा मिल सकता है.

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

ऐक्सेस कंट्रोल

ऐक्सेस कंट्रोल, प्रॉक्सी का इस्तेमाल करने का एक और अच्छा उदाहरण है. किसी भरोसेमंद कोड को टारगेट ऑब्जेक्ट के तौर पर पास करने के बजाय, उसकी प्रोक्सी को किसी तरह के सुरक्षा कवच में लपेटकर पास किया जा सकता है. जब ऐप्लिकेशन को लगता है कि अविश्वसनीय कोड ने कोई खास टास्क पूरा कर लिया है, तो वह रेफ़रंस रद्द कर सकता है. इससे प्रॉक्सी, अपने टारगेट से अलग हो जाती है. मेम्ब्रेन, इस अलगाव को उन सभी ऑब्जेक्ट तक बार-बार बढ़ाएगा जो तय किए गए ओरिजनल टारगेट से ऐक्सेस किए जा सकते हैं.

प्रॉक्सी के साथ रिफ़्लेक्शन का इस्तेमाल करना

Reflect एक नया बिल्ट-इन ऑब्जेक्ट है. यह इंटरसेप्ट किए जा सकने वाले JavaScript ऑपरेशन के लिए तरीके उपलब्ध कराता है. यह प्रॉक्सी के साथ काम करने के लिए काफ़ी मददगार है. असल में, रिफ़्लेक्ट के तरीके, प्रॉक्सी हैंडलर के तरीके जैसे ही होते हैं.

Python या C# जैसी स्टैटिक टाइप वाली भाषाओं में, रिफ़्लेक्शन एपीआई का इस्तेमाल लंबे समय से किया जा रहा है. हालांकि, JavaScript एक डाइनैमिक भाषा है और उसे इसकी ज़रूरत नहीं है. यह कहा जा सकता है कि ES5 में पहले से ही रिफ़्लेक्शन की कई सुविधाएं मौजूद हैं, जैसे कि Array.isArray() या Object.getOwnPropertyDescriptor(). इन्हें दूसरी भाषाओं में रिफ़्लेक्शन माना जाएगा. ES2015 में Reflection API को शामिल किया गया है. इसमें इस कैटगरी के लिए आने वाले समय में इस्तेमाल होने वाले तरीके शामिल होंगे. इससे इन तरीकों को समझना आसान हो जाएगा. ऐसा इसलिए है, क्योंकि ऑब्जेक्ट को रिफ़्लेक्शन के तरीकों के लिए बकेट के बजाय, बेस प्रोटोटाइप के तौर पर इस्तेमाल किया जाता है.

Reflect का इस्तेमाल करके, हम अपने पिछले सुपरहीरो उदाहरण को बेहतर बना सकते हैं. इससे, 'पाना' और 'सेट करें' ट्रैप पर फ़ील्ड इंटरसेप्शन सही तरीके से हो पाएगा. इसके लिए, यह तरीका अपनाएं:

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

ये आउटपुट:

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

एक और उदाहरण के लिए, जब कोई व्यक्ति:

  • कस्टम कन्स्ट्रक्टर के अंदर प्रॉक्सी डेफ़िनिशन को रैप करें, ताकि हर बार किसी खास लॉजिक के साथ काम करने के लिए, मैन्युअल रूप से नई प्रॉक्सी बनाने से बचा जा सके.

  • बदलावों को 'सेव' करने की सुविधा जोड़ें. हालांकि, ऐसा सिर्फ़ तब करें, जब डेटा में असल में बदलाव किया गया हो. ऐसा इसलिए, क्योंकि सेव करने की प्रोसेस बहुत महंगी होती है.

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

Reflect API के ज़्यादा उदाहरणों के लिए, Tagtree के ES6 Proxies देखें.

Object.observe() को पॉलीफ़िल करना

हम Object.observe() को अलविदा कह रहे हैं. हालांकि, अब ES2015 प्रॉक्सी का इस्तेमाल करके, उन्हें पॉलीफ़िल किया जा सकता है. साइमन ब्लैकवेल ने हाल ही में, प्रॉक्सी पर आधारित Object.observe() शिम लिखा है. इसे देखना न भूलें. एरिक अर्विडसन ने साल 2012 में भी पूरी जानकारी वाला एक वर्शन लिखा था.

ब्राउज़र समर्थन

ES2015 प्रोक्सी, Chrome 49, Opera, Microsoft Edge, और Firefox पर काम करती हैं. Safari के लिए, इस सुविधा के बारे में सार्वजनिक तौर पर मिले सिग्नल अलग-अलग रहे हैं. हालांकि, हम उम्मीद बनाए हुए हैं. फ़िलहाल, Reflect Chrome, Opera, और Firefox के लिए उपलब्ध है. इसे Microsoft Edge के लिए डेवलप किया जा रहा है.

Google ने प्रॉक्सी के लिए सीमित polyfill रिलीज़ किया है. इसका इस्तेमाल सिर्फ़ सामान्य रैपर के लिए किया जा सकता है, क्योंकि यह सिर्फ़ उन प्रॉपर्टी को प्रॉक्सी कर सकता है जो प्रॉक्सी बनाने के समय मौजूद हों.

इसके बारे में और पढ़ें