क्या जावास्क्रिप्ट में गतिशील गेटर्स / सेटरर्स को लागू करना संभव है?


132

मुझे इस बात की जानकारी है कि ऐसे गुणों के लिए गेटर्स और सेटर कैसे बनाए जाते हैं जिनके नाम पहले से ही जानते हैं, कुछ इस तरह से करके:

// A trivial example:
function MyObject(val){
    this.count = 0;
    this.value = val;
}
MyObject.prototype = {
    get value(){
        return this.count < 2 ? "Go away" : this._value;
    },
    set value(val){
        this._value = val + (++this.count);
    }
};
var a = new MyObject('foo');

alert(a.value); // --> "Go away"
a.value = 'bar';
alert(a.value); // --> "bar2"

अब, मेरा प्रश्न यह है कि क्या इस तरह के कैच-सभी गेटर्स और सेटर्स को परिभाषित करना संभव है? यानी, किसी भी संपत्ति के नाम के लिए गेटर्स और सेटर बनाते हैं जो पहले से परिभाषित नहीं है।

PHP में जादू __get()और __set()जादू के तरीकों का उपयोग करना संभव है ( इन पर जानकारी के लिए PHP प्रलेखन देखें), इसलिए मैं वास्तव में पूछ रहा हूं कि क्या इन के बराबर एक जावास्क्रिप्ट है?

कहने की जरूरत नहीं है, मैं आदर्श रूप से एक समाधान की तरह हूं जो क्रॉस-ब्राउज़र संगत है।


मैं ऐसा करने में कामयाब रहा, मेरा जवाब यहां देखें कैसे।

जवाबों:


216

2013 और 2015 अपडेट (2011 से मूल उत्तर के लिए नीचे देखें) :

यह ES2015 (उर्फ "ES6") विनिर्देश के रूप में बदल गया: जावास्क्रिप्ट में अब प्रॉक्सी है । प्रॉक्सी आपको ऐसी ऑब्जेक्ट्स बनाने देता है जो अन्य ऑब्जेक्ट्स के लिए (परदे के पीछे) वास्तविक प्रॉक्सी हैं। यहां एक सरल उदाहरण दिया गया है जो किसी भी संपत्ति मूल्यों को बदल देता है जो रिट्रीवल पर सभी कैप के लिए हैं:

"use strict";
if (typeof Proxy == "undefined") {
    throw new Error("This browser doesn't support Proxy");
}
let original = {
    "foo": "bar"
};
let proxy = new Proxy(original, {
    get(target, name, receiver) {
        let rv = Reflect.get(target, name, receiver);
        if (typeof rv === "string") {
            rv = rv.toUpperCase();
        }
        return rv;
      }
});
console.log(`original.foo = ${original.foo}`); // "original.foo = bar"
console.log(`proxy.foo = ${proxy.foo}`);       // "proxy.foo = BAR"

आपके द्वारा ओवरराइड किए गए ऑपरेशनों में उनका डिफ़ॉल्ट व्यवहार नहीं है। उपरोक्त सभी में, हम ओवरराइड करते हैं get, लेकिन उन ऑपरेशनों की पूरी सूची है जिन्हें आप हुक कर सकते हैं।

में getहैंडलर समारोह के तर्कों की सूची:

  • target( originalहमारे मामले में) अनुमानित है ।
  • name (निश्चित रूप से) संपत्ति का नाम फिर से प्राप्त किया जा रहा है, जो आमतौर पर एक स्ट्रिंग है लेकिन एक प्रतीक भी हो सकता है।
  • receiverवह वस्तु है जिसका उपयोग thisगेटर फ़ंक्शन के रूप में किया जाना चाहिए यदि संपत्ति डेटा संपत्ति के बजाय एक एक्सेसर है। सामान्य स्थिति में यह छद्म या ऐसा कुछ है जो इसे विरासत में मिला है, लेकिन यह कुछ भी हो सकता है क्योंकि जाल द्वारा ट्रिगर किया जा सकता है Reflect.get

इससे आप अपने इच्छित सभी कैटर और सेटर सुविधा के साथ एक ऑब्जेक्ट बना सकते हैं:

"use strict";
if (typeof Proxy == "undefined") {
    throw new Error("This browser doesn't support Proxy");
}
let obj = new Proxy({}, {
    get(target, name, receiver) {
        if (!Reflect.has(target, name)) {
            console.log("Getting non-existent property '" + name + "'");
            return undefined;
        }
        return Reflect.get(target, name, receiver);
    },
    set(target, name, value, receiver) {
        if (!Reflect.has(target, name)) {
            console.log(`Setting non-existent property '${name}', initial value: ${value}`);
        }
        return Reflect.set(target, name, value, receiver);
    }
});

console.log(`[before] obj.foo = ${obj.foo}`);
obj.foo = "bar";
console.log(`[after] obj.foo = ${obj.foo}`);

उपरोक्त का आउटपुट है:

गैर-मौजूद संपत्ति 'फू' प्राप्त करना
[इससे पहले] obj.foo = अपरिभाषित
गैर-मौजूद संपत्ति 'फू' की स्थापना, प्रारंभिक मूल्य: बार
[के बाद] obj.foo = बार

ध्यान दें कि जब हम "गैर-मौजूद" संदेश प्राप्त fooकरते हैं, जब हम इसे पुनः प्राप्त करने का प्रयास करते हैं जब यह अभी तक मौजूद नहीं है, और फिर से जब हम इसे बनाते हैं, लेकिन उसके बाद नहीं।


2011 से उत्तर (2013 और 2015 के अपडेट के लिए ऊपर देखें) :

नहीं, जावास्क्रिप्ट में कैच-ऑल प्रॉपर्टी फीचर नहीं है। आप जिस एक्सेसरी सिंटैक्स का उपयोग कर रहे हैं , वह कल्पना की धारा 11.1.5 में कवर किया गया है , और ऐसा कोई वाइल्डकार्ड या ऐसा कुछ नहीं देता है।

तुम्हें पता है, निश्चित रूप से, यह करने के लिए एक समारोह को लागू कर सकता है, लेकिन मैं आप शायद का उपयोग नहीं करना चाहते हैं अनुमान लगा रहा हूँ f = obj.prop("foo");के बजाय f = obj.foo;और obj.prop("foo", value);बजाय obj.foo = value;(कार्य अज्ञात गुण संभाल करने के लिए जो आवश्यक हो जाएगा)।

FWIW, गटर फंक्शन (मैं सेटर लॉजिक से परेशान नहीं था) कुछ इस तरह दिखेगा:

MyObject.prototype.prop = function(propName) {
    if (propName in this) {
        // This object or its prototype already has this property,
        // return the existing value.
        return this[propName];
    }

    // ...Catch-all, deal with undefined property here...
};

लेकिन फिर, मैं कल्पना नहीं कर सकता कि आप वास्तव में ऐसा करना चाहते हैं, क्योंकि यह कैसे बदलता है कि आप ऑब्जेक्ट का उपयोग कैसे करते हैं।


1
वहाँ के लिए एक विकल्प है Proxy: Object.defineProperty()। मैंने विवरण को अपने नए उत्तर में रखा ।
एंड्रयू

@ और - मुझे डर है कि आपने सवाल को गलत कर दिया है, अपने जवाब पर मेरी टिप्पणी देखें।
टीजे क्राउडर

4

निम्नलिखित इस समस्या के लिए एक मूल दृष्टिकोण हो सकता है:

var obj = {
  emptyValue: null,
  get: function(prop){
    if(typeof this[prop] == "undefined")
        return this.emptyValue;
    else
        return this[prop];
  },
  set: function(prop,value){
    this[prop] = value;
  }
}

इसका उपयोग करने के लिए गुणों को तार के रूप में पारित किया जाना चाहिए। तो यहाँ एक उदाहरण है कि यह कैसे काम करता है:

//To set a property
obj.set('myProperty','myValue');

//To get a property
var myVar = obj.get('myProperty');

संपादित करें: मैंने जो प्रस्तावित किया है, उसके आधार पर एक बेहतर, अधिक वस्तु-उन्मुख दृष्टिकोण है:

function MyObject() {
    var emptyValue = null;
    var obj = {};
    this.get = function(prop){
        return (typeof obj[prop] == "undefined") ? emptyValue : obj[prop];
    };
    this.set = function(prop,value){
        obj[prop] = value;
    };
}

var newObj = new MyObject();
newObj.set('myProperty','MyValue');
alert(newObj.get('myProperty'));

आप इसे यहां काम करते हुए देख सकते हैं ।


यह काम नहीं करता है। आप संपत्ति के नाम को निर्दिष्ट किए बिना एक गेटर को परिभाषित नहीं कर सकते।
जॉन कुरलक

@JohnKurlak इस jsFiddle की जाँच करें: jsfiddle.net/oga7ne4x यह काम करता है। आपको केवल संपत्ति के नामों को तार के रूप में पास करना होगा।
clami219

3
आह, स्पष्ट करने के लिए धन्यवाद। मुझे लगा कि आप अपने स्वयं के प्राप्त () / सेट () को न लिखकर, () / सेट () भाषा सुविधा का उपयोग करने का प्रयास कर रहे हैं। मुझे यह समाधान अभी भी पसंद नहीं है क्योंकि यह वास्तव में मूल समस्या को हल नहीं करता है।
जॉन कुर्लाक

@ जॉन कोहलक, मैंने लिखा है कि यह एक मूल दृष्टिकोण है। यह समस्या को हल करने का एक अलग तरीका प्रदान करता है, भले ही यह समस्या को हल नहीं करता है जहां आपके पास एक मौजूदा कोड है जो अधिक पारंपरिक दृष्टिकोण का उपयोग करता है। लेकिन यह अच्छा है अगर आप स्क्रैच से शुरू कर रहे हैं। निश्चित रूप से योग्य नहीं है ...
clami219

@JohnKurlak देखें कि क्या यह अब बेहतर है! :)
clami219

0

प्रस्तावना:

टीजे क्राउडर के जवाब में उल्लेख किया गया है Proxy, जो उन संपत्तियों के लिए कैच-ऑल गेट्टर / सेटर की आवश्यकता होगी, जो मौजूद नहीं है, जैसा कि ओपी पूछ रहा था। इस बात पर निर्भर करता है कि वास्तव में डायनेमिक गेटर्स / सेटर्स के साथ क्या व्यवहार किया गया है, Proxyहालांकि वास्तव में आवश्यक नहीं हो सकता है; या, संभवतः, आप Proxyजो मैं आपको नीचे दिखाऊंगा उसके साथ संयोजन का उपयोग करना चाह सकते हैं ।

(PS मैंने Proxyहाल ही में लिनक्स पर फ़ायरफ़ॉक्स में पूरी तरह से प्रयोग किया है और पाया है कि यह बहुत सक्षम है, लेकिन साथ ही साथ कुछ भ्रमित / मुश्किल काम करने और सही पाने के लिए। अधिक महत्वपूर्ण बात, मैंने यह भी पाया है कि यह काफी धीमा है (कम से कम में) जावास्क्रिप्ट को आजकल कैसे अनुकूलित किया जाता है के संबंध में) - मैं डेका-गुणकों के धीमी के दायरे में बात कर रहा हूं।)


गतिशील रूप से निर्मित गेटर्स को लागू करने और विशेष रूप से बसने के लिए, आप उपयोग Object.defineProperty()या कर सकते हैं Object.defineProperties()। यह भी काफी तेज है।

जिस्ट यह है कि आप किसी ऑब्जेक्ट पर एक गेटर और / या सेटर को परिभाषित कर सकते हैं जैसे:

let obj = {};
let val = 0;
Object.defineProperty(obj, 'prop', { //<- This object is called a "property descriptor".
  //Alternatively, use: `get() {}`
  get: function() {
    return val;
  },
  //Alternatively, use: `set(newValue) {}`
  set: function(newValue) {
    val = newValue;
  }
});

//Calls the getter function.
console.log(obj.prop);
let copy = obj.prop;
//Etc.

//Calls the setter function.
obj.prop = 10;
++obj.prop;
//Etc.

यहां ध्यान देने योग्य कई बातें:

  • आप valueप्रॉपर्टी डिस्क्रिप्टर में संपत्ति का उपयोग नहीं कर सकते हैं ( ऊपर नहीं दिखाया गया है) एक साथ getऔर / या set; डॉक्स से:

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

  • इस प्रकार, आप ध्यान दें हूँ कि मैं एक बनाया valसंपत्ति के बाहर की Object.defineProperty()कॉल / संपत्ति वर्णनकर्ता। यह मानक व्यवहार है।
  • त्रुटि के अनुसार यहाँ , निर्धारित नहीं करते हैं writableकरने के लिए trueसंपत्ति डिस्क्रिप्टर में अगर आप का उपयोग करें getया set
  • आप सेटिंग पर विचार करना चाह सकते हैं configurableऔर enumerable, हालाँकि, इसके बाद आप पर निर्भर करता है; डॉक्स से:

    विन्यास

    • true यदि और केवल यदि इस संपत्ति के विवरणकर्ता के प्रकार को बदला जा सकता है और यदि संपत्ति को संबंधित वस्तु से हटाया जा सकता है।

    • झूठे की अवहेलना।


    गणनीय

    • true अगर और केवल अगर यह संपत्ति इसी वस्तु पर गुणों की गणना के दौरान दिखाई देती है।

    • झूठे की अवहेलना।


इस नोट पर, ये ब्याज के भी हो सकते हैं:

  • Object.getOwnPropertyNames(obj): एक वस्तु के सभी गुण प्राप्त करता है, यहां तक ​​कि गैर-एन्यूमरबल वाले (AFAIK यह ऐसा करने का एकमात्र तरीका है!)।
  • Object.getOwnPropertyDescriptor(obj, prop): किसी वस्तु का संपत्ति विवरणक प्राप्त करता है, जिस वस्तु को Object.defineProperty()ऊपर पारित किया गया था ।
  • obj.propertyIsEnumerable(prop);: एक विशिष्ट वस्तु उदाहरण पर एक व्यक्तिगत संपत्ति के लिए, इस फ़ंक्शन को ऑब्जेक्ट आवृत्ति पर यह निर्धारित करने के लिए कॉल करें कि क्या विशिष्ट संपत्ति गणना योग्य है या नहीं।

2
मुझे डर है कि आपने प्रश्न को गलत बताया है। ओपी विशेष रूप से के लिए कहा सब पकड़ की तरह PHP के __getऔर__setdefinePropertyउस मामले को संभालता नहीं है। प्रश्न से: "यानी, किसी भी संपत्ति के नाम के लिए गेटर्स और सेटर बनाएं जो पहले से परिभाषित नहीं है।" (उनका जोर)। definePropertyपहले से गुणों को परिभाषित करता है। ओपी ने जो पूछा वह करने का एकमात्र तरीका एक छद्म है।
टीजे क्राउडर

@TJCrowder मैं देख रहा हूं। आप तकनीकी रूप से सही हैं, हालांकि यह सवाल बहुत स्पष्ट नहीं था। मैंने अपने उत्तर को उसी के अनुसार समायोजित किया है। इसके अलावा, कुछ वास्तव में हमारे उत्तरों का संयोजन चाहते हैं (मैं व्यक्तिगत रूप से करता हूं)।
एंड्रयू

@ और जब मैंने 2011 में यह सवाल वापस पूछा तो मेरे मन में जो उपयोग-मामला था, वह एक पुस्तकालय था, जो एक ऐसी वस्तु लौटा सकता है, जिस पर उपयोगकर्ता ऐसा कह सकता है obj.whateverPropertyकि पुस्तकालय एक जेनेरिक गेट्टर के साथ अवरोधन कर सकता है और उसे संपत्ति का नाम दिया जा सकता है उपयोगकर्ता तक पहुँचने की कोशिश की। इसलिए 'कैच-ऑल गेटर्स एंड सेवर्स' की आवश्यकता।
डेसकॉग

-6
var x={}
var propName = 'value' 
var get = Function("return this['" + propName + "']")
var set = Function("newValue", "this['" + propName + "'] = newValue")
var handler = { 'get': get, 'set': set, enumerable: true, configurable: true }
Object.defineProperty(x, propName, handler)

यह मेरे लिए काम करता है


13
Function()जैसा प्रयोग करना है वैसा ही उपयोग करना eval। के मापदंडों के रूप में सीधे कार्य करें defineProperty। या, अगर किसी कारण से आप गतिशील रूप से बनाने के लिए जोर देते हैं getऔर setफिर, एक उच्च-क्रम फ़ंक्शन का उपयोग करें जो फ़ंक्शन बनाता है और इसे वापस लौटाता है, जैसेvar get = (function(propName) { return function() { return this[propName]; };})('value');
क्रिस-एल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.