टाइपस्क्रिप्ट "यह" स्कूपिंग मुद्दा जब jquery कॉलबैक में कहा जाता है


107

मैं टाइपस्क्रिप्ट में "यह" के स्कूपिंग से निपटने के लिए सबसे अच्छे दृष्टिकोण के बारे में निश्चित नहीं हूं।

यहाँ एक सामान्य पैटर्न का उदाहरण दिया गया है जिसे मैं टाइपस्क्रिप्ट में परिवर्तित कर रहा हूँ:

class DemonstrateScopingProblems {
    private status = "blah";
    public run() {
        alert(this.status);
    }
}

var thisTest = new DemonstrateScopingProblems();
// works as expected, displays "blah":
thisTest.run(); 
// doesn't work; this is scoped to be the document so this.status is undefined:
$(document).ready(thisTest.run); 

अब, मैं कॉल को बदल सकता था ...

$(document).ready(thisTest.run.bind(thisTest));

... जो काम करता है। लेकिन यह थोड़े भयानक है। इसका मतलब है कि कोड कुछ परिस्थितियों में सभी को संकलित और ठीक काम कर सकता है, लेकिन अगर हम उस दायरे को बांधना भूल जाते हैं तो यह टूट जाएगा।

मैं इसे कक्षा के भीतर करने का एक तरीका चाहूंगा, ताकि कक्षा का उपयोग करते समय हमें इस बारे में चिंता करने की आवश्यकता न हो कि "यह" क्या है।

कोई सुझाव?

अपडेट करें

एक अन्य दृष्टिकोण जो वसा तीर का उपयोग कर रहा है:

class DemonstrateScopingProblems {
    private status = "blah";

    public run = () => {
        alert(this.status);
    }
}

क्या यह एक मान्य दृष्टिकोण है?


2
यह मददगार होगा: youtube.com/watch?v=tvocUcbCupA
basarat

नोट: रयान ने टाइपस्क्रिप्ट विकी पर अपने जवाब की नकल की ।
फ्रैंकलिन यू

टाइपस्क्रिप्ट 2+ समाधान के लिए यहां देखें ।
दीलान

जवाबों:


166

यहां आपके पास कुछ विकल्प हैं, जिनमें से प्रत्येक का अपना ट्रेड-ऑफ है। दुर्भाग्य से कोई स्पष्ट सर्वश्रेष्ठ समाधान नहीं है और यह वास्तव में आवेदन पर निर्भर करेगा।


आपके प्रश्न में दिखाए गए अनुसार स्वचालित श्रेणी बंधन :

class DemonstrateScopingProblems {
    private status = "blah";

    public run = () => {
        alert(this.status);
    }
}
  • अच्छा / बुरा: यह आपके वर्ग के उदाहरण के अनुसार प्रति विधि एक अतिरिक्त बंद बनाता है। यदि यह विधि आमतौर पर केवल नियमित विधि कॉल में उपयोग की जाती है, तो यह ओवरकिल है। हालाँकि, यदि यह कॉलबैक स्थिति में बहुत अधिक उपयोग किया जाता है, तो thisइनवोक पर एक नया क्लोजर बनाने वाले प्रत्येक कॉल साइट के बजाय संदर्भ को कैप्चर करने के लिए क्लास इंस्टेंस के लिए अधिक कुशल है ।
  • अच्छा: thisसंदर्भ को संभालने के लिए बाहरी कॉलर्स के लिए असंभव
  • अच्छा: टाइपस्क्रिप्ट में टाइप कैफे
  • अच्छा: यदि फ़ंक्शन में पैरामीटर हैं, तो कोई अतिरिक्त काम नहीं
  • खराब: व्युत्पन्न कक्षाएं बेस क्लास विधियों को इस तरह से लिखी गई कॉल का उपयोग नहीं कर सकती हैं super.
  • खराब: किन तरीकों के सटीक शब्दार्थ "पूर्व-बद्ध" हैं और जो आपकी कक्षा और इसके उपभोक्ताओं के बीच एक अतिरिक्त गैर-टाइप अनुबंध नहीं बना रहे हैं।

Function.bind
के रूप में भी दिखाया गया है:

$(document).ready(thisTest.run.bind(thisTest));
  • अच्छी / बुरी: पहली विधि की तुलना में स्मृति / प्रदर्शन व्यापार-बंद
  • अच्छा: यदि फ़ंक्शन में पैरामीटर हैं, तो कोई अतिरिक्त काम नहीं
  • खराब: टाइपस्क्रिप्ट में, इस समय किसी प्रकार की सुरक्षा नहीं है
  • खराब: केवल ECMAScript 5 में उपलब्ध है, अगर यह आपके लिए मायने रखता है
  • बुरा: आपको दो बार इंस्टेंस नाम टाइप करना होगा


टाइप एरो में वसा तीर (व्याख्यात्मक कारणों के लिए कुछ डमी मापदंडों के साथ यहां दिखाया गया है):

$(document).ready((n, m) => thisTest.run(n, m));
  • अच्छी / बुरी: पहली विधि की तुलना में स्मृति / प्रदर्शन व्यापार-बंद
  • अच्छा: टाइपस्क्रिप्ट में, यह 100% प्रकार की सुरक्षा है
  • अच्छा: ECMAScript 3 में काम करता है
  • अच्छा: आपको केवल एक बार उदाहरण का नाम लिखना होगा
  • खराब: आपको दो बार पैरामीटर लिखना होगा
  • खराब: वैरिएड मापदंडों के साथ काम नहीं करता है

1
+1 महान जवाब रयान, पेशेवरों और विपक्ष के टूटने से प्यार है, धन्यवाद!
जोनाथन मोफेट

- अपने Function.bind में, आप हर बार एक नया क्लोजर बनाते हैं जो आपको ईवेंट संलग्न करने की आवश्यकता होती है।
131

1
मोटा तीर तो बस कर दिया !! : D: D = () => बहुत बहुत धन्यवाद! : डी
क्रिस्टोफर स्टॉक

@ ryan-cavanaugh वस्तु के मुक्त होने पर अच्छे और बुरे के बारे में क्या होगा? जैसा कि एसपीए के उदाहरण में> 30 मिनट के लिए सक्रिय है, जेएस कचरा संग्रहकर्ताओं को संभालने के लिए उपरोक्त में से कौन सा सबसे अच्छा है?
abbaf33f

ये सभी तब फ्रीवेबल होंगे जब क्लास का उदाहरण फ्रीवेबल होगा। यदि बाद में इवेंट हैंडलर का जीवनकाल कम हो जाता है, तो बाद वाले दो पहले मुक्त हो जाएंगे। सामान्य तौर पर मैं कहूंगा कि एक औसत दर्जे का अंतर नहीं है, हालांकि।
रयान कैवानुआग

16

एक अन्य समाधान जिसके लिए कुछ प्रारंभिक सेटअप की आवश्यकता होती है, लेकिन इसके अजेय प्रकाश के साथ भुगतान करता है, शाब्दिक रूप से एक-शब्द सिंटैक्स मेथड डेकोरेटर्स का उपयोग गेटर्स के माध्यम से जेआईटी-बाइंड विधियों के लिए कर रहा है।

मैंने इस विचार के कार्यान्वयन का प्रदर्शन करने के लिए GitHub पर एक रेपो बनाया है (यह कोड सहित 40 लाइनों के जवाब में फिट होने के लिए थोड़ा लंबा है, टिप्पणियों सहित) , जिसे आप बस के रूप में उपयोग करेंगे:

class DemonstrateScopingProblems {
    private status = "blah";

    @bound public run() {
        alert(this.status);
    }
}

मैंने इसका उल्लेख अभी तक कहीं भी नहीं देखा है, लेकिन यह त्रुटिपूर्ण रूप से काम करता है। इसके अलावा, इस दृष्टिकोण के लिए कोई उल्लेखनीय पहलू नहीं है: इस सज्जाकार का कार्यान्वयन - रनटाइम प्रकार-सुरक्षा के लिए कुछ प्रकार की जाँच सहित - तुच्छ और सीधा है, और प्रारंभिक विधि कॉल के बाद अनिवार्य रूप से शून्य ओवरहेड के साथ आता है।

आवश्यक हिस्सा निम्न प्रोटोटाइप को क्लास प्रोटोटाइप पर परिभाषित कर रहा है, जिसे पहली कॉल से तुरंत पहले निष्पादित किया गया है:

get: function () {
    // Create bound override on object instance. This will hide the original method on the prototype, and instead yield a bound version from the
    // instance itself. The original method will no longer be accessible. Inside a getter, 'this' will refer to the instance.
    var instance = this;

    Object.defineProperty(instance, propKey.toString(), {
        value: function () {
            // This is effectively a lightweight bind() that skips many (here unnecessary) checks found in native implementations.
            return originalMethod.apply(instance, arguments);
        }
    });

    // The first invocation (per instance) will return the bound method from here. Subsequent calls will never reach this point, due to the way
    // JavaScript runtimes look up properties on objects; the bound method, defined on the instance, will effectively hide it.
    return instance[propKey];
}

पूर्ण स्रोत


विचार को एक कदम आगे भी उठाया जा सकता है, एक क्लास डेकोरेटर में ऐसा करने के बजाय, तरीकों पर पुनरावृत्ति करना और उनमें से प्रत्येक के लिए उपरोक्त संपत्ति डिस्क्रिप्टर को एक पास में परिभाषित करना।


मुझे जिस चीज की जरूरत थी!
मार्सेल वैन डेर बहाव

14

Necromancing।
एक स्पष्ट सरल समाधान है जिसमें तीर-फ़ंक्शंस की आवश्यकता नहीं है (तीर-फ़ंक्शन 30% धीमे हैं), या गेटर्स के माध्यम से जेआईटी-तरीके।
यह समाधान इस संदर्भ को कंस्ट्रक्टर में बांधने के लिए है।

class DemonstrateScopingProblems 
{
    constructor()
    {
        this.run = this.run.bind(this);
    }


    private status = "blah";
    public run() {
        alert(this.status);
    }
}

आप क्लास के कंस्ट्रक्टर में सभी फ़ंक्शंस को स्वचालित रूप से बाँधने के लिए एक ऑटोबाइंड विधि लिख सकते हैं:

class DemonstrateScopingProblems 
{

    constructor()
    { 
        this.autoBind(this);
    }
    [...]
}


export function autoBind(self)
{
    for (const key of Object.getOwnPropertyNames(self.constructor.prototype))
    {
        const val = self[key];

        if (key !== 'constructor' && typeof val === 'function')
        {
            // console.log(key);
            self[key] = val.bind(self);
        } // End if (key !== 'constructor' && typeof val === 'function') 

    } // Next key 

    return self;
} // End Function autoBind

ध्यान दें कि यदि आप ऑटोबिंड-फ़ंक्शन को सदस्य फ़ंक्शन के रूप में एक ही वर्ग में नहीं डालते हैं, तो यह सिर्फ autoBind(this);और नहीं हैthis.autoBind(this);

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

ऐशे ही:

export function autoBind(self)
{
    for (const key of Object.getOwnPropertyNames(self.constructor.prototype))
    {

        if (key !== 'constructor')
        {
            // console.log(key);

            let desc = Object.getOwnPropertyDescriptor(self.constructor.prototype, key);

            if (desc != null)
            {
                let g = desc.get != null;
                let s = desc.set != null;

                if (g || s)
                {
                    if (g)
                        desc.get = desc.get.bind(self);

                    if (s)
                        desc.set = desc.set.bind(self);

                    Object.defineProperty(self.constructor.prototype, key, desc);
                    continue; // if it's a property, it can't be a function 
                } // End if (g || s) 

            } // End if (desc != null) 

            if (typeof (self[key]) === 'function')
            {
                let val = self[key];
                self[key] = val.bind(self);
            } // End if (typeof (self[key]) === 'function') 

        } // End if (key !== 'constructor') 

    } // Next key 

    return self;
} // End Function autoBind

मुझे "ऑटोबाइंड (यह)" नहीं "का उपयोग करना पड़ा। यह ऑटोइंड बिंड (यह)"
जॉनऑपिनकर

@ जोहानपिनार: हाँ, यह। ऑटो (यह) मानता है कि ऑटोबिंड वर्ग के अंदर है, अलग निर्यात के रूप में नहीं।
स्टीफन स्टीगर

मैं अब समझता हूँ। आप विधि को उसी वर्ग पर रखें। मैंने इसे "उपयोगिता" मॉड्यूल में डाल दिया।
जॉनऑपिनकर

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.