歡迎光臨
每天分享高質量文章

JavaScript效能最佳化小知識總結

前言

一直在學習javascript,也有看過《犀利開發Jquery核心詳解與實踐》,對這本書的評價只有兩個字犀利,可能是對javascript理解的還不夠透徹異或是自己太笨,更多的是自己不擅於思考懶得思考以至於裡面說的一些精髓都沒有太深入的理解。

鑒於想讓自己有一個提升,進不了一個更加廣闊的天地,總得找一個屬於自己的居所好好生存,所以平時會有意無意的去積累一些使用jQuerry的常用知識,特別是對於效能要求這一塊,總是會想是不是有更好的方式來實現。

下麵是我總結的一些小技巧,僅供參考。(我先會說一個總標題,然後用一小段話來說明這個意思 再最後用一個demo來簡單言明)

避免全域性查詢

在一個函式中會用到全域性物件儲存為區域性變數來減少全域性查詢,因為訪問區域性變數的速度要比訪問全域性變數的速度更快些

function search() {

//當我要使用當前頁面地址和主機域名

alert(window.location.href + window.location.host);

}

//最好的方式是如下這樣 先用一個簡單變數儲存起來

function search() {

var location = window.location;

alert(location.href + location.host);

}

定時器

如果針對的是不斷執行的程式碼,不應該使用setTimeout,而應該是用setInterval,因為setTimeout每一次都會初始化一個定時器,而setInterval只會在開始的時候初始化一個定時器

var timeoutTimes = 0;

function timeout() {

timeoutTimes++;

if (timeoutTimes < 10) {

setTimeout(timeout, 10);

}

}

timeout();

//可以替換為:

var intervalTimes = 0;

function interval() {

intervalTimes++;

if (intervalTimes >= 10) {

clearInterval(interv);

}

}

var interv = setInterval(interval, 10);

字串連線

如果要連線多個字串,應該少使用+=,如

s+=a;

s+=b;

s+=c;

應該寫成s+=a + b + c;

而如果是收集字串,比如多次對同一個字串進行+=操作的話,最好使用一個快取,使用JavaScript陣列來收集,最後使用join方法連線起來

var buf = [];

for (var i = 0; i < 100; i++) {

buf.push(i.toString());

}

var all = buf.join(“”);

避免with陳述句

和函式類似 ,with陳述句會建立自己的作用域,因此會增加其中執行的程式碼的作用域鏈的長度,由於額外的作用域鏈的查詢,在with陳述句中執行的程式碼肯定會比外面執行的程式碼要慢,在能不使用with陳述句的時候儘量不要使用with陳述句。

with (a.b.c.d) {

property1 = 1;

property2 = 2;

}

//可以替換為:

var obj = a.b.c.d;

obj.property1 = 1;

obj.property2 = 2;

數字轉換成字串

般最好用”” + 1來將數字轉換成字串,雖然看起來比較醜一點,但事實上這個效率是最高的,效能上來說:

(“” +) > String() > .toString() > new String()

浮點數轉換成整型

很多人喜歡使用parseInt(),其實parseInt()是用於將字串轉換成數字,而不是浮點數和整型之間的轉換,我們應該使用Math.floor()或者Math.round()

各種型別轉換

var myVar = “3.14159”,

str = “” + myVar, // to string

i_int = ~ ~myVar, // to integer

f_float = 1 * myVar, // to float

b_bool = !!myVar, /* to boolean – any string with length

and any number except 0 are true */

array = [myVar]; // to array

如果定義了toString()方法來進行型別轉換的話,推薦顯式呼叫toString(),因為內部的操作在嘗試所有可能性之後,會嘗試物件的toString()方法嘗試能否轉化為String,所以直接呼叫這個方法效率會更高

多個型別宣告

在JavaScript中所有變數都可以使用單個var陳述句來宣告,這樣就是組合在一起的陳述句,以減少整個指令碼的執行時間,就如上面程式碼一樣,上面程式碼格式也挺規範,讓人一看就明瞭。

插入迭代器

如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);

使用一次innerHTML賦值代替構建dom元素

對於大的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(‘

‘ + i + ‘

‘);

}

document.body.innerHTML = html.join(”);

透過模板元素clone,替代createElement

很多人喜歡在JavaScript中使用document.write來給頁面生成內容。事實上這樣的效率較低,如果需要直接插入HTML,可以找一個容器元素,比如指定一個div或者span,並設定他們的innerHTML來將自己的HTML程式碼插入到頁面中。通常我們可能會使用字串直接寫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;

刪除DOM節點

刪除dom節點之前,一定要刪除註冊在該節點上的事件,不管是用observe方式還是用attachEvent方式註冊的事件,否則將會產生無法回收的記憶體。另外,在removeChild和innerHTML=’’二者之間,儘量選擇後者. 因為在sIEve(記憶體洩露監測工具)中監測的結果是用removeChild無法有效地釋放dom節點

使用事件代理

任何可以冒泡的事件都不僅僅可以在事件標的上進行處理,標的的任何祖先節點上也能處理,使用這個知識就可以將事件處理程式附加到更高的地方負責多個標的的事件處理,同樣,對於內容動態增加並且子節點都需要相同的事件處理函式的情況,可以把事件註冊提到父節點上,這樣就不需要為每個子節點註冊事件監聽了。另外,現有的js庫都採用observe方式來建立事件監聽,其實現上隔離了dom物件和事件處理函式之間的迴圈取用,所以應該儘量採用這種方式來建立事件監聽

重覆使用的呼叫結果,事先儲存到區域性變數

//避免多次取值的呼叫開銷

var h1 = element1.clientHeight + num1;

var h2 = element1.clientHeight + num2;

//可以替換為:

var eleHeight = element1.clientHeight;

var h1 = eleHeight + num1;

var h2 = eleHeight + num2;

註意NodeList

最小化訪問NodeList的次數可以極大的改進指令碼的效能

var images = document.getElementsByTagName(‘img’);

for (var i = 0, len = images.length; i < len; i++) {

}

編寫JavaScript的時候一定要知道何時傳回NodeList物件,這樣可以最小化對它們的訪問

  • 進行了對getElementsByTagName()的呼叫
  • 獲取了元素的childNodes屬性
  • 獲取了元素的attributes屬性
  • 訪問了特殊的集合,如document.forms、document.images等等

要瞭解了當使用NodeList物件時,合理使用會極大的提升程式碼執行速度

最佳化迴圈

可以使用下麵幾種方式來最佳化迴圈

  • 減值迭代

大多數迴圈使用一個從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這種後測試迴圈,可以避免最初終止條件的計算,因此執行更快。

展開迴圈

當迴圈次數是確定的,消除迴圈並使用多次函式呼叫往往會更快。

避免雙重解釋

如果要提高程式碼效能,盡可能避免出現需要按照JavaScript解釋的字串,也就是

  • 儘量少使用eval函式

使用eval相當於在執行時再次呼叫解釋引擎對內容進行執行,需要消耗大量時間,而且使用Eval帶來的安全性問題也是不容忽視的。

不要使用Function建構式

不要給setTimeout或者setInterval傳遞字串引數

var num = 0;

setTimeout(‘num++’, 10);

//可以替換為:

var num = 0;

function addNum() {

num++;

}

setTimeout(addNum, 10);

縮短否定檢測

if (oTest != ‘#ff0000’) {

//do something

}

if (oTest != null) {

//do something

}

if (oTest != false) {

//do something

}

//雖然這些都正確,但用邏輯非運運算元來操作也有同樣的效果:

if (!oTest) {

//do something

}

條件分支

  • 將條件分支,按可能性順序從高到低排列:可以減少直譯器對條件的探測次數
  • 在同一條件子的多(>2)條件分支時,使用switch優於if:switch分支選擇的效率高於if,在IE下尤為明顯。4分支的測試,IE下switch的執行時間約為if的一半。
  • 使用三目運運算元替代條件分支

if (a > b) {

num = a;

} else {

num = b;

}

//可以替換為:

num = a > b ? a : b;

使用常量

  • 重覆值:任何在多處用到的值都應該抽取為一個常量
  • 使用者介面字串:任何用於顯示給使用者的字串,都應該抽取出來以方便國際化
  • URLs:在Web應用中,資源位置很容易變更,所以推薦用一個公共地方存放所有的URL
  • 任意可能會更改的值:每當你用到字面量值的時候,你都要問一下自己這個值在未來是不是會變化,如果答案是“是”,那麼這個值就應該被提取出來作為一個常量。

避免與null進行比較

由於JavaScript是弱型別的,所以它不會做任何的自動型別檢查,所以如果看到與null進行比較的程式碼,嘗試使用以下技術替換

  • 如果值應為一個取用型別,使用instanceof運運算元檢查其建構式
  • 如果值應為一個基本型別,作用typeof檢查其型別
  • 如果是希望物件包含某個特定的方法名,則使用typeof運運算元確保指定名字的方法存在於物件上

避免全域性量

全域性變數應該全部字母大寫,各單詞之間用_下劃線來連線。盡可能避免全域性變數和函式, 儘量減少全域性變數的使用,因為在一個頁面中包含的所有JavaScript都在同一個域中執行。所以如果你的程式碼中宣告了全域性變數或者全域性函式的話,後面的程式碼中載入的指令碼檔案中的同名變數和函式會改寫掉(overwrite)你的。

//糟糕的全域性變數和全域性函式

var current = null;

function init(){

//…

}

function change() {

//…

}

function verify() {

//…

}

//解決辦法有很多,Christian Heilmann建議的方法是:

//如果變數和函式不需要在“外面”取用,那麼就可以使用一個沒有名字的方法將他們全都包起來。

(function(){

var current = null;

function init() {

//…

}

function change() {

//…

}

function verify() {

//…

}

})();

//如果變數和函式需要在“外面”取用,需要把你的變數和函式放在一個“名稱空間”中

//我們這裡用一個function做名稱空間而不是一個var,因為在前者中宣告function更簡單,而且能保護隱私資料

myNameSpace = function() {

var current = null;

function init() {

//…

}

function change() {

//…

}

function verify() {

//…

}

//所有需要在名稱空間外呼叫的函式和屬性都要寫在return裡面

return {

init: init,

//甚至你可以為函式和屬性命名一個別名

set: change

};

};

尊重物件的所有權

因為JavaScript可以在任何時候修改任意物件,這樣就可以以不可預計的方式覆寫預設的行為,所以如果你不負責維護某個物件,它的物件或者它的方法,那麼你就不要對它進行修改,具體一點就是說:

  • 不要為實體或原型新增屬性
  • 不要為實體或者原型新增方法
  • 不要重定義已經存在的方法
  • 不要重覆定義其它團隊成員已經實現的方法,永遠不要修改不是由你所有的物件,你可以透過以下方式為物件建立新的功能:
  • 建立包含所需功能的新物件,並用它與相關物件進行互動
  • 建立自定義型別,繼承需要進行修改的型別,然後可以為自定義型別新增額外功能

迴圈取用

如果迴圈取用中包含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 () {

//……

}

return el;

}

init();

//可以替換為:

function init() {

var el = document.getElementById(‘MyElement’);

el.onclick = function () {

//……

}

try {

return el;

} 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的取用,從而打斷迴圈取用。

透過javascript建立的dom物件,必須append到頁面中

IE下,指令碼建立的dom物件,如果沒有append到頁面中,掃清頁面,這部分記憶體是不會回收的!

function create() {

var gc = document.getElementById(‘GC’);

for (var i = 0; i < 5000; i++) {

var el = document.createElement(‘div’);

el.innerHTML = “test”;

//下麵這句可以註釋掉,看看瀏覽器在任務管理器中,點選按鈕然後掃清後的記憶體變化

gc.appendChild(el);

}

}

釋放dom元素佔用的記憶體

將dom元素的innerHTML設定為空字串,可以釋放其子元素佔用的記憶體。

在rich應用中,使用者也許會在一個頁面上停留很長時間,可以使用該方法釋放積累得越來越多的dom元素使用的記憶體。

釋放javascript物件

在rich應用中,隨著實體化物件數量的增加,記憶體消耗會越來越大。所以應當及時釋放對物件的取用,讓GC能夠回收這些記憶體控制元件。

物件:obj = null

物件屬性:delete obj.myproperty

陣列item:使用陣列的splice方法釋放陣列中不用的item

避免string的隱式裝箱

對string的方法呼叫,比如’xxx’.length,瀏覽器會進行一個隱式的裝箱操作,將字串先轉換成一個String物件。推薦對宣告有可能使用String實體方法的字串時,採用如下寫法:

var myString = new String(‘Hello World’);

鬆散耦合

1、解耦HTML/JavaScript

JavaScript和HTML的緊密耦合:直接寫在HTML中的JavaScript、使用包含行內程式碼的

贊(0)

分享創造快樂