Javascript是一門非常靈活的語言,我們可以隨心所欲的書寫各種風格的程式碼,不同風格的程式碼也必然也會導致執行效率的差異,開發過程中零零散散地接觸到許多提升程式碼效能的方法,整理一下平時比較常見且容易規避的問題
Javascript本身執行效率
Javascript中的作用域鏈、閉包、原型繼承、eval等特性,在提供各種神奇功能的同時也帶來了各種效率問題,用之不慎就會導致執行效率低下。
我們在編碼過程中多多少少會使用到一些全域變數(window,document,自訂全域變數等等),了解javascript作用域鏈的人都知道,在局部作用域中存取全域變數需要一層一層遍歷整個作用域鏈直至頂級作用域,而局部變數的存取效率則會更快更高,因此在局部作用域中高頻率使用一些全域物件時可以將其導入到局部作用域中,例如:
//1、作为参数传入模块 (function(window,$){ var xxx = window.xxx; $("#xxx1").xxx(); $("#xxx2").xxx(); })(window,jQuery); //2、暂存到局部变量 function(){ var doc = document; var global = window.global; }
我們都知道eval可以將一段字串當做js程式碼來執行處理,據說使用eval執行的程式碼比不使用eval的程式碼慢100倍以上(具體效率我沒有測試,有興趣同學可以測試一下)
JavaScript 程式碼在執行前會進行類似「預先編譯」的操作:首先會建立一個目前執行環境下的活動對象,並將那些以var 申明的變數設為活動物件的屬性,但此時這些變數的賦值都是undefined,並將那些以function 定義的函數也加入為活動物件的屬性,而且它們的值正是函數的定義。但是,如果你使用了“eval”,則“eval”中的程式碼(實際上為字串)無法預先識別其上下文,無法事先解析和最佳化,即無法進行預編譯的操作。所以,其效能也會大幅降低
其實作在大家一般都很少會用eval了,這裡我想說的是兩個類別eval的場景(new Function{}
,setTimeout
,setInterver
)
setTimtout("alert(1)",1000); setInterver("alert(1)",1000); (new Function("alert(1)"))();
上述幾種型別程式碼執行效率都會比較低,因此建議直接傳入匿名方法、或方法的引用給setTimeout方法
var f = (function(){ var a = {name:"var3"}; var b = ["var1","var2"]; var c = document.getElementByTagName("li"); //****其它变量 //***一些运算 var res = function(){ alert(a.name); } return res; })()
上述程式碼中變數f的回傳值是由一個立即執行函數構成的閉包中傳回的方法res,該變數保留了對於這個閉包中所有變數(a,b,c等)的引用,因此這兩個變數會一直駐留在記憶體空間中,尤其是對於dom元素的引用對記憶體的消耗會很大,而我們在res中只使用到了a變數的值,因此,在閉包返回之前我們可以將其它變數釋放
var f = (function(){ var a = {name:"var3"}; var b = ["var1","var2"]; var c = document.getElementByTagName("li"); //****其它变量 //***一些运算 //闭包返回前释放掉不再使用的变量 b = c = null; var res = function(){ alert(a.name); } return res; })()
Js操作dom的效率
在web開發過程中,前端執行效率的瓶頸往往都是在dom操作上面,dom操作是一件很耗性能的事情,如何在dom操作過程中盡量節約效能呢?
當 DOM 元素的屬性改變 (如 color) 時, 瀏覽器會通知 render 重新描繪對應的元素, 此程序稱為 repaint。
如果該次變更涉及元素佈局(如width), 瀏覽器則拋棄原有屬性, 重新計算並把結果傳遞給render 以重新描繪頁面元素, 此過程稱為reflow。
先將元素從document中刪除,完成修改後再把元素放回原來的位置(當對某元素及其子元素進行大量reflow操作時,1,2兩種方法效果才會比較明顯)
#將元素的display設為”none”,完成修改後再把display修改為原來的值
修改多個樣式屬性時定義class類別代替多次修改style屬性(for certain同學推薦)
大量新增元素到頁面時使用documentFragment
#例如
for(var i=0;i<100:i++){ var child = docuemnt.createElement("li"); child.innerHtml = "child"; document.getElementById("parent").appendChild(child); }
上述程式碼會多次操作dom,效率比較低,可以改為下面的形式創建documentFragment,將所有元素加入到docuemntFragment不會改變dom結構,最後將其添加到頁面,只進行了一次reflow
#var frag = document.createDocumentFragment(); for(var i=0;i<100:i++){ var child = docuemnt.createElement("li"); child.innerHtml = "child"; frag.appendChild(child); } document.getElementById("parent").appendChild(frag);
当代码中需要多次访问元素的状态信息,在状态不变的情况下我们可以将其暂存到变量中,这样可以避免多次访问dom带来内存的开销,典型的例子就是:
var lis = document.getElementByTagName("li"); for(var i=1;i<lis.length;i++){ //*** } 上述方式会在每一次循环都去访问dom元素,我们可以简单将代码优化如下 var lis = document.getElementByTagName("li"); for(var i=1,j=lis.length ;i<j;i++){ //*** }
查找dom元素时尽量避免大面积遍历页面元素,尽量使用精准选择器,或者指定上下文以缩小查找范围,以jquery为例
少用模糊匹配的选择器:例如$("[name*='_fix']")
,多用诸如id以及逐步缩小范围的复合选择器$("li.active")
等
指定上下文:例如$("#parent .class")
,$(".class",$el)
等
使用场景:一个有大量记录的列表,每条记录都需要绑定点击事件,在鼠标点击后实现某些功能,我们通常的做法是给每条记录都绑定监听事件,这种做法会导致页面会有大量的事件监听器,效率比较低下。
基本原理:我们都知道dom规范中事件是会冒泡的,也就是说在不主动阻止事件冒泡的情况下任何一个元素的事件都会按照dom树的结构逐级冒泡至顶端。而event对象中也提供了event.target(IE下是srcElement)指向事件源,因此我们即使在父级元素上监听该事件也可以找到触发该事件的最原始的元素,这就是委托的基本原理。废话不多说,上示例
$("ul li").bind("click",function(){ alert($(this).attr("data")); })
上述写法其实是给所有的li元素都绑定了click事件来监听鼠标点击每一个元素的事件,这样页面上会有大量的事件监听器。
根据上面介绍的监听事件的原理我们来改写一下
$("ul").bind("click",function(e){ if(e.target.nodeName.toLowerCase() ==="li"){ alert($(e.target).attr("data")); } })
这样一来,我们就可以只添加一个事件监听器去捕获所有li上触发的事件,并做出相应的操作。
当然,我们不必每次都做事件源的判断工作,可以将其抽象一下交给工具类来完成。jquery中的delegate()方法就实现了该功能
语法是这样的$(selector).delegate(childSelector,event,data,function)
,例如:
$("p").delegate("button","click",function(){ $("p").slideToggle(); });
参数说明(引自w3school)
参数 | 描述 |
---|---|
childSelector | 必需。规定要附加事件处理程序的一个或多个子元素。 |
event | 必需。规定附加到元素的一个或多个事件。由空格分隔多个事件值。必须是有效的事件。 |
data | 可选。规定传递到函数的额外数据。 |
function | 必需。规定当事件发生时运行的函数。 |
Tips:事件委托还有一个好处就是,即使在事件绑定之后动态添加的元素上触发的事件同样可以监听到哦,这样就不用在每次动态加入元素到页面后都为其绑定事件了
以上是簡單介紹JavaScript的執行效率的詳細內容。更多資訊請關注PHP中文網其他相關文章!