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

एडी ओस्मानी
एडी ओस्मानी

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

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

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

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

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

प्रॉक्सी के व्यवहार को हैंडलर से कंट्रोल किया जाता है. यह हैंडलर target ऑब्जेक्ट के मूल व्यवहार को कई तरीकों से बदल सकता है. जब प्रॉक्सी पर कार्रवाई की जाती है, तो हैंडलर में वैकल्पिक ट्रैप तरीके (जैसे, .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"

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

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

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

आम तौर पर, आपको प्रॉक्सी को बिना प्रॉक्सी वाले ऑब्जेक्ट से अलग नहीं करना चाहिए, ताकि प्रॉक्सी की जगह तय करने से आपके ऐप्लिकेशन के नतीजे पर कोई असर न पड़े. इस वजह से, प्रॉक्सी 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 प्रॉक्सी देखें.

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

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

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

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

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

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