。本文的初衷是想介紹如何利用些簡單的程式碼小技巧就能促進JavaScript編譯器的最佳化進程從而提升程式碼運行效率。特別是在遊戲這種對於垃圾回收速度要求較高,你效能稍微差點用戶就能見到白屏的地方。
JavaScript中允許函數呼叫時候傳入動態參數,不過就以簡單的2參數函數為例,當你的參數型別、參數數目與回傳類型動態呼叫時才能決定,編譯器需要更多的時間來解析。編譯器自然地希望能夠處理那些單態可預測的資料結構、參數統計等。
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
使用常數能讓編譯器在編譯時即完成變數的值替換:
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;
JIT編譯器能夠找出你的程式碼中被執行次數最多的部分,將你的程式碼分割成多個小的程式碼區塊能夠有助於編譯器在編譯時將這些程式碼區塊轉換為內聯格式然後增加執行速度。
盡可能地多用Numbers與Booleans類型,因為他們與其他類似於字串等原始類型相比效能表現更好。使用字串類型可能會帶來額外的垃圾回收消耗。
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) { }
盡可能使用 === 這個嚴格比較操作符而不是 == 運算子。使用嚴格比較操作符能夠避免編譯器進行類型推導與轉換,從而提高一定的效能。
JavaScript中的if語句也非常靈活,你可以直接在 if(a) then bla 這個類型的條件選擇語句中傳入隨意類似的a值。不過這種情況下,就像上文提及的嚴格比較操作符與寬鬆比較操作符一樣,編譯器需要將其轉換為多個資料型別進行比較,而不能立刻得出結果。當然,這並不是一味的反對使用簡寫方式,而是在非常強調性能的場景,還是建議做好每一個細節的優化:
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 [index]方式進行參數獲取,並且盡量避免修改傳入的參數變數:
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' };
#如下列舉的幾個語法特性會影響最佳化進程:
eval
#with
// 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;
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]; };
// 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 );