如何改進javascript代碼的性能
本來在那片編寫可維護性代碼文章后就要總結(jié)這篇代碼性能文章的,耽擱了幾天,本來也是決定每天都要更新一篇文章的,因為以前欠下太多東西沒總結(jié),學(xué)過的東西沒去總結(jié)真的很快就忘記了,記錄一下在你腦力留下更深的印象,特別是這些可維護性代碼,性能什么的,當在你腦子里形成一種習慣了,那你就牛了!這里也要給初學(xué)者一個建議:多總結(jié)你學(xué)過的東西,因為這其實也是在學(xué)習新知識! 好,進入我們的主題:如何提高JS代碼的性能。
1.優(yōu)化DOM交互
DOM與我們的頁面緊密相關(guān),瀏覽器渲染頁面也就是在渲染解析后的DOM元素,DOM操作與交互要消耗大量的時間,因為它們往往需要重新渲染整個頁面或者一部分。進一步說,看似細微的一些操作也可能需要花很多時間來執(zhí)行,因為DOM要處理的信息非常多,因此我們應(yīng)該盡可能地優(yōu)化與DOM相關(guān)的操作,加快瀏覽器對頁面的渲染!為什么有些DOM操作會影響頁面性能,可以查看我寫的一些關(guān)于瀏覽器原理的文章:
ok,優(yōu)化DOM操作,我們主要有一些幾種方式:
1.1 最小化現(xiàn)場更新
什么是DOM的現(xiàn)場更新:需要對DOM部分已經(jīng)顯示的頁面的一部分的顯示立即更新。但是,每一個更改,不管是插入單個字符,還是一處整個片段,都有一定的性能懲罰,因為瀏覽器需要重新計算無數(shù)尺寸以進行更新(相關(guān)知識請閱讀:)。所以,現(xiàn)場更新進行的越多,代碼執(zhí)行所花的時間就越長,反之代碼執(zhí)行越快,如下:
var list = document.getElementById('mylist'), item, i; for(i = 0; i < 10; i++){ item = document.creatElement('li'); list.appendChild(item); item.appendChild(document.creatTextNode('item' + i)); }
這段代碼為列表mylist添加了10個項目,沒添加一個項目都要進行2次的現(xiàn)場更新:添加元素和添加文本節(jié)點,所以這個操作一個需要完成20個現(xiàn)場更新,每個更新都會損失性能,可見這樣的代碼運行起來是相對緩慢的。
解決的方法是使用文檔碎片間接地更改DOM元素:
var list = document.getElementById('mylist'), fragment = document.creatDocumentFragment(), item, i; for(i = 0; i < 10; i++){ item = document.creatElement('li'); fragment .appendChild(item); item.appendChild(document.creatTextNode('item' + i)); } list.appendChild(fragment);
像這樣的代碼只需進行一次的現(xiàn)場更新。記住,當給appendChild()傳入文檔碎片是,只有文檔碎片中的子節(jié)點才會被添加到目標元素,碎片本身不會被添加。
現(xiàn)在,你應(yīng)該明白你用循環(huán)直接進行DOM節(jié)點的增刪查改是多么對不起瀏覽器的事了吧 `(∩_∩)′ 。
1.2 使用 innerHTML
除了上面代碼中使用的creatElement() 和 appendChild()結(jié)合的方法創(chuàng)建DOM元素之外,還有通過給innerHTML賦值來創(chuàng)建。對于小的DOM更改而言,兩種方法的效率其實差不多,但對于大量的DOM節(jié)點的更改,后者要比前者快得多!為啥捏?
因為當我們給innerHTML賦值時,后臺會創(chuàng)建一個HTML解析器,然后使用內(nèi)部的DOM調(diào)用來創(chuàng)建DOM結(jié)構(gòu),而非基于Javascript的DOM調(diào)用,由于內(nèi)部方法是編譯好的而非解釋執(zhí)行的,所以執(zhí)行代碼的速度要快很多!
用innerHTML改寫上面的例子:
var list = document.getElementById('mylist'), html = '', //聲明一個空字符串 i; for(i = 0; i < 10; i++){ html += '<li>item' + i + '</li>'; } list.innerHTML = html; // 這里記得innerHTML后面的HTML四個字母都要大寫!
這種方式同樣也只進行了一次的現(xiàn)場更新,并且性能要比上一種方式要好!雖然在字符串的鏈接上有點性能損失。
1.3 使用事件代理/事件委托
事件處理程序為web應(yīng)用提供交互能力,因此許多開發(fā)人員會不分青紅皂白地向頁面中添加大量的處理程序,有個問題就是一個頁面上的事件處理程序數(shù)量將直接關(guān)系到頁面的整體運行性能。為什么捏?
首先,事件處理程序?qū)?yīng)至少一個函數(shù),JS中每個函數(shù)都是對象,都會占用內(nèi)存,內(nèi)存中的對象越多,性能就越差。
其次,我們必須事先指定所有事件處理程序,這就導(dǎo)致了DOM訪問次數(shù)增多,會延遲整個頁面的交互就緒時間,頁面響應(yīng)用戶操作變得相對緩慢。
所以減少事件處理程序同樣也可以讓我們的頁面更牛暢!使用事件委托勢在必得啊!
事件委托的原理其實就是事件冒泡,只指定一個事件處理程序就可以管理某一類型操作的所有事件。例如:click事件會一直冒泡到document層次,也就是說我們不必為每個元素添加事件,只需在較高的層次的元素上添加事件處理程序即可,然后利用事件對象(event)的屬性或方法去判斷當前點擊的元素,然后做出相應(yīng)的響應(yīng)。這個我就不展開講了,初學(xué)者可以自行查閱事件冒泡知識。
2.作用域很重要
說到作用域啊就很容易想到作用域鏈(scope chain),我們知道要搜索一個變量,所在的執(zhí)行環(huán)境都要沿著這條作用域向上搜索這個變量,作用域鏈上有很多的變量,那么我們就得遍歷,遍歷就需要時間啊,而且你越往上查找所需時間越多,如果我們能減少這個時間,我們代碼執(zhí)行效率不是可以提高了嗎?
好聰明啊,ok,我看看有哪些方式可以減少這個時間:
2.1 避免全局查找
這是性能優(yōu)化的一重點,上面也說了,越往上查找時間越多,也就是說查找全局變量和函數(shù)比局部要多!看代碼:
function updateUI(){ var imgs = document.getElementByTagName('img'); for(var i = 0 ,lng = imgs.length;i < lng;i ++){ imgss[i].title = document.title + 'image' + i; } var msg = docuement.getElementById('msg'); msg.innerHTML = 'update complete.'; }
這代碼很正常呀!我之前也經(jīng)常這么做滴。但是我們細心可以發(fā)現(xiàn),這段代碼有三處引用了全局變量document,如果我們的頁面很多圖片,那么在for循環(huán)中的document就會被執(zhí)行上百次,而每次都要需要在作用域鏈中查找,時間都去哪了,我還沒......停!。
我們可以通過在函數(shù)中創(chuàng)建一個局部變量保存對document的引用,這樣,我們在函數(shù)里任何地方引用document都不用跑到全局變量去找了。這樣就改進了代碼的性能,看代碼:
function updateUI(){ var doc = document; // 將document保存在局部變量doc中 var imgs = doc.getElementByTagName('img'); for(var i = 0 ,lng = imgs.length;i < lng;i ++){ imgss[i].title = doc.title + 'image' + i; } var msg = doc.getElementById('msg'); msg.innerHTML = 'update complete.'; }
所以啊,我們在開發(fā)中,如果在函數(shù)中會經(jīng)常用到全局變量,把它保存在局部變量中!
2.2 避免使用with語句
用with語句延長了作用域,查找變量同樣費時間,這個我們一般不會用到,所以不展開了。解決方法還是和上面的例子一樣,將全局變量保存在局部變量中!
3.優(yōu)化循環(huán)
循環(huán)在編程中可謂家常便飯,在js中也隨處可見,循環(huán)體會反復(fù)地執(zhí)行同一段代碼,執(zhí)行時間一直累加,所以能夠?qū)ρh(huán)體的代碼進行優(yōu)化也可以大大減少執(zhí)行時間!如何優(yōu)化?四種方式。
3.1 減值迭代
我們寫迭代器(循環(huán)條件)的時候一般都這樣(var i = 0;i < 10;i ++),從0開始,增加到某個特定值。然而在很多情況下,如果在循環(huán)中使用減值迭代器效率更高。我測試了下,如果循環(huán)體不復(fù)雜的話,兩者差不多!
//增值迭代 --效率較低 for(var i = 0;i < items.length;i++){ doSomething(items[i]); } //減值迭代 --效率較高 for(var i = items.length - 1;i >= 0;i--){ doSomething(items[i]); }
3.2 簡化終止條件
由于每次循環(huán)都會計算終止條件,所以必須保證它的執(zhí)行盡可能地塊。這里主要是避免其他DOM元素及其屬性的的查找。
//看終止條件,每次循環(huán)都需要查詢items及其length屬性 for(var i = 0;i < items.length;i++){ doSomething(items[i]); } //將items.length的值保存在局部變量lng中。 for(var i = 0,lng = items.length;i < lng;i++){ doSomething(items[i]); }
3.3 簡化循環(huán)體
原因和上面以上的,所以在循環(huán)體內(nèi)避免大量的密集的操作。
這其實和上面講的:1.1 最小化現(xiàn)場更新 。是一樣的優(yōu)化方式??梢缘够厝タ纯?。
4.基本的算法優(yōu)化
在計算機中,算法的復(fù)雜度用O表示。下面是javascript中幾種常見的算法類型:
O(1) :常數(shù),不管有多少值,執(zhí)行的時間都是恒定的,比如簡單值和存儲在變量中的值。
O(log n):對數(shù),總的執(zhí)行時間和數(shù)量有關(guān),但不一定要獲取每一個值,如:二分法查找
O(n) :線性,總執(zhí)行時間和數(shù)量直接相關(guān),如:遍歷
O(n*n) :平方,總執(zhí)行時間和數(shù)量有關(guān),每個值至少獲取N次,如:插入排序
ok,有了上面的知識,我們就可以對javascript進行一些算法上的優(yōu)化了??创a:
var value = 5; var sum = value + 10; alert(sum);
這段代碼進行了4次常量值的查找:數(shù)字5,變量value,數(shù)字10,變量sum,這段代碼的算法復(fù)雜度就是O(1)。又如:
var value = [10,5]; var sum = value[0] + value[1]; alert(sum);
在javascript中訪問數(shù)組元素也是一個O(1)操作,和簡單的變量查找效率一樣。再看:
var value = {one:10,two:10}; var sum = value.one + value.two; alert(sum);
要表達的是訪問對象上的屬性要比訪問數(shù)組和變量的效率低。因為這是一個O(n)操作。你需要在對象的原型鏈中查找該屬性,所花時間較多。
好了,看完這個是不是感覺眼前一片光明啊。其實我們前面所講的要把經(jīng)常用到的全局屬性保存在一個局部變量中就是根據(jù)這個原理了,訪問全局屬性是一個O(n)的操作,而訪問變量是一個O(1)的操作,大聲告訴我,挖掘機哪家強??!
5.最小化語句數(shù)
前面講的優(yōu)化差不多都是和精簡優(yōu)化語句有關(guān)的,是的,我覺得代碼的質(zhì)量和數(shù)量就是性能的評判標準。前面講了一些代碼質(zhì)量相關(guān)的優(yōu)化,這里就講講代碼數(shù)量的優(yōu)化。
5.1 精簡變量聲明
//用了5條語句聲明5個變量 var count = 5; var color = 'red'; var values = [1,2,3]; var now = new Date(); //用了1條語句聲明5個變量,注意每個變量用逗號隔開 var count = 5, color = 'red', values = [1,2,3], now = new Date();
5.2 使用數(shù)組和對象字面量
// 創(chuàng)建兩個對象 ----不好的方式 //one 四條語句 var values = new Array(); values[0] = 123; values[1] = 456; values[2] = 789; //two 四條語句 var person = new Object(); person.name = 'jozo'; person.age = 21; person.sayName = function(){ alert(this.name); }; // 創(chuàng)建兩個對象 ----推薦的方式 //one 1條語句 var values = [123,456,789] //two 1條語句 var person = { name : 'jozo', age : 21, sayName : function(){ alert(this.name); };
6.其他
寫累了,如有不正確的地方請指正哦,還有一些其他的優(yōu)化,下次文章繼續(xù)!
- 優(yōu)化JavaScript腳本的性能的幾個注意事項
- JavaScript腳本性能的優(yōu)化方法
- JavaScript腳本性能優(yōu)化注意事項
- JavaScript 字符串連接性能優(yōu)化
- JavaScript性能優(yōu)化 創(chuàng)建文檔碎片(document.createDocumentFragment)
- javascript中的關(guān)于類型轉(zhuǎn)換的性能優(yōu)化
- javascript模版引擎-tmpl的bug修復(fù)與性能優(yōu)化分析
- js性能優(yōu)化 如何更快速加載你的JavaScript頁面
- 高性能Javascript筆記 數(shù)據(jù)的存儲與訪問性能優(yōu)化
- web性能優(yōu)化之javascript性能調(diào)優(yōu)
- 有關(guān)javascript的性能優(yōu)化 (repaint和reflow)
- JavaScript中的無阻塞加載性能優(yōu)化方案
相關(guān)文章
JS判斷元素是否在數(shù)組內(nèi)的實現(xiàn)代碼
這篇文章主要介紹了JS判斷元素是否在數(shù)組內(nèi)的實現(xiàn)代碼,需要的朋友可以參考下2016-03-03基于JS實現(xiàn)移動端訪問PC端頁面時跳轉(zhuǎn)到對應(yīng)的移動端網(wǎng)頁
不想通過CSS自適應(yīng)在PC端和移動端分別顯示不同的樣式,那么只能通過在移動端訪問PC端網(wǎng)頁時跳轉(zhuǎn)到對應(yīng)的移動端網(wǎng)頁了,那么怎么跳轉(zhuǎn)呢,網(wǎng)上也有很多文章說明,以下實現(xiàn)思路經(jīng)過小編測試過,需要的朋友可以參考下2016-04-04前端無感知刷新token以及超時自動退出實現(xiàn)方案
前端需要做到無感刷新token,即刷token時要做到用戶無感知,避免頻繁登錄,下面這篇文章主要給大家介紹了關(guān)于前端無感知刷新token以及超時自動退出的實現(xiàn)方案,需要的朋友可以參考下2024-01-01JS獲取數(shù)組中出現(xiàn)次數(shù)最多及第二多元素的方法
這篇文章主要介紹了JS獲取數(shù)組中出現(xiàn)次數(shù)最多及第二多元素的方法,涉及javascript針對數(shù)組的遍歷、排序、判斷、查詢等相關(guān)操作技巧,需要的朋友可以參考下2017-10-10微信小程序中的店鋪評分組件及vue中用svg實現(xiàn)的評分顯示組件
這篇文章主要介紹了微信小程序之店鋪評分組件及vue中用svg實現(xiàn)的評分顯示組件,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2018-11-11js實現(xiàn)翻頁后保持checkbox選中狀態(tài)的實現(xiàn)方法
在項目中有需求如下:上下分頁后,選中的checkbox狀態(tài)保持不變2012-11-11js 將canvas生成圖片保存,或直接保存一張圖片的實現(xiàn)方法
下面小編就為大家分享一篇js 將canvas生成圖片保存,或直接保存一張圖片的實現(xiàn)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01