看了一下以前发布的文章,关于前端性能优化的,有网站前端性能优化之javascript和css及jquery的 jquery编程的标准写法和最佳实践 。前端性能优化是一个不断追求的过程,前面的文章虽然讲解了一些性能优化,可是关于性能优化的路还有很长,不少东西都没有说起。今天,我在前面的基础之上,再来简单总结一些经常使用的性能优化方式。javascript
一、使用运算符时,尽可能使用+=,-=、*=、\=等运算符号,而不是直接进行赋值运算。css
二、位运算。前端
当进行数学运算时位运算较快,位运算操做要比任何布尔运算或算数运算快,如取模,逻辑与和逻辑或也能够考虑用位运算来替换。java
有同窗问,常见的js位运算符有哪些?常见的位运算符有“~,&,|,^,.<<,>>,>>>”等等。jquery
关于位运算的应用,我前面也有文章说起,js运算符单竖杠“|”的用法和做用是什么?以及javascript实用技巧,js小知识你们能够去看看。web
一、switch语句。ajax
如有一系列复杂的if-else语句,能够转换成单个switch语句则能够获得更快的代码,还能够经过将case语句按照最可能的到最不可能的顺序进行组织,来进一步优化。编程
例如:json
function getCategory(age) { var category = ""; switch (true) { case isNaN(age): category = "not an age"; break; case (age >= 50): category = "Old"; break; case (age <= 20): category = "Baby"; break; default: category = "Young"; break; }; return category; } getCategory(5); //Baby
这样的稍微复杂一些的,咱们尽可能就不用if/else了,固然,简单的判断,仍是推荐if/else。数组
二、减小页面的重绘
个人jquery文章优化中,说起了这一项。
代码以下:
var str = "<div>这是一个测试字符串</div>"; /**效率低**/ var obj = document.getElementsByTagName("body"); for(var i = 0; i < 100; i++){ obj.innerHTML += str + i; } /**效率高**/ var obj = document.getElementsByTagName("body"); var arr = []; for(var i = 0; i < 100; i++){ arr[i] = str + i; } obj.innerHTML = arr.join("");
三、传递方法取代方法字符串
一些方法例如setTimeout()、setInterval(),接受字符串或者方法实例做为参数。直接传递方法对象做为参数来避免对字符串的二次解析。
传递方法
setTimeout(test, 1);//good
传递方法字符串
setTimeout('test()', 1);//不能说bad,只能说not good
四、使用原始操做代替方法调用
方法调用通常封装了原始操做,在性能要求高的逻辑中,可使用原始操做代替方法调用来提升性能。
原始操做
var min = a<b?a:b; //good
方法实例
var min = Math.min(a, b);//not good
五、定时器
若是针对的是不断运行的代码,不该该使用setTimeout,而应该是用setInterval。setTimeout每次要从新设置一个定时器。
六、最小化语句数
例如:
多个变量声明
/**不提倡**/ var i = 1; var j = "hello"; var arr = [1,2,3]; var now = new Date(); /**提倡**/ var i = 1, j = "hello", arr = [1,2,3], now = new Date();
插入迭代值
/**不提倡**/ var name = values[i]; i++; /**提倡**/ var name = values[i++];
使用数组和对象字面量,避免使用构造函数Array(),Object()
/**不提倡**/ var a = new Array(); a[0] = 1; a[1] = "hello"; a[2] = 45; var o = new Obejct(); o.name = "bill"; o.age = 13; /**提倡**/ var a = [1, "hello", 45]; var o = { name : "bill", age : 13 };
一、把数字转换成字符串。
应用""+1,效率是最高。
性能上来讲:""+字符串>String()>.toString()>new String()。
String()属于内部函数,因此速度很快。 .toString()要查询原型中的函数,因此速度略慢。 new String()最慢。
二、浮点数转换成整型。
错误使用使用parseInt()。
parseInt()是用于将字符串转换成数字,而不是浮点数和整型之间的转换。
应该使用Math.floor()或者Math.round()。
Math是内部对象,因此Math.floor()其实并无多少查询方法和调用的时间,速度是最快的。
一、定义变量,避免每次获取
/**效率低**/ var divs = document.getElementsByTagName("div"); for(var i = 0; i < divs.length; i++){ ... } /**效率高,适用于获取DOM集合,若是纯数组则两种状况区别不大**/ var divs = document.getElementsByTagName("div"); for(var i = 0, len = divs.length; i < len; i++){ ... }
二、避免在循环中使用try-catch。
try-catch-finally语句在catch语句被执行的过程当中会动态构造变量插入到当前域中,对性能有必定影响。
若是须要异常处理机制,能够将其放在循环外层使用。
循环外使用try-catch
try { for ( var i = 0; i < 200; i++) {} } catch (e){}
三、避免遍历大量元素,尽可能缩小遍历范围。
一、做用域。
做用域(scope)是JAVASCRIPT编程中一个重要的运行机制,在JAVASCRIPT同步和异步编程以及JAVASCRIPT内存管理中起着相当重要的做用。 在JAVASCRIPT中,能造成做用域的有以下几点。
函数的调用
with语句
with会建立自已的做用域,所以会增长其中执行代码的做用域的长度。
全局做用域。
如下代码为例:
var foo = function() { var local = {}; }; foo(); console.log(local); //=> undefined var bar = function() { local = {}; }; bar(); console.log(local); //=> {} /**这里咱们定义了foo()函数和bar()函数,他们的意图都是为了定义一个名为local的变量。在foo()函数中,咱们使用var语句来声明定义了一个local变量,而由于函数体内部会造成一个做用域,因此这个变量便被定义到该做用域中。并且foo()函数体内并无作任何做用域延伸的处理,因此在该函数执行完毕后,这个local变量也随之被销毁。而在外层做用域中则没法访问到该变量。而在bar()函数内,local变量并无使用var语句进行声明,取而代之的是直接把local做为全局变量来定义。故外层做用域能够访问到这个变量。**/ local = {}; // 这里的定义等效于 global.local = {};
二、做用域链
在JAVASCRIPT编程中,会遇到多层函数嵌套的场景,这就是典型的做用域链的表示。
function foo() { var val = 'hello'; function bar() { function baz() { global.val = 'world;' }; baz(); console.log(val); //=> hello }; bar(); }; foo(); /**在`JAVASCRIPT`中,变量标识符的查找是从当前做用域开始向外查找,直到全局做用域为止。因此`JAVASCRIPT`代码中对变量的访问只能向外进行,而不能逆而行之。baz()函数的执行在全局做用域中定义了一个全局变量val。而在bar()函数中,对val这一标识符进行访问时,按照从内到外的查找原则:在bar函数的做用域中没有找到,便到上一层,即foo()函数的做用域中查找。然而,使你们产生疑惑的关键就在这里:本次标识符访问在foo()函数的做用域中找到了符合的变量,便不会继续向外查找,故在baz()函数中定义的全局变量val并无在本次变量访问中产生影响。**/
三、减小做用域链上的查找次数
/**效率低**/ for(var i = 0; i < 10000; i++){ var but1 = document.getElementById("but1"); } /**效率高**/ /**避免全局查找**/ var doc = document; for(var i = 0; i < 10000; i++){ var but1 = doc.getElementById("but1"); } /**上面代码中,第二种状况是先把全局对象的变量放到函数里面先保存下来,而后直接访问这个变量,而第一种状况是每次都遍历做用域链,直到全局环境,咱们看到第二种状况实际上只遍历了一次,而第一种状况倒是每次都遍历了,并且这种差异在多级做用域链和多个全局变量的状况下还会表现的很是明显。在做用域链查找的次数是`O(n)`。经过建立一个指向`document`的局部变量,就能够经过限制一次全局查找来改进这个函数的性能。**/
四、闭包
JAVASCRIPT中的标识符查找遵循从内到外的原则。
function foo() { var local = 'Hello'; return function() { return local; }; } var bar = foo(); console.log(bar()); //=> Hello /**这里所展现的让外层做用域访问内层做用域的技术即是闭包(Closure)。得益于高阶函数的应用,使foo()函数的做用域获得`延伸`。foo()函数返回了一个匿名函数,该函数存在于foo()函数的做用域内,因此能够访问到foo()函数做用域内的local变量,并保存其引用。而因这个函数直接返回了local变量,因此在外层做用域中即可直接执行bar()函数以得到local变量。**/
闭包是JAVASCRIPT的高级特性,由于把带有内部变量引用的函数带出了函数外部,因此该做用域内的变量在函数执行完毕后的并不必定会被销毁,直到内部变量的引用被所有解除。因此闭包的应用很容易形成内存没法释放的状况。
良好的闭包管理。
循环事件绑定、私有属性、含参回调等必定要使用闭包时,并谨慎对待其中的细节。
循环绑定事件,咱们假设一个场景:有六个按钮,分别对应六种事件,当用户点击按钮时,在指定的地方输出相应的事件。 var btns = document.querySelectorAll('.btn'); // 6 elements var output = document.querySelector('#output'); var events = [1, 2, 3, 4, 5, 6]; // Case 1 for (var i = 0; i < btns.length; i++) { btns[i].onclick = function(evt) { output.innerText += 'Clicked ' + events[i]; }; } /**这里第一个解决方案显然是典型的循环绑定事件错误,这里不细说,详细能够参照我给一个网友的回答;而第二和第三个方案的区别就在于闭包传入的参数。**/ // Case 2 for (var i = 0; i < btns.length; i++) { btns[i].onclick = (function(index) { return function(evt) { output.innerText += 'Clicked ' + events[index]; }; })(i); } /**第二个方案传入的参数是当前循环下标,然后者是直接传入相应的事件对象。事实上,后者更适合在大量数据应用的时候,由于在JavaScript的函数式编程中,函数调用时传入的参数是基本类型对象,那么在函数体内获得的形参会是一个复制值,这样这个值就被看成一个局部变量定义在函数体的做用域内,在完成事件绑定以后就能够对events变量进行手工解除引用,以减轻外层做用域中的内存占用了。并且当某个元素被删除时,相应的事件监听函数、事件对象、闭包函数也随之被销毁回收。**/ // Case 3 for (var i = 0; i < btns.length; i++) { btns[i].onclick = (function(event) { return function(evt) { output.innerText += 'Clicked ' + event; }; })(events[i]); }
避开闭包陷阱
闭包是个强大的工具,但同时也是性能问题的主要诱因之一。不合理的使用闭包会致使内存泄漏。
闭包的性能不如使用内部方法,更不如重用外部方法。
因为IE 9浏览器的DOM节点做为COM对象来实现,COM的内存管理是经过引用计数的方式,引用计数有个难题就是循环引用,一旦DOM引用了闭包(例如event handler),闭包的上层元素又引用了这个DOM,就会形成循环引用从而致使内存泄漏。
善用函数
使用一个匿名函数在代码的最外层进行包裹。
;(function() { // 主业务代码 })();
有的甚至更高级一点:
;(function(win, doc, $, undefined) { // 主业务代码 })(window, document, jQuery);
甚至连如RequireJS, SeaJS, OzJS 等前端模块化加载解决方案,都是采用相似的形式:
/**RequireJS**/ define(['jquery'], function($) { // 主业务代码 }); /**SeaJS**/ define('module', ['dep', 'underscore'], function($, _) { // 主业务代码 });
在制做网页过程当中,用的比较多的地方,咱们一般封装成函数。在封装函数的时候,善用运用回调函数。
例子以下:
function getData(callBack){ $.ajax({ url:"", data:{}, dataType:"json", type:"get", success:function(data){ callBack(null,data) } }) }
咱们在调用的时候能够以下:
getData(function(error,data){ console.log(data) })
向一个数组中插入元素是平时很常见的一件事情。你可使用push在数组尾部插入元素,能够用unshift在数组头部插入元素,也能够用splice在数组中间插入元素。 可是这些已知的方法,并不意味着没有更加高效的方法。
尾部插入元素
咱们有2个数组
var arr = [1,2,3,4,5]; var arr2 = [];
测试以下:
arr[arr.length] = 6; // 最快 arr.push(6); // 慢34.66% arr2 = arr.concat([6]); // 慢85.79%
前面插入元素
var arr = [1,2,3,4,5]; arr.unshift(0); [0].concat(arr);
发现:
[0].concat(arr); // 快 arr.unshift(0); // 慢64.70%
向数组中间添加元素
使用splice能够简单的向数组中间添加元素,这也是最高效的方法。
var items = ['one', 'two', 'three', 'four']; items.splice(items.length / 2, 0, 'hello');