2016:編寫高性能的JavaScript翻譯自Felix Maier的文章Writing-Efficient-JavaScript。本文從屬於筆者的Web 前端入門與最佳實踐中JavaScript 入門與最佳實踐系列文章。javascript
本文的初衷是想介紹如何利用些簡單的代碼小技巧就能促進JavaScript編譯器的優化進程從而提高代碼運行效率。特別是在遊戲這種對於垃圾回收速度要求較高,你性能稍微差點用戶就能見到白屏的地方。前端
JavaScript中容許函數調用時候傳入動態參數,不過就以簡單的2參數函數爲例,當你的參數類型、參數數目與返回類型動態調用時才能決定,編譯器須要更多的時間來解析。編譯器天然地但願可以處理那些單態可預測的數據結構、參數統計等。java
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
使用常量可以讓編譯器在編譯時即完成變量的值替換:git
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編譯器可以找出你的代碼中被執行次數最多的部分,將你的代碼分割成多個小的代碼塊可以有助於編譯器在編譯時將這些代碼塊轉化爲內聯格式而後增長執行速度。github
儘量地多用Numbers與Booleans類型,由於他們與其餘相似於字符串等原始類型相比性能表現更好。使用字符串類型可能會帶來額外的垃圾回收消耗。web
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) { }
儘量使用===
這個嚴格比較操做符而不是==
操做符。使用嚴格比較操做符可以避免編譯器進行類型推導與轉換,從而提升必定的性能。express
JavaScript中的if語句也很是靈活,你能夠直接在if(a) then bla
這個類型的條件選擇語句中傳入隨意相似的a值。不過這種狀況下,就像上文說起的嚴格比較操做符與寬鬆比較操做符同樣,編譯器須要將其轉化爲多個數據類型進行比較,而不能馬上得出結果。固然,這並非一味的反對使用簡寫方式,而是在很是強調性能的場景,仍是建議作好每個細節的優化:segmentfault
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
try/catch
同時儘可能避免在函數內聲明函數或者閉包,可能在大量的運算中致使過多的垃圾回收操做。
Object實例一般會共享隱類,所以當咱們訪問或者設置某個實例的未預約義變量值的時候會建立一個隱類。
// 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;
儘量的緩存數組長度的計算值,而且儘量在同一個數組中存放單個類型。避免使用for-in
語法來遍歷某個數組,由於它真的很慢。另外,continue與break語句在循環中的性能也是不錯的,這一點使用的時候不用擔憂。另外,儘量將短小的邏輯部分拆分到獨立的函數中,這樣更有利於編譯器進行優化。另外,使用前綴自增表達式,也能帶來小小的性能提高。(++i代替i++)
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]; };
draeImage函數算是最快的2D Canvas API之一了,不過咱們須要注意的是若是爲了圖方便省略了全參數傳入,也會增長性能損耗:
// 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 );