高性能JavaScript编程实践

Posted 职坐标在线

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高性能JavaScript编程实践相关的知识,希望对你有一定的参考价值。

前言

最近在翻这本书(2010年版 丁琛译),感觉可能是因为浏览器引擎的改进或是其他原因,书中有些原本能提高性能的代码在最新的浏览器中已经失效。但是有些章节的有些内容还是相当不错的,譬如第八章编程实践,为了方便以后的查阅,对此做个总结。失效的代码也会在以后做更进一步的探索。

避免双重求值

这个优化策略很好理解,我们可能都已经不知不觉地运用在了实际的编程中:

javascript


1

2

3

4

5

6

7

// not use this

setTimeout('alert("hello world")', 1000);

 

// use this

setTimeout(function() {

  alert('hello world');

}, 1000);

上面的两段代码都能执行,但是我们一般用第二种,同样的对于setInterval来说也是如此,类似的还有eval()以及Function()构造函数。JavaScript像其他很多脚本语言一样,允许你在程序中提取一个包含代码的字符串,然后动态执行它。当你在JavaScript代码中执行另一段JavaScript代码时,都会导致双重求值的性能消耗,此代码首先会以正常的方式求值,然后在执行过程中对包含于字符串中的代码发起另一个求值运算。所以,大多数时候没必要使用eval()和Function(),就避免使用它们;至于setTimeout和setInterval,建议传入函数而不是字符串来作为第一个参数。

由此联想到了php中的”和“”,我们尽量用”去引用字符串,也是同样的原因。

使用Object/Array直接量

JavaScript


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

// use this

var obj = {

  name: 'hanzichi',

  age: 10

};

 

var arr = [0, 1, 2, 3];

 

// not use this

var obj = new Object();

obj.name = 'hanzichi';

obj.age = 10;

 

var arr = new Array();

arr[0] = 0;

arr[1] = 1;

arr[2] = 2;

arr[3] = 3;

不要重复工作

也许最常见的重复工作就是浏览器探测(当然“不要重复工作”只是一种思想,并不一定针对浏览器探测)。

考虑一个添加事件处理器的例子,典型的跨浏览器代码写法如下:

JavaScript


1

2

3

4

5

6

7

8

9

10

11

function addHandler(target, eventType, handler) {

  if (target.addEventListener) {  // DOM2 Events

    target.addEventListener(eventType, handler, false);

  } else {  // IE

    target.attachEvent('on' + eventType, handler);

  }

}          

 

addHandler(document, 'click', function() {

  console.log('hello world');

});

但是如果一个页面调用了好多次addHandler函数添加事件,每次都会去做浏览器的判断,但是事实是每次的判断结果都是一样的,因为浏览器并不会变化,这时我们就可以针对“不要重复工作”做一个优化策略。

  • 延迟加载

延迟加载,也称惰性加载,惰性载入等。延迟加载意味着在信息被使用前不会做任何操作:

JavaScript


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

function addHandler(target, eventType, handler) {

  if (target.addEventListener) {  // DOM2 Events

    addHandler = function(target, eventType, handler) {

      target.addEventListener(eventType, handler, false);

    };

  } else {  // IE

    addHandler = function(target, eventType, handler) {

      target.attachEvent('on' + eventType, handler);

    };

  }

  addHandler(target, eventType, handler);

}          

 

// 调用

addHandler(document, 'click', function() {

  console.log('hello world');

});

 

addHandler(window, 'keydown', function() {

  console.log('key down');

});

方法在第一次被调用时,会先检查并决定使用哪种方法去绑定事件处理器,然后原始函数被包含正确操作的新函数覆盖。最后一步调用新的函数(也可以直接return 新的函数),并传入原始参数。之后的每次调用addHandler()都不会再做检测,因为检测代码已经被新的函数覆盖。

调用延迟加载函数时,第一次总会消耗较长的时间,因为它必须运行检测接着再调用另一个函数完成任务,但随后调用函数会变快,因为不需要再进行检测。当一个函数在页面中不会立即调用时,延迟加载是最好的选择。

  • 条件预加载

条件预加载会在脚本加载期间提前检测,而不会等到函数被调用:

JavaScript


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

var addHandler = document.addEventListener ?

  function(target, eventType, handler) {

    target.addEventListener(eventType, handler, false);

  }:

  function(target, eventType, handler) {

    target.attachEvent('on' + eventType, handler);

  };    

 

// 调用

addHandler(document, 'click', function() {

  console.log('hello world');

});

 

addHandler(window, 'keydown', function() {

  console.log('key down');

});

条件预加载确保所有函数消耗的时间相同,其代价是需要在脚本加载时就检测,而不是加载后。预加载适用于一个函数马上就要被用到,并且在整个页面的生命周期中频繁出现的场合。

 

常见的“不要重复工作”还有做ajax时候的浏览器探测,自己可以思考下写写代码。

使用速度快的部分

  • 位运算

用位运算加速大家都应该熟悉并且熟练掌握:

JavaScript


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

// use &1 instead of %2

var a = 10;

if (a & 1) {  // use this

  // ...

}

 

if (a % 2) { // not use this

  // ...

}

 

// use << 1 instead of *2

var a = 10;

var b = a << 1; // use this

var b = a * 2;  // not use this

 

var a = 10;

var b = a >> 1; // use this

var b = a / 2;  // not use this

位运算除了能加速快,还能用在各种算法和数据结构中,比如状态压缩dp,按位dp等等。

  • 原生方法

无论你的JavaScript代码如何优化,都永远不会比JavaScript引擎提供的原生方法更快,因为JavaScript原生部分在你写代码前就已经存在在浏览器中了,并且都是用底层语言写的,诸如C++。这意味着这些方法会被编译成机器码,成为浏览器的一部分。

所以尽量用一些内置的函数或者常量,比如Math对象提供的:

JavaScript


1

2

3

4

5

6

7

8

9

10

11

Math.E

Math.LN10

Math.LN2

Math.PI

Math.SQRT1_2

Math.abs()

Math.sin()

Math.sqrt()

.

.

.

另外一个例子是选择器API,它允许使用CSS选择器来查找DOM节点。CSS查询被JavaScript原生支持并被JQuery发扬光大。JQuery引擎被广泛认为是最快的CSS查询引擎,但是它仍然比原生方法慢。原生的querySelector()和querySelectorAll()方法完成任务平均所需时间是基于JavaScript的CSS查询的10%。所以当原生方法可用时,尽量使用它们。特别是数学运算和DOM操作,用编译后的代码做更多的事情,你的代码就会越快。

近期热门文章回顾

【127】2017 年你应该学习的编程语言、框架和工具

【128】周末推荐丨用100行代码写个密码强度检测器

【129】周鸿祎写代码水平怎样?周鸿祎20个晚上写了10000行代码!

【130】程序员干货:15项关键提示让您的软件远离Bug干扰

【131】2016黑客挖洞全盘点:揭秘互联网三巨头背后的中国力量


回复文章编号,例“001”即可获得全文


以上是关于高性能JavaScript编程实践的主要内容,如果未能解决你的问题,请参考以下文章

函数式编程实践

函数式编程的Java编码实践:利用惰性写出高性能且抽象的代码

函数式编程的Java编码实践:利用惰性写出高性能且抽象的代码

今日图书—大巧不工/JavaScript异步编程/结网/创新者的窘境

异步编程你必须知道的6个最佳实践

Quora的Python异步编程实践