前端性能優(yōu)化補(bǔ)充篇

前言
看了一下之前發(fā)布的文章,關(guān)于前端性能優(yōu)化的,有網(wǎng)站前端性能優(yōu)化之javascript和css及jquery的 jquery編程的標(biāo)準(zhǔn)寫法和最佳實(shí)踐 。前端性能優(yōu)化是一個(gè)不斷追求的過程,前面的文章雖然講解了一些性能優(yōu)化,但是關(guān)于性能優(yōu)化的路還有很長(zhǎng),很多東西都沒有提及。今天,我在前面的基礎(chǔ)之上,再來簡(jiǎn)單總結(jié)一些常用的性能優(yōu)化方式。
運(yùn)算符
1、使用運(yùn)算符時(shí),盡量使用+=,-=、*=、\=等運(yùn)算符號(hào),而不是直接進(jìn)行賦值運(yùn)算。
2、位運(yùn)算。
當(dāng)進(jìn)行數(shù)學(xué)運(yùn)算時(shí)位運(yùn)算較快,位運(yùn)算操作要比任何布爾運(yùn)算或算數(shù)運(yùn)算快,如取模,邏輯與和邏輯或也可以考慮用位運(yùn)算來替換。
有同學(xué)問,常見的js位運(yùn)算符有哪些?常見的位運(yùn)算符有“~,&,|,^,.<<,>>,>>>”等等。
關(guān)于位運(yùn)算的應(yīng)用,我前面也有文章提及,js運(yùn)算符單豎杠“|”的用法和作用是什么?以及javascript實(shí)用技巧,js小知識(shí)大家可以去看看。
常規(guī)優(yōu)化
1、switch語句。
若有一系列復(fù)雜的if-else語句,可以轉(zhuǎn)換成單個(gè)switch語句則可以得到更快的代碼,還可以通過將case語句按照最可能的到最不可能的順序進(jìn)行組織,來進(jìn)一步優(yōu)化。
例如:
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
這樣的稍微復(fù)雜一些的,我們盡量就不用if/else了,當(dāng)然,簡(jiǎn)單的判斷,還是推薦if/else。
2、減少頁面的重繪
我的jquery文章優(yōu)化中,提及了這一項(xiàng)。
代碼如下:
var str = "<div>這是一個(gè)測(cè)試字符串</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("");
3、傳遞方法取代方法字符串
一些方法例如setTimeout()、setInterval(),接受字符串或者方法實(shí)例作為參數(shù)。直接傳遞方法對(duì)象作為參數(shù)來避免對(duì)字符串的二次解析。
傳遞方法
setTimeout(test, 1);//good
傳遞方法字符串
setTimeout('test()', 1);//不能說bad,只能說not good
4、使用原始操作代替方法調(diào)用
方法調(diào)用一般封裝了原始操作,在性能要求高的邏輯中,可以使用原始操作代替方法調(diào)用來提高性能。
原始操作
var min = a<b?a:b; //good
方法實(shí)例
var min = Math.min(a, b);//not good
5、定時(shí)器
如果針對(duì)的是不斷運(yùn)行的代碼,不應(yīng)該使用setTimeout,而應(yīng)該是用setInterval。setTimeout每次要重新設(shè)置一個(gè)定時(shí)器。
6、最小化語句數(shù)
例如:
多個(gè)變量聲明
/**不提倡**/ 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++];
使用數(shù)組和對(duì)象字面量,避免使用構(gòu)造函數(shù)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 };
類型轉(zhuǎn)換
1、把數(shù)字轉(zhuǎn)換成字符串。
應(yīng)用""+1,效率是最高。
性能上來說:""+字符串>String()>.toString()>new String()。
String()屬于內(nèi)部函數(shù),所以速度很快。 .toString()要查詢?cè)椭械暮瘮?shù),所以速度略慢。 new String()最慢。
2、浮點(diǎn)數(shù)轉(zhuǎn)換成整型。
錯(cuò)誤使用使用parseInt()。
parseInt()是用于將字符串轉(zhuǎn)換成數(shù)字,而不是浮點(diǎn)數(shù)和整型之間的轉(zhuǎn)換。
應(yīng)該使用Math.floor()或者M(jìn)ath.round()。
Math是內(nèi)部對(duì)象,所以Math.floor()其實(shí)并沒有多少查詢方法和調(diào)用的時(shí)間,速度是最快的。
循環(huán)
1、定義變量,避免每次獲取
/**效率低**/ var divs = document.getElementsByTagName("div"); for(var i = 0; i < divs.length; i++){ ... } /**效率高,適用于獲取DOM集合,如果純數(shù)組則兩種情況區(qū)別不大**/ var divs = document.getElementsByTagName("div"); for(var i = 0, len = divs.length; i < len; i++){ ... }
2、避免在循環(huán)中使用try-catch。
try-catch-finally語句在catch語句被執(zhí)行的過程中會(huì)動(dòng)態(tài)構(gòu)造變量插入到當(dāng)前域中,對(duì)性能有一定影響。
如果需要異常處理機(jī)制,可以將其放在循環(huán)外層使用。
循環(huán)外使用try-catch
try { for ( var i = 0; i < 200; i++) {} } catch (e){}
3、避免遍歷大量元素,盡量縮小遍歷范圍。
作用域鏈和閉包優(yōu)化
1、作用域。
作用域(scope)是JAVASCRIPT編程中一個(gè)重要的運(yùn)行機(jī)制,在JAVASCRIPT同步和異步編程以及JAVASCRIPT內(nèi)存管理中起著至關(guān)重要的作用。 在JAVASCRIPT中,能形成作用域的有如下幾點(diǎn)。
函數(shù)的調(diào)用 with語句 with會(huì)創(chuàng)建自已的作用域,因此會(huì)增加其中執(zhí)行代碼的作用域的長(zhǎng)度。 全局作用域。
以下代碼為例:
var foo = function() { var local = {}; }; foo(); console.log(local); //=> undefined var bar = function() { local = {}; }; bar(); console.log(local); //=> {} /**這里我們定義了foo()函數(shù)和bar()函數(shù),他們的意圖都是為了定義一個(gè)名為local的變量。在foo()函數(shù)中,我們使用var語句來聲明定義了一個(gè)local變量,而因?yàn)楹瘮?shù)體內(nèi)部會(huì)形成一個(gè)作用域,所以這個(gè)變量便被定義到該作用域中。而且foo()函數(shù)體內(nèi)并沒有做任何作用域延伸的處理,所以在該函數(shù)執(zhí)行完畢后,這個(gè)local變量也隨之被銷毀。而在外層作用域中則無法訪問到該變量。而在bar()函數(shù)內(nèi),local變量并沒有使用var語句進(jìn)行聲明,取而代之的是直接把local作為全局變量來定義。故外層作用域可以訪問到這個(gè)變量。**/ local = {}; // 這里的定義等效于 global.local = {};
2、作用域鏈
在JAVASCRIPT編程中,會(huì)遇到多層函數(shù)嵌套的場(chǎng)景,這就是典型的作用域鏈的表示。
function foo() { var val = 'hello'; function bar() { function baz() { global.val = 'world;' }; baz(); console.log(val); //=> hello }; bar(); }; foo(); /**在`JAVASCRIPT`中,變量標(biāo)識(shí)符的查找是從當(dāng)前作用域開始向外查找,直到全局作用域?yàn)橹?。所以`JAVASCRIPT`代碼中對(duì)變量的訪問只能向外進(jìn)行,而不能逆而行之。baz()函數(shù)的執(zhí)行在全局作用域中定義了一個(gè)全局變量val。而在bar()函數(shù)中,對(duì)val這一標(biāo)識(shí)符進(jìn)行訪問時(shí),按照從內(nèi)到外的查找原則:在bar函數(shù)的作用域中沒有找到,便到上一層,即foo()函數(shù)的作用域中查找。然而,使大家產(chǎn)生疑惑的關(guān)鍵就在這里:本次標(biāo)識(shí)符訪問在foo()函數(shù)的作用域中找到了符合的變量,便不會(huì)繼續(xù)向外查找,故在baz()函數(shù)中定義的全局變量val并沒有在本次變量訪問中產(chǎn)生影響。**/
3、減少作用域鏈上的查找次數(shù)
/**效率低**/ 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"); } /**上面代碼中,第二種情況是先把全局對(duì)象的變量放到函數(shù)里面先保存下來,然后直接訪問這個(gè)變量,而第一種情況是每次都遍歷作用域鏈,直到全局環(huán)境,我們看到第二種情況實(shí)際上只遍歷了一次,而第一種情況卻是每次都遍歷了,而且這種差別在多級(jí)作用域鏈和多個(gè)全局變量的情況下還會(huì)表現(xiàn)的非常明顯。在作用域鏈查找的次數(shù)是`O(n)`。通過創(chuàng)建一個(gè)指向`document`的局部變量,就可以通過限制一次全局查找來改進(jìn)這個(gè)函數(shù)的性能。**/
4、閉包
JAVASCRIPT中的標(biāo)識(shí)符查找遵循從內(nèi)到外的原則。
function foo() { var local = 'Hello'; return function() { return local; }; } var bar = foo(); console.log(bar()); //=> Hello /**這里所展示的讓外層作用域訪問內(nèi)層作用域的技術(shù)便是閉包(Closure)。得益于高階函數(shù)的應(yīng)用,使foo()函數(shù)的作用域得到`延伸`。foo()函數(shù)返回了一個(gè)匿名函數(shù),該函數(shù)存在于foo()函數(shù)的作用域內(nèi),所以可以訪問到foo()函數(shù)作用域內(nèi)的local變量,并保存其引用。而因這個(gè)函數(shù)直接返回了local變量,所以在外層作用域中便可直接執(zhí)行bar()函數(shù)以獲得local變量。**/
閉包是JAVASCRIPT的高級(jí)特性,因?yàn)榘褞в?#8203;內(nèi)部變量引用的函數(shù)帶出了函數(shù)外部,所以該作用域內(nèi)的變量在函數(shù)執(zhí)行完畢后的并不一定會(huì)被銷毀,直到內(nèi)部變量的引用被全部解除。所以閉包的應(yīng)用很容易造成內(nèi)存無法釋放的情況。
良好的閉包管理。
循環(huán)事件綁定、私有屬性、含參回調(diào)等一定要使用閉包時(shí),并謹(jǐn)慎對(duì)待其中的細(xì)節(jié)。
循環(huán)綁定事件,我們假設(shè)一個(gè)場(chǎng)景:有六個(gè)按鈕,分別對(duì)應(yīng)六種事件,當(dāng)用戶點(diǎn)擊按鈕時(shí),在指定的地方輸出相應(yīng)的事件。 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]; }; } /**這里第一個(gè)解決方案顯然是典型的循環(huán)綁定事件錯(cuò)誤,這里不細(xì)說,詳細(xì)可以參照我給一個(gè)網(wǎng)友的回答;而第二和第三個(gè)方案的區(qū)別就在于閉包傳入的參數(shù)。**/ // Case 2 for (var i = 0; i < btns.length; i++) { btns[i].onclick = (function(index) { return function(evt) { output.innerText += 'Clicked ' + events[index]; }; })(i); } /**第二個(gè)方案?jìng)魅氲膮?shù)是當(dāng)前循環(huán)下標(biāo),而后者是直接傳入相應(yīng)的事件對(duì)象。事實(shí)上,后者更適合在大量數(shù)據(jù)應(yīng)用的時(shí)候,因?yàn)樵贘avaScript的函數(shù)式編程中,函數(shù)調(diào)用時(shí)傳入的參數(shù)是基本類型對(duì)象,那么在函數(shù)體內(nèi)得到的形參會(huì)是一個(gè)復(fù)制值,這樣這個(gè)值就被當(dāng)作一個(gè)局部變量定義在函數(shù)體的作用域內(nèi),在完成事件綁定之后就可以對(duì)events變量進(jìn)行手工解除引用,以減輕外層作用域中的內(nèi)存占用了。而且當(dāng)某個(gè)元素被刪除時(shí),相應(yīng)的事件監(jiān)聽函數(shù)、事件對(duì)象、閉包函數(shù)也隨之被銷毀回收。**/ // Case 3 for (var i = 0; i < btns.length; i++) { btns[i].onclick = (function(event) { return function(evt) { output.innerText += 'Clicked ' + event; }; })(events[i]); }
避開閉包陷阱
閉包是個(gè)強(qiáng)大的工具,但同時(shí)也是性能問題的主要誘因之一。不合理的使用閉包會(huì)導(dǎo)致內(nèi)存泄漏。
閉包的性能不如使用內(nèi)部方法,更不如重用外部方法。
由于IE 9瀏覽器的DOM節(jié)點(diǎn)作為COM對(duì)象來實(shí)現(xiàn),COM的內(nèi)存管理是通過引用計(jì)數(shù)的方式,引用計(jì)數(shù)有個(gè)難題就是循環(huán)引用,一旦DOM引用了閉包(例如event handler),閉包的上層元素又引用了這個(gè)DOM,就會(huì)造成循環(huán)引用從而導(dǎo)致內(nèi)存泄漏。
善用函數(shù)
使用一個(gè)匿名函數(shù)在代碼的最外層進(jìn)行包裹。
;(function() { // 主業(yè)務(wù)代碼 })();
有的甚至更高級(jí)一點(diǎn):
;(function(win, doc, $, undefined) { // 主業(yè)務(wù)代碼 })(window, document, jQuery);
甚至連如RequireJS, SeaJS, OzJS 等前端模塊化加載解決方案,都是采用類似的形式:
/**RequireJS**/ define(['jquery'], function($) { // 主業(yè)務(wù)代碼 }); /**SeaJS**/ define('module', ['dep', 'underscore'], function($, _) { // 主業(yè)務(wù)代碼 });
善用回調(diào)函數(shù)
在制作網(wǎng)頁過程中,用的比較多的地方,我們通常封裝成函數(shù)。在封裝函數(shù)的時(shí)候,善用運(yùn)用回調(diào)函數(shù)。
例子如下:
function getData(callBack){ $.ajax({ url:"", data:{}, dataType:"json", type:"get", success:function(data){ callBack(null,data) } }) }
我們?cè)谡{(diào)用的時(shí)候可以如下:
getData(function(error,data){ console.log(data) })
數(shù)組中插入元素最快的方法
向一個(gè)數(shù)組中插入元素是平時(shí)很常見的一件事情。你可以使用push在數(shù)組尾部插入元素,可以用unshift在數(shù)組頭部插入元素,也可以用splice在數(shù)組中間插入元素。 但是這些已知的方法,并不意味著沒有更加高效的方法。
尾部插入元素
我們有2個(gè)數(shù)組
var arr = [1,2,3,4,5]; var arr2 = [];
測(cè)試如下:
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);
發(fā)現(xiàn):
[0].concat(arr); // 快 arr.unshift(0); // 慢64.70%
向數(shù)組中間添加元素
使用splice可以簡(jiǎn)單的向數(shù)組中間添加元素,這也是最高效的方法。
var items = ['one', 'two', 'three', 'four']; items.splice(items.length / 2, 0, 'hello');
相關(guān)文章
15 個(gè)為編程初學(xué)者準(zhǔn)備的網(wǎng)站(都是國(guó)外的一些網(wǎng)站)
今天的文章,我們將分享15個(gè)可以學(xué)習(xí)編程的網(wǎng)站,這些網(wǎng)站上提供了很多編程教程,圖書以及編程練習(xí),希望對(duì)你有用2024-11-02- 這篇文章主要介紹了web開發(fā)中的長(zhǎng)度單位主要包括px,pt,em等,需要的朋友可以參考下2023-08-06
網(wǎng)頁前端開發(fā)的一些尺寸單位(px,rem單位)
px單位是絕對(duì)單位,一般用于pc端網(wǎng)頁開發(fā),因?yàn)槭墙^對(duì)單位所以在移動(dòng)端上的使用體驗(yàn)并不是很好,rem它是描述相對(duì)于當(dāng)前根元素字體尺寸,是相對(duì)單位,它可以根據(jù)根元素的變換而2023-08-06WEB前端優(yōu)化必備js/css壓縮工具YUI-compressor詳解與集成用法
壓縮工具層次不窮,各有優(yōu)點(diǎn),選擇適合的壓縮工具為將來做項(xiàng)目開發(fā)使用是一件很重要的事情??!在這介紹YUI-compressor,需要的朋友可以參考下2023-06-21- 瀏覽器是多進(jìn)程的,有瀏覽器主進(jìn)程,網(wǎng)絡(luò)進(jìn)程,渲染進(jìn)程,插件進(jìn)程等,在將html,css,javascript解析成一個(gè)頁面的時(shí)候,就需要多個(gè)進(jìn)程的分工合作2023-05-01
- 本文為大家整理了常用的文件對(duì)應(yīng)的MIME類型,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-25
postman中form-data、x-www-form-urlencoded、raw、binary的區(qū)別介紹
這篇文章介紹了postman中form-data、x-www-form-urlencoded、raw、binary的區(qū)別,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-12-28網(wǎng)頁中使用Unicode字符的介紹(&#,\u等)
國(guó)際組織制定了可以容納世界上所有文字和符號(hào)的字符編碼方案,稱為Unicode,是通用字符集Universal Character Set的縮寫,用以滿足跨語言、跨平臺(tái)進(jìn)行文本轉(zhuǎn)換、處理的要求2021-11-27前端實(shí)現(xiàn)字符串GBK與GB2312的編解碼(小結(jié))
這篇文章主要介紹了前端實(shí)現(xiàn)字符串GBK與GB2312的編解碼(小結(jié)),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2020-12-02告別硬編碼讓你的前端表格自動(dòng)計(jì)算的實(shí)例代碼
這篇文章主要介紹了告別硬編碼讓你的前端表格自動(dòng)計(jì)算,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-27