前端性能優化補充篇

前言

看了一下以前發佈的文章,關於前端性能優化的,有網站前端性能優化之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('m​​odule', ['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');
相關文章
相關標籤/搜索