ทําความเข้าใจข้อจํากัดที่คอมไพเลอร์ปิดไว้

Closure Compiler ต้องการให้อินพุต JavaScript เป็นไปตามข้อจํากัดบางประการ ยิ่งการเพิ่มประสิทธิภาพที่คุณขอให้ผู้คอมไพล์ทํางานมากเท่าใด คอมไพเลอร์ก็จะจํากัด JavaScript อินพุตมากขึ้นเท่านั้น

เอกสารนี้อธิบายข้อจํากัดหลักของการเพิ่มประสิทธิภาพแต่ละระดับ ดูข้อมูลเพิ่มเติมจากสมมติฐานเพิ่มเติมที่หน้าวิกิพีเดียนี้

ข้อจํากัดสําหรับระดับการเพิ่มประสิทธิภาพทั้งหมด

คอมไพเลอร์จะวางข้อจํากัด 2 อย่างต่อไปนี้ใน JavaScript ทั้งหมดที่เบราว์เซอร์ประมวลผลสําหรับระดับการเพิ่มประสิทธิภาพทั้งหมด

  • คอมไพเลอร์รู้จัก ECMAScript เท่านั้น

    ECMAScript 5 เป็น JavaScript เวอร์ชันที่รองรับเกือบทุกภูมิภาค อย่างไรก็ตาม คอมไพเลอร์ยังรองรับฟีเจอร์จํานวนมากใน ECMAScript 6 อีกด้วย คอมไพเลอร์รองรับเฉพาะฟีเจอร์ภาษาทางการเท่านั้น

    ฟีเจอร์เฉพาะเบราว์เซอร์ที่สอดคล้องกับข้อกําหนดเฉพาะภาษา ECMAScript ที่เหมาะสมจะใช้งานได้ในคอมไพเลอร์ เช่น ออบเจ็กต์ ActiveX จะสร้างด้วยไวยากรณ์ JavaScript ทางกฎหมาย ดังนั้นโค้ดที่สร้างออบเจ็กต์ ActiveX จะทํางานร่วมกับคอมไพเลอร์

    ผู้ดูแลคอมไพเลอร์จะทํางานอย่างต่อเนื่องเพื่อรองรับเวอร์ชันใหม่ของภาษาและฟีเจอร์ต่างๆ โปรเจ็กต์จะระบุเวอร์ชันของภาษา ECMAScript ที่ต้องการได้โดยใช้แฟล็ก --language_in

  • คอมไพล์ไม่ได้เก็บรักษาความคิดเห็นไว้

    ระดับการเพิ่มประสิทธิภาพคอมไพเลอร์ทั้งหมดจะนําความคิดเห็นออก ดังนั้นโค้ดที่ใช้ความคิดเห็นที่จัดรูปแบบพิเศษจะใช้กับคอมโพเนนต์คอมไพเลอร์ไม่ได้

    เช่น เนื่องจากคอมไพเลอร์ไม่ได้เก็บความคิดเห็นไว้ คุณจึงใช้ "ความคิดเห็นแบบมีเงื่อนไข" ของ JScript โดยตรงไม่ได้ แต่คุณหลีกเลี่ยงข้อจํากัดนี้โดยใส่ความคิดเห็นแบบมีเงื่อนไขในนิพจน์ eval() ผู้คอมไพล์จะประมวลผลโค้ดต่อไปนี้ได้โดยไม่ต้องสร้างข้อผิดพลาด

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

    หมายเหตุ: คุณรวมใบอนุญาตโอเพนซอร์สและข้อความสําคัญอื่นๆ ที่ด้านบนของเอาต์พุตคอมไพเลอร์ได้โดยใช้ @preserve หมายเหตุ

ข้อจํากัดสําหรับ 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
    
  • การอ้างอิงตัวแปรเป็นพร็อพเพอร์ตี้ของออบเจ็กต์ส่วนกลาง:

    คอมไพเลอร์เปลี่ยนชื่อพร็อพเพอร์ตี้และตัวแปรแยกกัน เช่น คอมไพเลอร์จะปฏิบัติต่อการอ้างอิง 2 รายการต่อไปนี้กับ 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() ใน Loop 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.
        

ผลของการแยกพร็อพเพอร์ตี้ออบเจ็กต์

ในโหมดขั้นสูง คอมไพเลอร์จะยุบพร็อพเพอร์ตี้ของออบเจ็กต์เพื่อเตรียมพร้อมสําหรับการตัดชื่อ ตัวอย่างเช่น Compiler เปลี่ยนรูปแบบนี้

   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 Chat ในลักษณะที่คงที่โดยไม่รู้จัก Superclass

    โค้ดต่อไปนี้ปลอดภัยเนื่องจากคอมไพเลอร์รู้ว่า 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 getter/setter:

    คอมไพเลอร์ไม่เข้าใจโครงสร้างเหล่านี้เลย ตัวแปลงรหัสและตัวแปลงรหัส 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 ยังไม่มีผลข้างเคียง จึงถูกนําออก