הסבר על ההגבלות שקבע מהדר הסגר

מהדר הסגירה צופה שקלט ה-JavaScript שלו יעמוד בכמה הגבלות. ככל שרמת ה'אופטימיזציה' שתבקשו מהמהמר לבקש תהיה גבוהה יותר, כך יהווה יותר הגבלות על ה-JavaScript בקלט.

במסמך הזה מתוארות המגבלות העיקריות של כל רמת אופטימיזציה. אפשר גם לעיין בדף ה-wiki הזה כדי לראות הנחות נוספות שהמהדר הכין.

הגבלות בכל רמות האופטימיזציה

המהדר קובע את שתי ההגבלות הבאות על כל JavaScript שהוא מעבד, עבור כל רמות האופטימיזציה:

  • המהדר מזהה רק את ECMAScript.

    ECMAScript 5 הוא גרסת ה-JavaScript הנתמכת כמעט בכל מקום. עם זאת, המהדר תומך גם ברבים מהתכונות ב-ECMAScript 6. המהדר תומך רק בתכונות שפה רשמיות.

    תכונות ספציפיות לדפדפן שתואמות למפרט ECMAScript המתאים לשפה יפעלו היטב עם המהדר. לדוגמה, אובייקטים מסוג ActiveX נוצרים באמצעות תחביר JavaScript חוקי, לכן קוד שיוצר אובייקטים מסוג ActiveX פועל עם המהדר.

    המהדרים פועלים באופן פעיל כדי לתמוך בגרסאות שפה חדשות ובתכונות החדשות. בפרויקטים מצוין באמצעות --language_in מה הסימון של גרסת ה-ECMAScript שאליה הם התכוונו.

  • המהדר לא שומר/ת תגובות.

    כל רמות האופטימיזציה של המהדר מסירות תגובות, לכן קוד שמסתמך על תגובות שעוצבו במיוחד לא פועל עם כלי העריכה.

    לדוגמה, מכיוון שהמהדר לא שומר תגובות, לא תוכלו להשתמש ישירות ב"תגובות מותנות" של JScript. עם זאת, אפשר לעקוף את ההגבלה הזו על ידי גלישת תגובות מותנות בביטויים eval(). המהדר יכול לעבד את הקוד הבא בלי ליצור שגיאה:

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

    הערה: אפשר לכלול רישיונות לקוד פתוח וטקסט חשוב נוסף בחלק העליון של הפלט של המהדר, באמצעות ההערה @preserve.

הגבלות על SIMPLE_OPIMIZATIONS

רמת האופטימיזציה הפשוטה משנה את השם של פרמטרים של פונקציות, משתנים מקומיים ופונקציות מקומיות כדי להפחית את גודל הקוד. עם זאת, חלק מהבנייה של JavaScript עלולה לקטוע את תהליך שינוי השם.

במהלך השימוש ב-SIMPLE_OPTIMIZATIONS יש להימנע מהמבנה ומשיטות העבודה הבאות:

  • with:

    כשמשתמשים ב-with, המהדר לא יכול להבדיל בין משתנה מקומי לבין מאפיין אובייקט באותו שם, ולכן הוא משנה את השם של כל המופעים של השם.

    בנוסף, ההצהרה with מקשה על הקריאה של אנשים. ההצהרה with משנה את הכללים הרגילים לפענוח שמות, והיא עלולה להקשות אפילו על המתכנתים שכתבו את הקוד לזהות את שמו.

  • eval():

    המהדר לא מנתח את ארגומנט המחרוזת של eval(), ולכן הוא לא ישנה את השמות של סמלים בארגומנט זה.

  • ייצוגי מחרוזות של שמות פונקציות או פרמטרים:

    המהדר משנה את השם של פונקציות ופרמטרים של פונקציות, אבל לא משנה את המחרוזות בקוד שמתייחסות לפונקציות או לפרמטרים לפי שם. לכן כדאי להימנע מייצוג של שמות פונקציות או פרמטרים במחרוזות בקוד. לדוגמה, הפונקציה argumentNames() של ספריית אב הטיפוס משתמשת ב-Function.toString() כדי לאחזר את שמות הפרמטרים של פונקציה. עם זאת, אמנם argumentNames() מפתה אותך להשתמש בשמות הארגומנטים בקוד שלך, אבל הידור פשוט פשוט לא מאפשר עיון מהסוג הזה.

הגבלות על ADVANCED_PEIMIZATIONS

ברמת הידור 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();
    
    המהדר יכווץ את שתי השיטות של create (לאחר תרגום מ-ES6 ל-ES5) כך שהשיחה 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.settingProperty או ES6 getter/setters:

    המהדר לא מבין את המבנים האלה. הטרנסגרטורים ו-set6 של ES מומרים ל-Object.configureProperties(...) באמצעות טרנספילציה. נכון לעכשיו, מהדר לא יכול לנתח באופן סטטי את המבנה הזה ומניח שהגישה לנכסים וההגדרה הן ללא תופעות לוואי. המצב הזה עלול להוביל להשלכות מסוכנות. למשל:

    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 אין תופעות לוואי ולכן הוא הוסר.