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 );