क्लोज़र कंपाइलर की ओर से लगाई गई पाबंदियों को समझना

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

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

सभी ऑप्टिमाइज़ेशन लेवल के लिए पाबंदियां

कंपाइलर सभी JavaScript लेवल पर लागू होने वाले सभी JavaScript पर, ये दो पाबंदियां लगाता है:

  • कंपाइलर सिर्फ़ ECMAScript को पहचानता है.

    ECMAScript 5, JavaScript का ऐसा वर्शन है जो करीब-करीब हर जगह काम करता है. हालांकि, कंपाइलर, ECMAScript 6 की कई सुविधाओं के साथ भी काम करता है. कंपाइलर सिर्फ़ आधिकारिक भाषा में मिलने वाली सुविधाओं के साथ काम करता है.

    ब्राउज़र की खास सुविधाएं, जो सही ECMAScript भाषा की खास बातों के मुताबिक हों, कंपाइलर के साथ ठीक से काम करेंगी. उदाहरण के लिए, ActiveX ऑब्जेक्ट कानूनी JavaScript सिंटैक्स के साथ बनाए जाते हैं, इसलिए ActiveX ऑब्जेक्ट बनाने वाला कोड कंपाइलर के साथ काम करता है.

    कंपाइलर के रखरखाव वाले लोग, भाषा के नए वर्शन और उनकी सुविधाओं के साथ काम करते हैं. --language_in फ़्लैग का इस्तेमाल करके, प्रोजेक्ट यह तय कर सकते हैं कि वे ECMAScript में भाषा का कौनसा वर्शन इस्तेमाल करना चाहते हैं.

  • कंपाइलर टिप्पणियों को सुरक्षित नहीं रखता है.

    सभी कंपाइलर ऑप्टिमाइज़ेशन लेवल टिप्पणियों को हटा देते हैं. इसलिए, खास तौर पर फ़ॉर्मैट की गई टिप्पणियों पर निर्भर कोड, कंपाइलर के साथ काम नहीं करता.

    उदाहरण के लिए, कंपाइलर टिप्पणियों को सुरक्षित नहीं रखता है, इसलिए आप सीधे JScript की "शर्त के साथ टिप्पणियां" इस्तेमाल नहीं कर सकते हैं. हालांकि, eval() एक्सप्रेशन में कंडीशनल टिप्पणियों को रैप करके, इस पाबंदी को ठीक किया जा सकता है. कंपाइलर कोई गड़बड़ी जनरेट किए बिना, नीचे दिया गया कोड प्रोसेस कर सकता है:

     x = eval("/*@cc_on 2+@*/ 0");
    

    ध्यान दें: आप कंपाइलर आउटपुट में सबसे ऊपर, ओपन सोर्स लाइसेंस और अन्य ज़रूरी टेक्स्ट शामिल कर सकते हैं. इसके लिए, आपको @preserving एनोटेशन का इस्तेमाल करना होगा.

SimPLE_OPTIMIZATIONS के प्रतिबंध

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

SIMPLE_OPTIMIZATIONS का इस्तेमाल करते समय, इन कंस्ट्रक्ट और तरीकों का इस्तेमाल करने से बचें:

  • with:

    with का इस्तेमाल करने पर, कंपाइलर स्थानीय वैरिएबल और एक ही नाम की ऑब्जेक्ट प्रॉपर्टी में अंतर नहीं कर सकता. इसलिए, यह नाम के सभी इंस्टेंस के नाम बदल देता है.

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

  • eval():

    कंपाइलर eval() के स्ट्रिंग आर्ग्युमेंट को पार्स नहीं करता. इसलिए, यह आर्ग्युमेंट में किसी भी सिंबल का नाम नहीं लेगा.

  • फ़ंक्शन या पैरामीटर के नाम को स्ट्रिंग के तौर पर दिखाना:

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

advanced_OPTIMIZATIONS के लिए प्रतिबंध

ADVANCED_OPTIMIZATIONS कंपाइलेशन लेवल में भी वही बदलाव होते हैं जो SIMPLE_OPTIMIZATIONS में होते हैं. साथ ही, ग्लोबल लेवल पर प्रॉपर्टी, वैरिएबल, और फ़ंक्शन के नाम बदलने, डेड कोड एलिमिनेशन, और प्रॉपर्टी को फ़्लैट करने जैसी सुविधाएं भी मिलती हैं. ये नए पास, इनपुट JavaScript पर अतिरिक्त प्रतिबंध लागू करते हैं. आम तौर पर, JavaScript की डाइनैमिक सुविधाओं का इस्तेमाल करने से आपके कोड पर सही स्टैटिक ऐनलिसिस नहीं होगी.

ग्लोबल वैरिएबल, फ़ंक्शन, और प्रॉपर्टी का नाम बदलने के असर:

दुनिया भर में ADVANCED_OPTIMIZATIONS का नाम बदलने से इन तरीकों को खतरनाक माना जाता है:

  • ऐसा बाहरी रेफ़रंस जिनके बारे में जानकारी नहीं दी गई:

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

  • बाहरी कोड में एक्सपोर्ट नहीं किए गए अंदरूनी नामों का इस्तेमाल करना:

    कंपाइल किए गए कोड को ऐसे सभी चिह्नों को एक्सपोर्ट करना चाहिए जिनके बारे में कंपाइल किया गया कोड दिखता है. ऐडवांस कंपाइलेशन और एक्सटर्नल में सिंबल एक्सपोर्ट करने का तरीका बताया गया है.

  • ऑब्जेक्ट प्रॉपर्टी का रेफ़रंस देने के लिए स्ट्रिंग के नामों का इस्तेमाल करना:

    कंपाइलर बेहतर मोड में प्रॉपर्टी का नाम बदलता है, लेकिन यह स्ट्रिंग का नाम कभी नहीं बदलता.

      var x = { renamed_property: 1 };
      var y = x.renamed_property; // This is OK.
    
      // 'renamed_property' below doesn't exist on x after renaming, so the
      //  following evaluates to false.
      if ( 'renamed_property' in x ) {}; // BAD
    
      // The following also fails:
      x['renamed_property']; // BAD
    

    अगर आपको कोट की गई स्ट्रिंग वाली किसी प्रॉपर्टी को रेफ़र करना है, तो हमेशा कोट की गई स्ट्रिंग का इस्तेमाल करें:

      var x = { 'unrenamed_property': 1 };
      x['unrenamed_property'];  // This is OK.
      if ( 'unrenamed_property' in x ) {};   // This is OK
    
  • ग्लोबल ऑब्जेक्ट की प्रॉपर्टी के तौर पर वैरिएबल के बारे में बताना:

    कंपाइलर स्वतंत्र रूप से प्रॉपर्टी और वैरिएबल के नाम बदलता है. उदाहरण के लिए, कंपाइलर नीचे दिए गए दो रेफ़रंस को foo के लिए अलग-अलग मानता है, भले ही वे एक जैसे हों:

      var foo = {};
      window.foo; // BAD
    

    यह कोड:

      var a = {};
      window.b;
    

    अगर आपको किसी वैरिएबल को ग्लोबल ऑब्जेक्ट की प्रॉपर्टी के तौर पर दिखाना है, तो हमेशा उसे इस तरह रेफ़र करें:

    window.foo = {}
    window.foo;
    

Implications of dead code elimination

The ADVANCED_OPTIMIZATIONS compilation level removes code that is never executed. This elimination of dead code makes the following practices dangerous:

  • Calling functions from outside of compiled code:

    When you compile functions without compiling the code that calls those functions, the Compiler assumes that the functions are never called and removes them. To avoid unwanted code removal, either:

    • compile all the JavaScript for your application together, or
    • export compiled functions.

    Advanced Compilation and Externs describes both of these approaches in greater detail.

  • Retrieving functions through iteration over constructor or prototype properties:

    To determine whether a function is dead code, the Compiler has to find all the calls to that function. By iterating over the properties of a constructor or its prototype you can find and call methods, but the Compiler can't identify the specific functions called in this manner.

    For example, the following code causes unintended code removal:

    function Coordinate() {
    }
    Coordinate.prototype.initX = function() {
      this.x = 0;
    }
    Coordinate.prototype.initY = function() {
      this.y = 0;
    }
    var coord = new Coordinate();
    for (method in Coordinate.prototype) {
      Coordinate.prototype[method].call(coord); // BAD
    }
        

    कंपाइलर यह नहीं समझता कि initX() और initY() को for लूप में कॉल किया जाता है. इसलिए, यह इन दोनों तरीकों को हटा देता है.

    ध्यान दें कि अगर आप किसी फ़ंक्शन को पैरामीटर के तौर पर पास करते हैं, तो कंपाइलर उस पैरामीटर को कॉल कर सकता है. उदाहरण के लिए, कंपाइलर बेहतर फ़ंक्शन में नीचे दिए गए कोड को कंपाइल करते समय getHello() फ़ंक्शन को नहीं हटाता है.

    function alertF(f) {
      alert(f());
    }
    function getHello() {
      return 'hello';
    }
    // The Compiler figures out that this call to alertF also calls getHello().
    alertF(getHello); // This is OK.
        

ऑब्जेक्ट प्रॉपर्टी को फ़्लैट करने के असर

बेहतर मोड में, कंपाइलर, ऑब्जेक्ट प्रॉपर्टी को छोटा करता है, ताकि नाम छोटा करने की तैयारी की जा सके. उदाहरण के लिए, कंपाइलर इसे बदलता है:

   var foo = {};
   foo.bar = function (a) { alert(a) };
   foo.bar("hello");

इसमें:

   var foo$bar = function (a) { alert(a) };
   foo$bar("hello");

इस प्रॉपर्टी के फ़्लैट होने के बाद, पास का नाम बदलने की सुविधा ज़्यादा असरदार तरीके से काम कर पाती है. कंपाइलर foo$bar को एक वर्ण से बदल सकता है, उदाहरण के लिए.

हालांकि, प्रॉपर्टी को फ़्लैट करने से भी इन तरीकों को नुकसान पहुंच सकता है:

  • this कंस्ट्रक्टर और प्रोटोटाइप तरीकों के बाहर इस्तेमाल करना:

    प्रॉपर्टी को फ़्लैट करने पर, किसी फ़ंक्शन में कीवर्ड this का मतलब बदल सकता है. उदाहरण के लिए:

       var foo = {};
       foo.bar = function (a) { this.bad = a; }; // BAD
       foo.bar("hello");
    

    बन जाता है:

       var foo$bar = function (a) { this.bad = a; };
       foo$bar("hello");
    

    बदलाव से पहले, this में foo.bar का मतलब foo था. बदलाव के बाद, this का मतलब है दुनिया भर के this. ऐसे मामलों में कंपाइलर यह चेतावनी देता है:

    "WARNING - dangerous use of this in static method foo.bar"

    प्रॉपर्टी को फ़्लैट करने से रोकने के लिए, अपनी पहचान फ़ाइलों को this तक पहुंचने से रोकें. सिर्फ़ कंस्ट्रक्टर और प्रोटोटाइप मेथड में this का इस्तेमाल करें. this का मतलब साफ़ तौर पर नहीं बताया जा सकता, जब आप new कीवर्ड वाले किसी कंस्ट्रक्टर को कॉल करते हैं या किसी ऐसे फ़ंक्शन में कॉल करते हैं जो prototype की प्रॉपर्टी है.

  • बिना किसी जानकारी के स्टैटिक तरीकों का इस्तेमाल करना:

    उदाहरण के लिए, अगर आपके पास ये हैं:

    class A { static create() { return new A(); }};
    class B { static create() { return new B(); }};
    let cls = someCondition ? A : B;
    cls.create();
    
    कंपाइलर (ES6 से ES5 में बदलने के बाद), दोनों create तरीके छोटा हो जाएंगे, इसलिए cls.create() कॉल काम नहीं करेगा. ऐसा करने के लिए, @nocollapse एनोटेशन का इस्तेमाल करें:
    class A {
      /** @nocollapse */
      static create() {
        return new A();
      }
    }
    class B {
      /** @nocollapse */
      static create() {
        return new A();
      }
    }
    

  • सुपर क्लास की जानकारी के बिना स्थिर विधि में सुपर का इस्तेमाल करना:

    यह कोड सुरक्षित है, क्योंकि कंपाइलर को इसके बारे में पता है कि super.sayHi() का मतलब है Parent.sayHi():

    class Parent {
      static sayHi() {
        alert('Parent says hi');
      }
    }
    class Child extends Parent {
      static sayHi() {
        super.sayHi();
      }
    }
    Child.sayHi();
    

    हालांकि, प्रॉपर्टी फ़्लैटिंग नीचे दिए गए कोड को तोड़ देगी, भले ही myMixin(Parent).sayHi बिना कंपाइल के बराबर हो: Parent.sayHi

    class Parent {
      static sayHi() {
        alert('Parent says hi');
      }
    }
    class Child extends myMixin(Parent) {
      static sayHi() {
        super.sayHi();
      }
    }
    Child.sayHi();
    

    इस जानकारी में /** @nocollapse */ एनोटेशन का इस्तेमाल न करें.

  • Object.defineProperties या ES6 गेटर/सेटर का इस्तेमाल करके:

    कंपाइलर इन कंस्ट्रक्शन को अच्छी तरह नहीं समझ पाता. ES6 गेटर और सेटर को ट्रांसपाइलेशन के ज़रिए Object.definePropertiesISBN में बदला जाता है. कंपाइलर फ़िलहाल, इस कंस्ट्रक्शन का स्टैटिक तरीके से विश्लेषण नहीं कर सकता और यह मानता है कि प्रॉपर्टी के ऐक्सेस और सेट का कोई असर नहीं पड़ता. इससे नतीजे खतरनाक हो सकते हैं. उदाहरण के लिए:

    class C {
      static get someProperty() {
        console.log("hello getters!");
      }
    }
    var unused = C.someProperty;
    

    इस पर कंपाइल किया जाता है:

    C = function() {};
    Object.defineProperties(C, {a: // Note someProperty is also renamed to 'a'.
      {configurable:!0, enumerable:!0, get:function() {
        console.log("hello world");
        return 1;
    }}});
    

    C.someProperty का कोई खराब असर नहीं होने की वजह से, इसे हटा दिया गया है.