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()
ใน Loopfor
จึงนําทั้งสองวิธีนี้ออกโปรดทราบว่าหากคุณส่งฟังก์ชันเป็นพารามิเตอร์ คอมไพเลอร์จะค้นหาการเรียกไปยังพารามิเตอร์นั้นได้ เช่น คอมไพเลอร์จะไม่นําฟังก์ชัน
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 ยังไม่มีผลข้างเคียง จึงถูกนําออก