js优化

来源:互联网 发布:ubuntu chmod 755 编辑:程序博客网 时间:2024/05/18 01:57

由于JS是一种解释型语言,执行速度要比编译型语言慢得多。(注:,Chrome是第一款内置优化引擎,将JS编译成本地代码的浏览器,其它浏览器也陆续实现了JS的编译过程。但是,即使到了编译执行JS的新阶段,仍然会存在低效率的代码。)

插入迭代器

如var name=values[i]; i++;前面两条语句可以写成var name=values[i++]

使用直接量
?
var  aTest = new Array();//替换为var aTest = [];var aTest = new Object;//替换为var aTest = {};var reg = new RegExp();//替换为var reg = /../;//如果要创建具有一些特性的一般对象,也可以使用字面量,如下:var oFruit = new O;oFruit.color = "red";oFruit.name = "apple";//前面的代码可用对象字面量来改写成这样:var oFruit = { color: "red", name: "apple"};


 使用DocumentFragment优化多次append

一旦需要更新DOM,请考虑使用文档碎片来构建DOM结构,然后再将其添加到现存的文档中。

for(var i = 0; i < 1000; i++) {    var el = document.createElement('p');    el.innerHTML = i;    document.body.appendChild(el);}//可以替换为:var frag = document.createDocumentFragment();for(var i = 0; i < 1000; i++) {    var el = document.createElement('p');    el.innerHTML = i;    frag.appendChild(el);}document.body.appendChild(frag);

对于大的DOM更改,使用innerHTML要比使用标准的DOM方法创建同样的DOM结构快得多。

var frag = document.createDocumentFragment();for(var i = 0; i < 1000; i++) {    var el = document.createElement('p');    el.innerHTML = i;    frag.appendChild(el);}document.body.appendChild(frag);//可以替换为:var html = [];for(var i = 0; i < 1000; i++) {    html.push('<p>'+ i + '</p>');}document.body.innerHTML = html.join('');

通常我们可能会使用字符串直接写HTML来创建节点,其实这样做,1无法保证代码的有效性2字符串操作效率低,所以应该是用document.createElement()方法,而如果文档中存在现成的样板节点,应该是用cloneNode()方法,因为使用createElement()方法之后,你需要设置多次元素的属性,使用cloneNode()则可以减少属性的设置次数——同样如果需要创建很多元素,应该先准备一个样板节点

var frag = document.createDocumentFragment();for(var i = 0; i < 1000; i++) {    var el = document.createElement('p');    el.innerHTML = i;    frag.appendChild(el);}document.body.appendChild(frag);//替换为:var frag = document.createDocumentFragment();var pEl = document.getElementsByTagName('p')[0];for(var i = 0; i < 1000; i++) {    var el = pEl.cloneNode(false);    el.innerHTML = i;    frag.appendChild(el);}document.body.appendChild(frag);

使用firstChild和nextSibling代替childNodes遍历dom元素

?

var nodes = element.childNodes;for(var i = 0, l = nodes.length; i < l; i++) {    var node = nodes[i];    //……}//可以替换为:var node = element.firstChild;while(node) {    //……    node = node.nextSibling;}

优化循环

可以使用下面几种方式来优化循环

减值迭代

大多数循环使用一个从0开始、增加到某个特定值的迭代器,在很多情况下,从最大值开始,在循环中不断减值的迭代器更加高效

简化终止条件

由于每次循环过程都会计算终止条件,所以必须保证它尽可能快,也就是说避免属性查找或者其它的操作,最好是将循环控制量保存到局部变量中,也就是说对数组或列表对象的遍历时,提前将length保存到局部变量中,避免在循环的每一步重复取值。

var list = document.getElementsByTagName('p');for(var i = 0; i < list.length; i++) {    //……}//替换为:var list = document.getElementsByTagName('p');for(var i = 0, l = list.length; i < l; i++) {    //……}

使用后测试循环

在JavaScript中,我们可以使用for(;;),while(),for(in)三种循环,事实上,这三种循环中for(in)的效率极差,因为他需要查询散列键,只要可以,就应该尽量少用。for(;;)和while循环,while循环的效率要优于for(;;),可能是因为for(;;)结构的问题,需要经常跳转回去。

?
var arr = [1, 2, 3, 4, 5, 6, 7];var sum = 0;for(var i = 0, l = arr.length; i < l; i++) {    sum += arr[i];}//可以考虑替换为:var arr = [1, 2, 3, 4, 5, 6, 7];var sum = 0, l = arr.length;while(l--) {    sum += arr[l];}

最常用的for循环和while循环都是前测试循环,而如do-while这种后测试循环,可以避免最初终止条件的计算,因此运行更快。

循环引用

如果循环引用中包含DOM对象或者ActiveX对象,那么就会发生内存泄露。内存泄露的后果是在浏览器关闭前,即使是刷新页面,这部分内存不会被浏览器释放。

简单的循环引用:

var el = document.getElementById('MyElement');var func = function() {    //…}el.func = func;func.element = el;

?

但是通常不会出现这种情况。通常循环引用发生在为dom元素添加闭包作为expendo的时候。

function init() {    var el = document.getElementById('MyElement');    el.onclick = function() {        //……    }}init();

init在执行的时候,当前上下文我们叫做context。这个时候,context引用了el,el引用了function,function引用了context。这时候形成了一个循环引用。

下面2种方法可以解决循环引用:

1)  置空dom对象

?
function init() {    var el = document.getElementById('MyElement');    el.onclick = function() {        //……    }}init();//可以替换为:function init() {    var el = document.getElementById('MyElement');    el.onclick = function() {        //……    }    el = null;}init();

将el置空,context中不包含对dom对象的引用,从而打断循环应用。

如果我们需要将dom对象返回,可以用如下方法:

function init() {    var el = document.getElementById('MyElement');    el.onclick = function() {        //……    }    returnel;}init();//可以替换为:function init() {    var el = document.getElementById('MyElement');    el.onclick = function() {        //……    }    try{        returnel;    } finally {        el = null;    }}init();

?

2)  构造新的context

?
function init() {    var el = document.getElementById('MyElement');    el.onclick = function() {        //……    }}init();//可以替换为:function elClickHandler() {    //……}function init() {    var el = document.getElementById('MyElement');    el.onclick = elClickHandler;}init();

 把function抽到新的context中,这样,function的context就不包含对el的引用,从而打断循环引用。

避免string的隐式装箱

对string的方法调用,比如'xxx'.length,浏览器会进行一个隐式的装箱操作,将字符串先转换成一个String对象。推荐对声明有可能使用String实例方法的字符串时,采用如下写法:

var myString = new String('Hello World');

性能方面的注意事项

1、尽量使用原生方法

2、switch语句相对if较快

通过将case语句按照最可能到最不可能的顺序进行组织

3、位运算较快

当进行数字运算时,位运算操作要比任何布尔运算或者算数运算快

4、巧用||和&&布尔运算符

?
function eventHandler(e) {    if(!e) e = window.event;}//可以替换为:function eventHandler(e) {    e = e || window.event;}if(myobj) {    doSomething(myobj);}//可以替换为:myobj && doSomething(myobj);

何时用单引号,何时用双引号

虽然在JavaScript当中,双引号和单引号都可以表示字符串, 为了避免混乱,我们建议在HTML中使用双引号,在JavaScript中使用单引号,但为了兼容各个浏览器,也为了解析时不会出错,定义JSON对象时,最好使用双引号

字符串连接。

如果是追加字符串,最好使用s+=anotherStr操作,而不是要使用s=s+anotherStr。

类型转换

1. 把数字转换成字符串,应用"" + 1,虽然看起来比较丑一点,但事实上这个效率是最高的,性能上来说:("" +) > String() > .toString() > new String() 

尽量使用编译时就能使用的内部操作要比运行时使用的用户操作要快。

String()属于内部函数,所以速度很快,而.toString()要查询原型中的函数,所以速度逊色一些,new String()用于返回一个精确的副本。

2. 浮点数转换成整型,这个更容易出错,很多人喜欢使用parseInt(),其实parseInt()是用于将字符串转换成数字,而不是浮点数和整型之间的转换,我们应该使用Math.floor()或者Math.round()。Math是内部对象,所以Math.floor()其实并没有多少查询方法和调用的时间,速度是最快的。

3. 对于自定义的对象,如果定义了toString()方法来进行类型转换的话,推荐显式调用toString(),因为内部的操作在尝试所有可能性之后,会尝试对象的toString()方法尝试能否转化为String,所以直接调用这个方法效率会更高

尽量作用JSON格式来创建对象,而不是var obj=new Object()方法。

因为前者是直接复制,而后者需要调用构造器,因而前者的性能更好。

对字符串进行循环操作,例如替换、查找,就使用正则表达式。

因为JS的循环速度比较慢,而正则表达式的操作是用C写成的API,性能比较好。

对于大的JS对象,因为创建时时间和空间的开销都比较大,因此应该尽量考虑采用缓存。

避免使用eval()方法

  eval()方法可以执行一段JavaScript代码,应该避免使用的原因:

  • 性能较差,它必须调用编译器来传递其参数,然后执行
  • 安全问题,因为它会执行传递给它的任何代码,所以容易受各种注入攻击,特别是在来源未知的时候
  • 不利于调试,eval的参数是动态产生的,调试起来不方便,可读性也较差
//Incorrect usage: Using eval to set a valueeval("myValue = myObject." + myKey + ";");//Correct usage: Using subscript notation to set a valuemyValue = myObject[myKey];

尽量减少DOM操作

  DOM是一个包含了很多信息的复杂的API,因此即使是很小的操作可能会花费较长的时间执行(如果要重绘页面的话)。为了提高程序性能,应尽量减少DOM操作,这里有一些建议:

1.减少DOM的数目

DOM节点的数目会影响与它相关的所有操作,要尽量使DOM树小一些:

  • 避免多余的标记和嵌套的表格
  • 元素数尽量控制在500个以内(document.getElementsByTagName('*').length)

2.缓存已经访问过的节点

当访问过一个DOM元素后,就应该把它缓存起来,因为你的程序往往要重复访问某个对象的,例如:

for (var i = 0; i < document.images.length; i++) {    document.images[i].src = "blank.gif";}

以上例子中,docum.images对象被访问了多次,这并不高效,因为每一次循环中,浏览器都要查找这个元素两次:第一次读取它的长度,第二次改变相应的src值。更好的做法是先把这个对象存储起来:

var imgs = document.images;for (var i = 0; i < imgs.length; i++) {  //当然也可以把 imgs.length 提前算出来,这里不是重点    imgs[i].src = "blank.gif";}

减少页面重绘

  在控制DOM元素数目的同时,你还可以通过减少修改元素(减少页面的重绘)的方法来提高性能。重绘有两种方式:repaintreflow

1.repaint,也叫redraw,即改变了元素的视觉效果,但是不影响它的排版(比如改变背景颜色)

2.reflow,会影响部分或者全部页面的排版,浏览器不仅要计算该元素的位置,还要计算它影响到的周围的元素位置

当你要改变页面布局的时候,reflow就发生了,主要有如下情况:

  • 增加或删除DOM节点
  • 改变元素的位置
  • 改变元素的尺寸(如margin,padding,border,font,width,height等)
  • 调整浏览器窗口的尺寸
  • 增加或删除css
  • 改变内容(如用户输入表单)
  • 命中css选择器(如hover)
  • 更改了class属性
  • 利用脚本更改了DOM
  • 检索一个必须被计算的尺寸(如offsetWidth,offsetHeight)
  • 设置了一个css属性

(检查某对象是否有某属性)

复制代码
 1  //method1 2      var myObject = { 3        name: '@tips_js' 4      }; 5      if (myObject.name) { } 6   7  //method2 8      var myObject = { 9        name: '@tips_js'10      };11  12      myObject.hasOwnProperty('name'); // true13      'name' in myObject; // true14  15      myObject.hasOwnProperty('valueOf'); // false, valueOf 继承自原型链16      'valueOf' in myObject; // true
复制代码

两者检查属性的深度不同,换言之hasOwnProperty只在本身有此属性时返回true,而in操作符不区分属性来自于本身或继承自原型链。

(优化嵌套的条件语句) 

面对大量的if-else语句

复制代码
 1  //method1 2      if (color) { 3          if (color === 'black') { 4              printBlackBackground(); 5          } else if (color === 'red') { 6              printRedBackground(); 7          } else if (color === 'blue') { 8              printBlueBackground(); 9          } else if (color === 'green') {10              printGreenBackground();11          } else {12              printYellowBackground();13          }14      }15      16  //method217      switch(color) {18          case 'black':19              printBlackBackground();20              break;21          case 'red':22              printRedBackground();23              break;24          case 'blue':25              printBlueBackground();26              break;27          case 'green':28              printGreenBackground();29              break;30          default:31              printYellowBackground();32      }33      34  //method335      switch(true) {36          case (typeof color === 'string' && color === 'black'):37              printBlackBackground();38              break;39          case (typeof color === 'string' && color === 'red'):40              printRedBackground();41              break;42          case (typeof color === 'string' && color === 'blue'):43              printBlueBackground();44              break;45          case (typeof color === 'string' && color === 'green'):46              printGreenBackground();47              break;48          case (typeof color === 'string' && color === 'yellow'):49              printYellowBackground();50              break;51      }52      53  //method454      var colorObj = {55          'black': printBlackBackground,56          'red': printRedBackground,57          'blue': printBlueBackground,58          'green': printGreenBackground,59          'yellow': printYellowBackground60      };61      if (color in colorObj) {62        colorObj[color]();63      }
复制代码

(测量javascript代码块性能的小知识)

快速的测量javascript的性能,我们可以使用console的方法,例如 

复制代码
1  console.time("Array initialize");2  var arr = new Array(100),3      len = arr.length,4      i;5  6  for (i = 0; i < len; i++) {7      arr[i] = new Object();8  };9  console.timeEnd("Array initialize"); // 0.711ms
复制代码

(更快的取整)

一个位操作符 ~ 将输入的32位的数字(input)转换为 -(input+1). 两个位操作符将输入(input)转变为 -(-(input + 1)+1) 是一个使结果趋向于0的取整好工具. 对于数字, 负数就像使用Math.ceil()方法而正数就像使用Math.floor()方法. 转换失败时,返回0,这在Math.floor()方法转换失败返回NaN时或许会派上用场。

复制代码
 1  // 单个 ~ 2  console.log(~1337)    // -1338 3   4  // 数字输入 5  console.log(~~47.11)  // -> 47 6  console.log(~~-12.88) // -> -12 7  console.log(~~1.9999) // -> 1 8  console.log(~~3)      // -> 3 9  10  // 转换失败11  console.log(~~[]) // -> 012  console.log(~~NaN)  // -> 013  console.log(~~null) // -> 014  15  // 大于32位整数时转换失败16  console.log(~~(2147483647 + 1) === (2147483647 + 1)) // -> 0
复制代码

Safe string concatenation

复制代码
 1  //method 1 2  var one = 1; 3  var two = 2; 4  var three = '3'; 5  var result = ''.concat(one, two, three); //"123" 6  //method 2 7  var one = 1; 8  var two = 2; 9  var three = '3';10  var result = one + two + three; //"33" instead of "123"
复制代码

拼接时使用加号,可能会导致意想不到的错误结果。

(返回对象,使方法可以链式调用)

复制代码
 1  function Person(name) { 2    this.name = name; 3   4    this.sayName = function() { 5      console.log("Hello my name is: ", this.name); 6      return this; 7    }; 8   9    this.changeName = function(name) {10      this.name = name;11      return this;12    };13  }14  15  var person = new Person("John");16  person.sayName().changeName("Timmy").sayName();
复制代码

 在面向对象的Javascript中为对象建立一个方法时,返回当前对象可以让你在一条链上调用方法。

(过滤并排序字符串列表)

你可能有一个很多名字组成的列表,需要过滤掉重复的名字并按字母表将其排序。

复制代码
1  var keywords = ['do', 'if', 'in', 'for', 'new', 'try', 'var', 'case', 'else', 'enum', 'null', 'this', 'true', 'void', 'with', 'break', 'catch', 'class', 'const', 'false', 'super', 'throw', 'while', 'delete', 'export', 'import', 'return', 'switch', 'typeof', 'default', 'extends', 'finally', 'continue', 'debugger', 'function', 'do', 'if', 'in', 'for', 'int', 'new', 'try', 'var', 'byte', 'case', 'char', 'else', 'enum', 'goto', 'long', 'null', 'this', 'true', 'void', 'with', 'break', 'catch', 'class', 'const', 'false', 'final', 'float', 'short', 'super', 'throw', 'while', 'delete', 'double', 'export', 'import', 'native', 'public', 'return', 'static', 'switch', 'throws', 'typeof', 'boolean', 'default', 'extends', 'finally', 'package', 'private', 'abstract', 'continue', 'debugger', 'function', 'volatile', 'interface', 'protected', 'transient', 'implements', 'instanceof', 'synchronized', 'do', 'if', 'in', 'for', 'let', 'new', 'try', 'var', 'case', 'else', 'enum', 'eval', 'null', 'this', 'true', 'void', 'with', 'break', 'catch', 'class', 'const', 'false', 'super', 'throw', 'while', 'yield', 'delete', 'export', 'import', 'public', 'return', 'static', 'switch', 'typeof', 'default', 'extends', 'finally', 'package', 'private', 'continue', 'debugger', 'function', 'arguments', 'interface', 'protected', 'implements', 'instanceof', 'do', 'if', 'in', 'for', 'let', 'new', 'try', 'var', 'case', 'else', 'enum', 'eval', 'null', 'this', 'true', 'void', 'with', 'await', 'break', 'catch', 'class', 'const', 'false', 'super', 'throw', 'while', 'yield', 'delete', 'export', 'import', 'public', 'return', 'static', 'switch', 'typeof', 'default', 'extends', 'finally', 'package', 'private', 'continue', 'debugger', 'function', 'arguments', 'interface', 'protected', 'implements', 'instanceof'];2  var filteredAndSortedKeywords = keywords3    .filter(function (keyword, index) {4        return keywords.lastIndexOf(keyword) === index;5      })6    .sort(function (a, b) {7        return a < b ? -1 : 1;8      });
复制代码

 因为我们不想改变我们的原始列表,所以我们准备用高阶函数叫做filter,它将基于我们传递的回调方法返回一个新的过滤后的数组。回调方法将比较当前关键字在原始列表里的索引和新列表中的索引,仅当索引匹配时将当前关键字push到新数组。

最后我们准备使用sort方法排序过滤后的列表,sort只接受一个比较方法作为参数,并返回按字母表排序后的列表。

复制代码
1  const filteredAndSortedKeywords = keywords2   .filter((keyword, index) => keywords.lastIndexOf(keyword) === index)3    .sort((a, b) => a < b ? -1 : 1);4  console.log(filteredAndSortedKeywords);5  // ['abstract', 'arguments', 'await', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do', 'double', 'else', 'enum', 'eval', 'export', 'extends', 'false', 'final', 'finally', 'float', 'for', 'function', 'goto', 'if', 'implements', 'import', 'in', 'instanceof', 'int', 'interface', 'let', 'long', 'native', 'new', 'null', 'package', 'private', 'protected', 'public', 'return', 'short', 'static', 'super', 'switch', 'synchronized', 'this', 'throw', 'throws', 'transient', 'true', 'try', 'typeof', 'var', 'void', 'volatile', 'while', 'with', 'yield']
复制代码

(JS中的短路求值)

短路求值是说, 只有当第一个运算数的值无法确定逻辑运算的结果时,才对第二个运算数进行求值:当AND(&&)的第一个运算数的值为false时,其结果必定为false;当OR(||)的第一个运算数为true时,最后结果必定为true。

逻辑或可以用来给参数设置默认值。

1  function theSameOldFoo(name){2      name = name || 'Bar' ;3      console.log("My best friend's name is " + name);4  }5  theSameOldFoo();  // My best friend's name is Bar6  theSameOldFoo('Bhaskar');  // My best friend's name is Bhaskar

 逻辑与可以用来避免调用undefined参数的属性时报错

复制代码
 1  var dog = { 2    bark: function(){ 3       console.log('Woof Woof'); 4     } 5  }; 6  // 调用 dog.bark(); 7  dog.bark(); // Woof Woof. 8  // 但是当dog未定义时,dog.bark() 将会抛出"Cannot read property 'bark' of undefined." 错误 9  // 防止这种情况,我们可以使用 &&.10  dog&&dog.bark();   // This will only call dog.bark(), if dog is defined.
复制代码

 

(避免修改和传递arguments给其他方法 — 影响优化)

在JavaScript的方法里,arguments参数可以让你访问传递给该方法的所有参数。arguments是一个类数组对象;arguments可是使用数组标记访问,而且它有length参数,但是它没有filter, map, forEach这样内建到数组内的方法。因此,如下代码是一个非常常见的将arguments转换为数组的办法:

1 var args = Array.prototype.slice.call(arguments);2 //或者3 var args = [].slice.call(arguments);

不幸的是,传递arguments给任何参数,将导致Chrome和Node中使用的V8引擎跳过对其的优化,这也将使性能相当慢。所以,正确的做法只有:

1 var args = new Array(arguments.length);2     for(var i = 0; i < args.length; ++i) {3         args[i] = arguments[i];4     }

 

不要使用delete运算符

删除操作比分配一个null属性慢很多。分配null在两个浏览器都快99%,但它不能修改对象的结构,但删除可以。

编辑:我认为这里有点误导,这并不意味着你不应该使用delete操作符,delete运算符有它自己的使用情况,它可以防止对象的内存泄漏。

不要以后再添加属性

尽量不要在以后再添加属性,最好从一开始就定义对象的架构。这在Firefox中快100%,在Chrome中快89%。

字符串联连

字符串联连是一个非常昂贵的操作,但是应该用什么方法呢?当然不是Array.prototype.join。

+=运算符似乎比+快很多,他们在两种浏览器上比String.prototype.concat和Array.prototype.join都更快。Array.prototype.join是最慢的,符合市场预期

你不需要所有的东西都用jQuery

大多数开发者使用jQuery做一些简单的任务,我的意思在一些场合你没有必要使用jQuery,你觉得用$.val()始终是必要的吗?就拿这个例子:

js 代码:
  1. $('input').keyup(function() {
  2.     if($(this).val() === 'blah') { ... }
  3. });

这是学习如何使用JavaScript修改DOM的最重要原因之一,这样你可以编写更高效的代码。

用纯JavaScript100%完成同样的功能100%的速度更快,这是JSPerf基准测试

js 代码:
  1. $('input').keyup(function() {
  2.   if(this.value === 'blah') { ... }
  3. });

 

避免不必要的属性查找
 
在JS中访问变量或数组都是O(1)操作,比访问对象上的属性更有效率,后者是一个O(n)操作。对象上的任何属性查找都要比访问变量或数组花费更长时间,因为必须在原型链中对拥有该名称的属性进行一次搜索,即属性查找越多,执行时间越长。所以针对需要多次用到对象属性,应将其存储在局部变量。
 

优化循环
 
循环是编程中最常见的结构,优化循环是性能优化过程中很重要的一部分。一个循环的基本优化步骤如下:
 
减值迭代——大多数循环使用一个从0开始,增加到某个特定值的迭代器。在很多情况下,从最大值开始,在循环中不断减值的迭代器更加有效。
简化终止条件——由于每次循环过程都会计算终止条件,故必须保证它尽可能快,即避免属性查找或其它O(n)的操作。
简化循环体——循环体是执行最多的,故要确保其被最大限度地优化。确保没有某些可以被很容易移出循环的密集计算。
使用后测试循环——最常用的for和while循环都是前测试循环,而如do-while循环可以避免最初终止条件的计算,因些计算更快。
for(var i = 0; i < values.length; i++) {
    process(values[i]);
}
优化1:简化终止条件
 
for(var i = 0, len = values.length; i < len; i++) {
    process(values[i]);
}
优化2:使用后测试循环(注意:使用后测试循环需要确保要处理的值至少有一个)
 
var i values.length - 1;
if(i > -1) {
    do {
        process(values[i]);
    }while(--i >= 0);
}
3.展开循环
 
当循环的次数确定时,消除循环并使用多次函数调用往往更快
当循环的次数不确定时,可以使用Duff装置来优化。Duff装置的基本概念是通过计算迭代的次数是否为8的倍数将一个循环展开为一系列语句。如下:
// Jeff Greenberg for JS implementation of Duff's Device
// 假设:values.length > 0
function process(v) {
    alert(v);
}
 
var values = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17];
var iterations = Math.ceil(values.length / 8);
var startAt = values.length % 8;
var i = 0;
 
do {
    switch(startAt) {
        case 0 : process(values[i++]);
        case 7 : process(values[i++]);
        case 6 : process(values[i++]);
        case 5 : process(values[i++]);
        case 4 : process(values[i++]);
        case 3 : process(values[i++]);
        case 2 : process(values[i++]);
        case 1 : process(values[i++]);
    }
    startAt = 0;
}while(--iterations > 0);
如上展开循环可以提升大数据集的处理速度。接下来给出更快的Duff装置技术,将do-while循环分成2个单独的循环。(注:这种方法几乎比原始的Duff装置实现快上40%。)
 
// Speed Up Your Site(New Riders, 2003)
function process(v) {
    alert(v);
}
 
var values = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17];
var iterations = Math.floor(values.length / 8);
var leftover = values.length % 8;
var i = 0;
 
if(leftover > 0) {
    do {
        process(values[i++]);
    }while(--leftover > 0);
}
 
do {
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
}while(--iterations > 0);
针对大数据集使用展开循环可以节省很多时间,但对于小数据集,额外的开销则可能得不偿失。

避免双重解释
 
当JS代码想解析JS代码时就会存在双重解释惩罚,当使用eval()函数或是Function构造函数以及使用setTimeout()传一个字符串时都会发生这种情况。如下
 
eval("alert('hello world');"); // 避免
var sayHi = new Function("alert('hello world');"); // 避免
setTimeout("alert('hello world');", 100);// 避免
以上代码是包含在字符串中的,即在JS代码运行的同时必须新启运一个解析器来解析新的代码。实例化一个新的解析器有不容忽视的开销,故这种代码要比直接解析要慢。以下这几个例子,除了极少情况下eval是必须的,应尽量避免使用上述。对于Function构造函数,直接写成一般的函数即可。对于setTimeout可以传入函数作为第一个参数。如下:
 
alert('hello world');
var sayHi = function() {
    alert('hello world');
};
setTimeout(function() {
    alert('hello world');
}, 100);
总之,若要提高代码性能,尽可能避免出现需要按照JS解释的代码。
多使用innerHTML

有两种在页面上创建DOM节点的方法:使用诸如createElement()和appendChild()之类的DOM方法,以及使用innerHTML。对于小的DOM更改,两者效率差不多,但对于大的DOM更改,innerHTML要比标准的DOM方法创建同样的DOM结构快得多。
 
当使用innerHTML设置为某个值时,后台会创建一个HTML解释器,然后使用内部的DOM调用来创建DOM结构,而非基于JS的DOM调用。由于内部方法是编译好的而非解释执行,故执行的更快。

----------------------------------------------------------------------------------------------------------------------------------------------------

01按强类型风格写代码


JS是弱类型的,但是写代码的时候不能太随意,写得太随意也体现了编码风格不好。下面分点说明:

(1)定义变量的时候要指明类型,告诉JS解释器这个变量是什么数据类型的,而不要让解释器去猜,例如不好的写法:

var num,    str,    obj;

声明了三个变量,但其实没什么用,因为解释器不知道它们是什么类型的,好的写法应该是这样的:

var num = 0,    str = '',    obj = null;

定义变量的时候就给他一个默认值,这样不仅方便了解释器,也方便了阅读代码的人,他会在心里有数——知道这些变量可能会当作什么用。


(2)不要随意地改变变量的类型,例如下面代码:

var num = 5;num = "-" + num;

第1行它是一个整型,第2行它变成了一个字符串。因为JS最终都会被解释成汇编的语言,汇编语言变量的类型肯定是要确定的,你把一个整型的改成了字符串,那解释器就得做一些额外的处理。并且这种编码风格是不提倡的,有一个变量第1行是一个整型,第10行变成了一个字符串,第20行又变成了一个object,这样就让阅读代码的人比较困惑,上面明明是一个整数,怎么突然又变成一个字符串了。好的写法应该是再定义一个字符串的变量:

var num = 5;var sign = "-" + num;

(3)函数的返回类型应该是要确定的,例如下面不确定的写法:

function getPrice(count){    if(count < 0) return "";    else return count * 100;}

getPrice这个函数有可能返回一个整数,也有可能返回一个空的字符串。这样写也不太好,虽然它是符合JS语法的,但这种编码风格是不好的。使用你这个函数的人会有点无所适从,不敢直接进行加减乘除,因为如果返回字符串进行运算的话值就是NaN了。函数的返回类型应该是要确定的,如下面是返回整型:

function getPrice(count){    if(count < 0) return -1;    else return count * 100;}

然后告诉使用者,如果返回-1就表示不合法。如果类型确定,解释器也不用去做一些额外的工作,可以加快运行速度。



02减少作用域查找


(1)不要让代码暴露在全局作用域下

例如以下运行在全局作用域的代码:

<script>    var map = document.querySelector("#my-map");    map.style.height = "600px";</script>

有时候你需要在页面直接写一个script,要注意在一个script标签里面,代码的上下文都是全局作用域的,由于全局作用域比较复杂,查找比较慢。例如上面的map变量,第二行在使用的时候,需要在全局作用域查找一下这个变量,假设map是在一个循环里面使用,那可能就会有效率问题了。所以应该要把它搞成一个局部的作用域:

<script>!function(){    var map = document.querySelector("#my-map");    map.style.height = "600px";}()</script>

上面用了一个function制造一个局部作用域,也可以用ES6的块级作用域。由于map这个变量直接在当前的局部作用域命中了,所以就不用再往上一级的作用域(这里是全局作用域)查找了,而局部作用域的查找是很快的。同时直接在全局作用域定义变量,会污染window对象。


(2)不要滥用闭包

闭包的作用在于可以让子级作用域使用它父级作用域的变量,同时这些变量在不同的闭包是不可见的。这样就导致了在查找某个变量的时候,如果当前作用域找不到,就得往它的父级作用域查找,一级一级地往上直到找到了,或者到了全局作用域还没找到。因此如果闭包嵌套得越深,那么变量查找的时间就越长。如下:

function getResult(count){    count++;    function process(){        var factor = 2;        return count * factor - 5;    }    return process();}

上面的代码定义了一个process函数,在这个函数里面count变量的查找时间要高于局部的factor变量。其实这里不太适合用闭包,可以直接把count传给process:

function getResult(count){    count++;    function process(count){        var factor = 2;        return count * factor - 5;    }    return process(count);}

这样count的查找时间就和factor一样,都是在当前作用域直接命中。这个就启示我们如果某个全局变量需要频繁地被使用的时候,可以用一个局部变量缓存一下,如下:

var url = "";if(window.location.protocal === "https:"){    url = "wss://xxx.com" + window.location.pathname + window.location.search;}

频繁地使用了window.location对象,所以可以先把它缓存一下:

var url = "";var location = window.location;if(location.protocal === "https:"){    url = "wss://xxx.com" + location.pathname + location.search;}

搞成了一个局变变量,这样查找就会明显快于全局的查找,代码也可以写少一点。



03避免==的使用


这里你可能会有疑问了,有些人喜欢用==,有些人喜欢用===,大家的风格不一样,你为什么要强制别人用===呢?习惯用==的人,不能仅仅是因为==比===少敲了一次键盘。为什么不提倡用==呢?

(1)如果你确定了变量的类型,那么就没必要使用==了,如下:

if(typeof num != "undefined"){} var num = parseInt(value);if(num == 10){}

上面的两个例子都是确定类型的,一个是字符串,一个是整数。就没必要使用==了,直接用===就可以了。


(2)如果类型不确定,那么应该手动做一下类型转换,而不是让别人或者以后的你去猜这里面有类型转换,如下:

var totalPage = "5";if(parseInt(totalPage) === 1){}


(3)使用==在JSLint检查的时候是不通过的:

if(a == b){}

如下JSLint的输出:

Expected ‘===’ and instead saw ‘==’.


if(a == b){


(4)并且使用==可能会出现一些奇怪的现象,这些奇怪的现象可能会给代码埋入隐患:

null == undefined          //true'' == '0'                  //false0  == ''                   //true0  == '0'                  //true' \t\r\n ' == 0            //truenew String("abc") == "abc" //truenew Boolean(true) == true  //truetrue == 1                  //true

上面的比较在用===的时候都是false,这样才是比较合理的。例如第一点null居然会等于undefined,就特别地奇怪,因为null和undefined是两个毫无关系的值,null应该是作为初始化空值使用,而undefined是用于检验某个变量是否未定义。


这和第1点介绍强类型的思想是相通的。



04 合并表达式


如果用1句代码就可以实现5句代码的功能,那往往1句代码的执行效率会比较高,并且可读性可能会更好


(1)用三目运算符取代简单的if-else

如上面的getPrice函数:

function getPrice(count){    if(count < 0) return -1;    else return count * 100;}

可以改成:

function getPrice(count){    return count < 0 ? return -1 : count * 100;}

这个比写一个if-else看起来清爽多了。当然,如果你写了if-else,压缩工具也会帮你把它改三目运算符的形式:

function getPrice(e){return 0>e?-1:100*e}


(2)连等

连等是利用赋值运算表达式会返回所赋的值,并且执行顺序是从右到左的,如下:

overtime = favhouse = listingDetail = {...}

有时候你会看到有人这样写:

var age = 0;if((age = +form.age.value) >= 18){    console.log("你是成年人");} else {    consoe.log("小朋友,你还有" + (18 - age) + "就成年了");}

也是利用了赋值表达式会返回一个值,在if里面赋值的同时用它的返回值做判断,然后else里面就已经有值了。上面的+号把字符串转成了整数。


(3)自增

利用自增也可以简化代码。如下,每发出一条消息,localMsgId就自增1:

chatService.sendMessage(localMsgId++, msgContent);


05减少魔数


例如,在某个文件的第800行,冒出来了一句:

dialogHandler.showQuestionNaire("seller", "sell", 5, true);

就会让人很困惑了,上面的四个常量分别代表什么呢,如果我不去查一个那个函数的变量说明就不能够很快地意会到这些常量分别有什么用。这些意义不明的常量就叫“魔数”。


所以最好还是给这些常量取一个名字,特别是在一些比较关键的地方。例如上面的代码可改成:

var naireType = "seller",    dialogType = "sell",    questionsCount = 5,    reloadWindow = true;naireHandler.showNaire(naireType, dialogType, questionsCount, reloadWindow);

这样意义就很明显了。



06 使用ES6简化代码


ES6已经发展很多年了,兼容性也已经很好了。恰当地使用,可以让代码更加地简洁优雅。


(1)使用箭头函数取代小函数

有很多使用小函数的场景,如果写个function,代码起码得写3行,但是用箭头函数一行就搞定了,例如实现数组从大到小排序:

var nums = [4, 8, 1, 9, 0];nums.sort(function(a, b){    return b - a;});//输出[9, 8, 4, 1, 0]

如果用箭头函数,排序只要一行就搞定了:

var nums = [4, 8, 1, 9, 0];nums.sort(a, b => b - a);

代码看起来简洁多了,还有setTimeout里面经常会遇到只要执行一行代码就好了,写个function总感觉有点麻烦,用字符串的方式又不太好,所以这种情况用箭头函数也很方便:

setTimeout(() => console.log("hi"), 3000)

箭头函数在C++/Java等其它语言里面叫做Lambda表达式,Ruby比较早就有这种语法形式了,后来C++/Java也实现了这种语法。


当然箭头函数或者Lambda表达式不仅适用于这种一行的,多行代码也可以,不过在一行的时候它的优点才比较明显。


(2)使用ES6的class

虽然ES6的class和使用function的prototype本质上是一样的,都是用的原型。但是用class可以减少代码量,同时让代码看起来更加地高大上,使用function要写这么多:

function Person(name, age){    this.name = name;    this.age = age;}Person.prototype.addAge = function(){    this.age++;};Person.prototype.setName = function(name){    this.name = name;};

使用class代码看加地简洁易懂:

class Person{    constructor(name, age){        this.name = name;        this.age = age;    }    addAge(){        this.age++;    }    setName(name){        this.name = name;    }}

并且class还可以很方便地实现继承、静态的成员函数,就不需要自己再去通过一些技巧去实现了。


(3)字符串拼接

以前要用+号拼接:

var tpl =     '<div>' +     '    <span>1</span>' +    '</div>';

现在只要用两个反引号“`”就可以了:

var tpl = `   <div>        <span>1</span>    </div>`;

另外反引号还支持占位替换,原本你需要:

var page = 5,    type = encodeURIComponet("#js");var url = "/list?page=" + page + "&type=" + type;

现在只需要:

var url = `/list?page=${page}&type=${type}`;

就不用使用+号把字符串拆散了。


(4)块级作用域变量

块级作用域变量也是ES6的一个特色,下面的代码是一个任务队列的模型抽象:

var tasks = [];for(var i = 0; i < 4; i++){    tasks.push(function(){        console.log("i is " + i);    });}for(var j = 0; j < tasks.length; j++){    tasks[j]();}

但是上面代码的执行输出是4,4,4,4,并且不是想要输出:0,1,2,3,所以每个task就不能取到它的index了,这是因为闭包都是用的同一个i变量,i已经变成4了,所以执行闭包的时候就都是4了。那怎么办呢?可以这样解决:

var tasks = [];for(var i = 0; i < 4; i++){    !function(k){        tasks.push(function(){            console.log("i is " + k);        });    }(i);}for(var j = 0; j < tasks.length; j++){    tasks[j]();}

把i赋值给了k,由于k它是一个function的一个参数,每次执行函数的时候,肯定会实例化新的k,所以每次的k都是不同的变量,这样就输出就正常了。


但是代码看起来有点别扭,如果用ES6,只要把var改成let就可以了:

var tasks = [];for(let i = 0; i <= 4; i++){    tasks.push(function(){        console.log("i is " + i);    });}for(var j = 0; j < tasks.length; j++){    tasks[j]();}

只改动了3个字符就达到了目的。因为for循环里面有个大括号,大括号就是一个独立的作用域,let定义的变量在独立的作用域里面它的值也是独立的。当然即使没写大括号for循环执行也是独立的。





转载:

https://mp.weixin.qq.com/s/5dAjzaTgbhZUpZ6w-juU8Q

http://www.cnblogs.com/platycoden/p/5208657.html

原创粉丝点击