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

Addy Osmani
Addy Osmani

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

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

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

प्रॉक्सी एपीआई में एक प्रॉक्सी कंस्ट्रक्टर होता है, जो तय किए गए टारगेट ऑब्जेक्ट और हैंडलर ऑब्जेक्ट को लेता है.

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

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

इंटरसेप्शन

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

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

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

इस्तेमाल के उदाहरण

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

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

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

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 ऑपरेशन के लिए तरीके उपलब्ध कराता है. यह Proxies के साथ काम करने के लिए बहुत काम का होता है. असल में, Reflect के तरीके प्रॉक्सी हैंडलर की तरह होते हैं.

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 प्रॉक्सी देखें.

पॉलीफ़िलिंग Object.observe()

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

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

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

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

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