UDN-企业互联网技术人气社区

板块导航

浏览  : 766
回复  : 0

[讨论交流] 2016:编写高性能的JavaScript

[复制链接]
呵呵燕的头像 楼主
发表于 2016-11-26 21:34:35 | 显示全部楼层 |阅读模式
  本文的初衷是想介绍如何利用些简单的代码小技巧就能促进JavaScript编译器的优化进程从而提升代码运行效率。特别是在游戏这种对于垃圾回收速度要求较高,你性能稍微差点用户就能见到白屏的地方。

  Monomorphism:单态性

  JavaScript中允许函数调用时候传入动态参数,不过就以简单的2参数函数为例,当你的参数类型、参数数目与返回类型动态调用时才能决定,编译器需要更多的时间来解析。编译器自然地希望能够处理那些单态可预测的数据结构、参数统计等。

 
  1.  function example(a, b) {

  2.   // we expect a, b to be numeric

  3.   console.log(++a * ++b);

  4.   };

  5.   example(); // bad

  6.   example(1); // still bad

  7.   example("1", 2); // dammit meg

  8.   example(1, 2); // good
复制代码


  Constants:常量

  使用常量能够让编译器在编译时即完成变量的值替换:

  1.   const a = 42; // we can easily unfold this

  2.   const b = 1337 * 2; // we can resolve this expression

  3.   const c = a + b; // still can be resolved

  4.   const d = Math.random() * c; // we can only unfold 'c'

  5.   // before unfolding

  6.   a;

  7.   b;

  8.   c;

  9.   d;

  10.   // after unfolding

  11.   // we can do this at compile time!

  12.   42;

  13.   2674;

  14.   2716;

  15.   Math.random() * 2716;
复制代码


  Inlining:内联

  JIT编译器能够找出你的代码中被执行次数最多的部分,将你的代码分割成多个小的代码块能够有助于编译器在编译时将这些代码块转化为内联格式然后增加执行速度。

  Data Types:数据类型

  尽可能地多用Numbers与Booleans类型,因为他们与其他类似于字符串等原始类型相比性能表现更好。使用字符串类型可能会带来额外的垃圾回收消耗。

  
  1. const ROBOT = 0;

  2.   const HUMAN = 1;

  3.   const SPIDER = 2;

  4.   let E_TYPE = {

  5.   Robot: ROBOT,

  6.   Human: HUMAN,

  7.   Spider: SPIDER

  8.   };

  9.   // bad

  10.   // avoid uncached strings in heavy tasks (or better in general)

  11.   if (entity.type === "Robot") {

  12.   }

  13.   // good

  14.   // the compiler can resolve member expressions

  15.   // without much deepness pretty fast

  16.   if (entity.type === E_TYPE.Robot) {

  17.   }

  18.   // perfect

  19.   // right side of binary expression can even get unfold

  20.   if (entity.type === ROBOT) {

  21.   }

  22.   Strict & Abstract Operators
复制代码


  尽可能使用===这个严格比较操作符而不是==操作符。使用严格比较操作符能够避免编译器进行类型推导与转换,从而提高一定的性能。

  Strict Conditions

  JavaScript中的if语句也非常灵活,你可以直接在if(a) then bla这个类型的条件选择语句中传入随意类似的a值。不过这种情况下,就像上文提及的严格比较操作符与宽松比较操作符一样,编译器需要将其转化为多个数据类型进行比较,而不能立刻得出结果。当然,这并不是一味的反对使用简写方式,而是在非常强调性能的场景,还是建议做好每一个细节的优化:

 
  1.  let a = 2;

  2.   // bad

  3.   // abstracts to check in the worst case:

  4.   // - is value equal to true

  5.   // - is value greater than zero

  6.   // - is value not null

  7.   // - is value not NaN

  8.   // ..

  9.   if (a) {

  10.   // if a is true, do something

  11.   }

  12.   // good

  13.   if (a === 2) {

  14.   // do sth

  15.   }

  16.   // same goes for functions

  17.   function b() {

  18.   return (!false);

  19.   };

  20.   if (b()) {

  21.   // get in here slow

  22.   }

  23.   if (b() === true) {

  24.   // get in here fast

  25.   // the compiler knows a specific value to compare with

  26.   }
复制代码


  Arguments

  尽可能避免使用arguments[index]方式进行参数获取,并且尽量避免修改传入的参数变量:

  1.   function mul(a, b) {

  2.   return (arguments[0]*arguments[1]); // bad, very slow

  3.   return (a*b); // good

  4.   };

  5.   function test(a, b) {

  6.   a = 5; // bad, dont modify argument identifiers

  7.   let tmp = a; // good

  8.   tmp *= 2; // we can now modify our fake 'a'

  9.   };
复制代码


  Toxicity:这些关键字有毒

  Toxicity

  如下列举的几个语法特性会影响优化进程:

  eval

  with

  try/catch

  同时尽量避免在函数内声明函数或者闭包,可能在大量的运算中导致过多的垃圾回收操作。

  Objecs

  Object实例通常会共享隐类,因此当我们访问或者设置某个实例的未预定义变量值的时候会创建一个隐类。

  1.   // our hidden class 'hc_0'

  2.   class Vector {

  3.   constructor(x, y) {

  4.   // compiler finds and expects member declarations here

  5.   this.x = x;

  6.   this.y = y;

  7.   }

  8.   };

  9.   // both vector objects share hidden class 'hc_0'

  10.   let vec1 = new Vector(0, 0);

  11.   let vec2 = new Vector(2, 2);

  12.   // bad, vec2 got hidden class 'hc_1' now

  13.   vec2.z = 0;

  14.   // good, compiler knows this member

  15.   vec2.x = 1;
复制代码


  Loops

  尽可能的缓存数组长度的计算值,并且尽可能在同一个数组中存放单个类型。避免使用for-in语法来遍历某个数组,因为它真的很慢。另外,continue与break语句在循环中的性能也是不错的,这一点使用的时候不用担心。另外,尽可能将短小的逻辑部分拆分到独立的函数中,这样更有利于编译器进行优化。另外,使用前缀自增表达式,也能带来小小的性能提升。(++i代替i++)

  1.   let badarray = [1, true, 0]; // bad, dont mix types

  2.   let array = [1, 0, 1]; // happy compiler

  3.   // bad choice

  4.   for (let key in array) {

  5.   };

  6.   // better

  7.   // but always try to cache the array size

  8.   let i = 0;

  9.   for (; i < array.length; ++i) {

  10.   key = array[i];

  11.   };

  12.   // good

  13.   let i = 0;

  14.   let key = null;

  15.   let length = array.length;

  16.   for (; i < length; ++i) {

  17.   key = array[i];

  18.   };
复制代码


  drawImage

  draeImage函数算是最快的2D Canvas API之一了,不过我们需要注意的是如果为了图方便省略了全参数传入,也会增加性能损耗:

  1.   // bad

  2.   ctx.drawImage(

  3.   img,

  4.   x, y

  5.   );

  6.   // good

  7.   ctx.drawImage(

  8.   img,

  9.   // clipping

  10.   sx, sy,

  11.   sw, sh,

  12.   // actual stuff

  13.   x, y,

  14.   w, h

  15.   );

  16.   // much hax

  17.   // no subpixel rendering by passing integers

  18.   ctx.drawImage(

  19.   img,

  20.   sx|0, sy|0,

  21.   sw|0, sh|0,

  22.   x|0, y|0,

  23.   w|0, h|0

  24.   );
复制代码


原文作者:王下邀月熊_Chevalier  来源:开发者头条

相关帖子

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部