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()
จึงจะไม่เปลี่ยนชื่อสัญลักษณ์ใดๆ ภายใน อาร์กิวเมนต์นี้การแสดงชื่อฟังก์ชันหรือพารามิเตอร์เป็นสตริง:
คอมไพเลอร์จะเปลี่ยนชื่อฟังก์ชันและพารามิเตอร์ของฟังก์ชัน แต่จะไม่ เปลี่ยนสตริงใดๆ ในโค้ดที่อ้างอิงถึงฟังก์ชันหรือ พารามิเตอร์ตามชื่อ ดังนั้นคุณจึงควรหลีกเลี่ยงการแสดงชื่อฟังก์ชันหรือ พารามิเตอร์เป็นสตริงในโค้ด ตัวอย่างเช่น ฟังก์ชันไลบรารี Prototype
argumentNames()
ใช้Function.toString()
เพื่อดึงชื่อของพารามิเตอร์ของฟังก์ชัน แต่ในขณะที่argumentNames()
อาจ กระตุ้นให้คุณใช้ชื่อของอาร์กิวเมนต์ในโค้ด การคอมไพล์โหมด Simple จะทําให้การอ้างอิงประเภทนี้ใช้งานไม่ได้
ข้อจำกัดสำหรับ 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()
ในลูปfor
จึงนำทั้ง 2 เมธอดนี้ออกโปรดทราบว่าหากคุณส่งฟังก์ชันเป็นพารามิเตอร์ คอมไพเลอร์จะค้นหาการเรียกพารามิเตอร์นั้นได้ ตัวอย่างเช่น คอมไพเลอร์จะไม่นำฟังก์ชัน
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
การใช้วิธีแบบคงที่โดยไม่ทราบว่ามีการเรียกใช้ในคลาสใด
ตัวอย่างเช่น หากคุณมี:
คอมไพเลอร์จะยุบทั้ง 2 เมธอด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()
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.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 ไม่มีผลข้างเคียง จึงนำออก