درک محدودیت های اعمال شده توسط کامپایلر بسته شدن

کامپایلر بسته انتظار دارد ورودی جاوا اسکریپت خود را با چند محدودیت مطابقت دهد. هر چه سطح بهینه سازی بالاتری را از کامپایلر بخواهید انجام دهد، کامپایلر محدودیت های بیشتری را روی جاوا اسکریپت ورودی قرار می دهد.

این سند محدودیت های اصلی را برای هر سطح از بهینه سازی شرح می دهد. همچنین برای مفروضات اضافی ساخته شده توسط کامپایلر به این صفحه ویکی مراجعه کنید.

محدودیت برای تمام سطوح بهینه سازی

کامپایلر دو محدودیت زیر را برای همه جاوا اسکریپتی که پردازش می‌کند، برای تمام سطوح بهینه‌سازی قرار می‌دهد:

  • کامپایلر فقط ECMAScript را می شناسد.

    ECMAScript 5 نسخه جاوا اسکریپت است که تقریباً در همه جا پشتیبانی می شود. با این حال، کامپایلر از بسیاری از ویژگی‌های ECMAScript 6 نیز پشتیبانی می‌کند. کامپایلر تنها از ویژگی‌های زبان رسمی پشتیبانی می‌کند.

    ویژگی های خاص مرورگر که با مشخصات زبان ECMAScript مناسب مطابقت دارند با کامپایلر به خوبی کار می کنند. به عنوان مثال، اشیاء ActiveX با نحو قانونی جاوا اسکریپت ایجاد می شوند، بنابراین کدی که اشیاء ActiveX را ایجاد می کند با کامپایلر کار می کند.

    نگهدارنده های کامپایلر به طور فعال برای پشتیبانی از نسخه های زبان جدید و ویژگی های آنها کار می کنند. پروژه ها می توانند با استفاده از پرچم --language_in مشخص کنند که کدام نسخه زبان ECMAScript را مد نظر دارند.

  • کامپایلر نظرات را حفظ نمی کند.

    تمام سطوح بهینه‌سازی کامپایلر نظرات را حذف می‌کنند، بنابراین کدی که به نظرات فرمت‌بندی شده خاص متکی است با کامپایلر کار نمی‌کند.

    به عنوان مثال، به دلیل اینکه کامپایلر نظرات را حفظ نمی کند، نمی توانید مستقیماً از "کامنت های شرطی" JScript استفاده کنید. با این حال، می‌توانید با قرار دادن کامنت‌های شرطی در عبارات eval() ، این محدودیت را برطرف کنید. کامپایلر می تواند کد زیر را بدون ایجاد خطا پردازش کند:

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

    توجه: می توانید مجوزهای منبع باز و سایر متن های مهم را در بالای خروجی کامپایلر با استفاده از حاشیه نویسی @preserve قرار دهید.

محدودیت برای SIMPLE_OPTIMIZATIONS

سطح بهینه سازی ساده نام پارامترهای تابع، متغیرهای محلی و توابع تعریف شده محلی را برای کاهش اندازه کد تغییر می دهد. با این حال، برخی از ساختارهای جاوا اسکریپت می توانند این فرآیند تغییر نام را شکست دهند.

هنگام استفاده از SIMPLE_OPTIMIZATIONS از ساختارها و شیوه های زیر اجتناب کنید:

  • with :

    هنگامی که with کامپایلر استفاده می کنید، نمی توانید بین یک متغیر محلی و یک ویژگی شی با همان نام تمایز قائل شوید و بنابراین نام همه نمونه های نام را تغییر می دهد.

    علاوه بر این، دستور with خواندن کد شما را برای انسان سخت‌تر می‌کند. دستور with قوانین عادی برای وضوح نام را تغییر می دهد و حتی برای برنامه نویسی که کد را نوشته است، تشخیص نام به چه چیزی اشاره دارد، دشوار می کند.

  • eval() :

    کامپایلر آرگومان رشته ای eval() را تجزیه نمی کند و بنابراین نام هیچ نمادی را در این آرگومان تغییر نمی دهد.

  • نمایش رشته ای نام تابع یا پارامتر:

    کامپایلر توابع و پارامترهای تابع را تغییر نام می دهد اما هیچ رشته ای را در کد شما که به توابع یا پارامترها با نام اشاره می کند تغییر نمی دهد. بنابراین باید از نمایش نام تابع یا پارامتر به عنوان رشته در کد خود اجتناب کنید. برای مثال، تابع کتابخانه Prototype argumentNames() از Function.toString() برای بازیابی نام پارامترهای یک تابع استفاده می کند. اما در حالی که argumentNames() ممکن است شما را وسوسه کند که از نام آرگومان ها در کد خود استفاده کنید، کامپایل حالت Simple این نوع مرجع را از بین می برد.

محدودیت برای ADVANCED_OPTIMIZATIONS

سطح کامپایل ADVANCED_OPTIMIZATIONS همان تبدیل‌های SIMPLE_OPTIMIZATIONS را انجام می‌دهد و همچنین تغییر نام سراسری ویژگی‌ها، متغیرها و توابع، حذف کد مرده، و مسطح کردن ویژگی را اضافه می‌کند. این پاس‌های جدید محدودیت‌های بیشتری را برای جاوا اسکریپت ورودی ایجاد می‌کنند. به طور کلی، استفاده از ویژگی های پویا جاوا اسکریپت از تحلیل استاتیک صحیح روی کد شما جلوگیری می کند.

پیامدهای تغییر نام متغیر جهانی، تابع و ویژگی:

تغییر نام جهانی ADVANCED_OPTIMIZATIONS اقدامات زیر را خطرناک می کند:

  • مراجع خارجی اعلام نشده:

    به منظور تغییر نام متغیرها، توابع و خصوصیات جهانی به درستی، کامپایلر باید از همه ارجاعات به آن جهانی ها اطلاع داشته باشد. شما باید در مورد نمادهایی که خارج از کد در حال کامپایل تعریف شده اند به کامپایلر بگویید. Advanced Compilation and Externs نحوه اعلان نمادهای خارجی را شرح می دهد.

  • استفاده از نام‌های داخلی صادر نشده در کد خارجی:

    کد کامپایل شده باید هر نمادی را که کد کامپایل نشده به آن اشاره دارد صادر کند. Advanced Compilation and Externs نحوه صادرات نمادها را شرح می دهد.

  • استفاده از نام رشته ها برای اشاره به ویژگی های شی:

    کامپایلر ویژگی ها را در حالت پیشرفته تغییر نام می دهد، اما هرگز نام رشته ها را تغییر نمی دهد.

      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
    
  • اشاره به متغیرها به عنوان ویژگی های شی جهانی:

    کامپایلر خصوصیات و متغیرها را به طور مستقل تغییر نام می دهد. به عنوان مثال، Compiler با دو مرجع زیر به 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 در متدهای سازنده و نمونه اولیه استفاده کنید. هنگامی که سازنده ای را با کلمه کلیدی new یا در داخل تابعی که ویژگی یک prototype است فراخوانی می کنید، معنای this امر بدون ابهام است.

  • استفاده از متدهای استاتیک بدون دانستن اینکه در کدام کلاس فراخوانی شده اند:

    به عنوان مثال، اگر شما:

    class A { static create() { return new A(); }};
    class B { static create() { return new B(); }};
    let cls = someCondition ? A : B;
    cls.create();
    
    داشته باشید، کامپایلر هر دو متد create را جمع می کند (پس از انتقال از ES6 به ES5) بنابراین cls.create() با شکست مواجه می شود. می توانید با حاشیه نویسی @nocollapse از این امر جلوگیری کنید:
    class A {
      /** @nocollapse */
      static create() {
        return new A();
      }
    }
    class B {
      /** @nocollapse */
      static create() {
        return new A();
      }
    }
    
  • استفاده از super در روش ایستا بدون دانستن سوپرکلاس:

    کد زیر امن است، زیرا کامپایلر می داند که () super.sayHi به Parent.sayHi() super.sayHi() اشاره دارد:

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

    با این حال، Flattening ویژگی کد زیر را می شکند، حتی اگر myMixin(Parent).sayHi برابر با Parent.sayHi uncompiled باشد:

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

    با حاشیه نویسی /** @nocollapse */ از این شکستگی جلوگیری کنید.

  • با استفاده از Object.defineProperties یا ES6 getter/setters:

    کامپایلر این ساختارها را به خوبی درک نمی کند. گیرنده و تنظیم کننده ES6 از طریق انتقال به Object.defineProperties(...) تبدیل می شوند. در حال حاضر، کامپایلر قادر به تجزیه و تحلیل استاتیکی این ساختار نیست و فرض می‌کند که دسترسی و مجموعه بدون عوارض جانبی است. این می تواند عواقب خطرناکی داشته باشد. مثلا:

    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 هیچ عارضه ای ندارد، بنابراین حذف شد.