本文的初衷是想介紹如何利用些簡單的程式碼小技巧就能促進JavaScript編譯器的優化程式從而提升程式碼執行效率。特別是在遊戲這種對於垃圾回收速度要求較高,你效能稍微差點使用者就能見到白屏的地方。
Monomorphism:單態性
JavaScript中允許函式呼叫時候傳入動態引數,不過就以簡單的2引數函式為例,當你的引數型別、引數數目與返回型別動態呼叫時才能決定,編譯器需要更多的時間來解析。編譯器自然地希望能夠處理那些單態可預測的資料結構、引數統計等。
1 2 3 4 5 6 7 8 9 10 |
function example(a, b) { // we expect a, b to be numeric console.log(++a * ++b); }; example(); // bad example(1); // still bad example("1", 2); // dammit meg example(1, 2); // good |
Constants:常量
使用常量能夠讓編譯器在編譯時即完成變數的值替換:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
const a = 42; // we can easily unfold this const b = 1337 * 2; // we can resolve this expression const c = a + b; // still can be resolved const d = Math.random() * c; // we can only unfold 'c' // before unfolding a; b; c; d; // after unfolding // we can do this at compile time! 42; 2674; 2716; Math.random() * 2716; |
Inlining:內聯
JIT編譯器能夠找出你的程式碼中被執行次數最多的部分,將你的程式碼分割成多個小的程式碼塊能夠有助於編譯器在編譯時將這些程式碼塊轉化為內聯格式然後增加執行速度。
Data Types:資料型別
儘可能地多用Numbers與Booleans型別,因為他們與其他類似於字串等原始型別相比效能表現更好。使用字串型別可能會帶來額外的垃圾回收消耗。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
const ROBOT = 0; const HUMAN = 1; const SPIDER = 2; let E_TYPE = { Robot: ROBOT, Human: HUMAN, Spider: SPIDER }; // bad // avoid uncached strings in heavy tasks (or better in general) if (entity.type === "Robot") { } // good // the compiler can resolve member expressions // without much deepness pretty fast if (entity.type === E_TYPE.Robot) { } // perfect // right side of binary expression can even get unfold if (entity.type === ROBOT) { } |
Strict & Abstract Operators
儘可能使用===
這個嚴格比較操作符而不是==
操作符。使用嚴格比較操作符能夠避免編譯器進行型別推導與轉換,從而提高一定的效能。
Strict Conditions
JavaScript中的if語句也非常靈活,你可以直接在if(a) then bla
這個型別的條件選擇語句中傳入隨意類似的a值。不過這種情況下,就像上文提及的嚴格比較操作符與寬鬆比較操作符一樣,編譯器需要將其轉化為多個資料型別進行比較,而不能立刻得出結果。當然,這並不是一味的反對使用簡寫方式,而是在非常強調效能的場景,還是建議做好每一個細節的優化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
let a = 2; // bad // abstracts to check in the worst case: // - is value equal to true // - is value greater than zero // - is value not null // - is value not NaN // .. if (a) { // if a is true, do something } // good if (a === 2) { // do sth } // same goes for functions function b() { return (!false); }; if (b()) { // get in here slow } if (b() === true) { // get in here fast // the compiler knows a specific value to compare with } |
Arguments
儘可能避免使用arguments[index]方式進行引數獲取,並且儘量避免修改傳入的引數變數:
1 2 3 4 5 6 7 8 9 10 |
function mul(a, b) { return (arguments[0]*arguments[1]); // bad, very slow return (a*b); // good }; function test(a, b) { a = 5; // bad, dont modify argument identifiers let tmp = a; // good tmp *= 2; // we can now modify our fake 'a' }; |
Toxicity:這些關鍵字有毒
Toxicity
如下列舉的幾個語法特性會影響優化程式:
- eval
- with
- try/catch
同時儘量避免在函式內宣告函式或者閉包,可能在大量的運算中導致過多的垃圾回收操作。
Objecs
Object例項通常會共享隱類,因此當我們訪問或者設定某個例項的未預定義變數值的時候會建立一個隱類。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// our hidden class 'hc_0' class Vector { constructor(x, y) { // compiler finds and expects member declarations here this.x = x; this.y = y; } }; // both vector objects share hidden class 'hc_0' let vec1 = new Vector(0, 0); let vec2 = new Vector(2, 2); // bad, vec2 got hidden class 'hc_1' now vec2.z = 0; // good, compiler knows this member vec2.x = 1; |
Loops
儘可能的快取陣列長度的計算值,並且儘可能在同一個陣列中存放單個型別。避免使用for-in
語法來遍歷某個陣列,因為它真的很慢。另外,continue與break語句在迴圈中的效能也是不錯的,這一點使用的時候不用擔心。另外,儘可能將短小的邏輯部分拆分到獨立的函式中,這樣更有利於編譯器進行優化。另外,使用字首自增表示式,也能帶來小小的效能提升。(++i代替i++)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
let badarray = [1, true, 0]; // bad, dont mix types let array = [1, 0, 1]; // happy compiler // bad choice for (let key in array) { }; // better // but always try to cache the array size let i = 0; for (; i < array.length; ++i) { key = array[i]; }; // good let i = 0; let key = null; let length = array.length; for (; i < length; ++i) { key = array[i]; }; |
drawImage
draeImage函式算是最快的2D Canvas API之一了,不過我們需要注意的是如果為了圖方便省略了全引數傳入,也會增加效能損耗:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// bad ctx.drawImage( img, x, y ); // good ctx.drawImage( img, // clipping sx, sy, sw, sh, // actual stuff x, y, w, h ); // much hax // no subpixel rendering by passing integers ctx.drawImage( img, sx|0, sy|0, sw|0, sh|0, x|0, y|0, w|0, h|0 ); |