註釋javascript
代碼中的註釋很重要,天然也是毋庸置疑的。一般咱們會強調代碼中註釋數量的多少,而輕視了對註釋質量的提升。編碼是及時添加註釋,會給後續代碼的維護人員帶來很大的便利。可是若是註釋不注意更新,或者因爲拷貝、粘貼引發的錯誤的註釋,則會誤導閱讀人員,反而給閱讀帶來障礙。
除了註釋要及時更新外,咱們還應對註釋的內容要特別關注。註釋要儘可能簡單、清晰明瞭,避免使用含混晦澀的語言,同時着重 註釋的意義,對不太直觀的部分進行註解。JavaScript 的註釋有兩種"//" 和"/* .... */"。
建議 // 用做代碼行註釋,html
/* .... */ 形式用做對整個代碼段的註銷,或較正式的聲明中,如函數參數、功能、文件功能等的描述中。前端
命名規則java
JavaScript 中的標識符的命名規則:jquery
一、以字母、下劃線'_'或美圓符號'$'開頭數組
二、容許名稱中包含字母,數字,下劃線'_'和美圓符號'$'瀏覽器
三、區分大小寫 變量、參數、成員變量、函數等名稱均以小寫字母開頭,構造器的名稱以大寫字母開頭。下劃線'_'開頭的變量通常習慣於標識私有 / 局部成員。而美圓符號'$'開頭的變量習慣於標識系統相關。閉包
建議:app
$開頭變量表示jquery-dom節點。dom
全大寫表常量(js無常量,用這種方式區分)。
普通變量用駱峯命名(treeName)。
全局變量或閉包變量用駱峯命名(TreeName)。
函數儘可能用動名詞組合(如setName)。
變量儘可能用名詞。
變量的聲明
儘管 JavaScript 語言並不要求在變量使用前先對變量進行聲明。但咱們仍是應該養成這個好習慣。這樣能夠比較容易的檢測出那些未經聲明的變量,避免其變爲隱藏的全局變量,形成隱患。 在函數的開始應先用 var 關鍵字聲明函數中要使用的局部變量,註釋變量的功能及表明的含義。每一個變量單獨佔一行,以便添加註釋。這是由於 JavaScript 中只有函數的 {} 代表做用域,用 var 關鍵字聲明的局部 變量只在函數內有效,而未經 var 聲明的變量則被視爲全局變量。
減小頁面重繪
減小頁面重繪雖然本質不是JS自己的優化,可是其每每是由JS引發的,而重繪的狀況每每是嚴重影響頁面性能的,因此徹底有必要拿出來,咱們看下面例子:
<div id="demo"></div> <input type="button" value="效率低" onclick="func1()" /> <input type="button" value="效率高" onclick="func2()" />
var str = "<div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div>"; //效率低的 function func1(){ var obj = document.getElementById("demo"); var start = new Date().getTime(); for(var i = 0; i < 100; i++){ obj.innerHTML += str + i; } var end = new Date().getTime(); alert("用時 " + (end - start) + " 毫秒"); } //效率高的 function func2(){ var obj = document.getElementById("demo"); var start = new Date().getTime(); var arr = []; for(var i = 0; i < 100; i++){ arr[i] = str + i; } obj.innerHTML = arr.join(""); var end = new Date().getTime(); alert("用時 " + (end - start) + " 毫秒"); }
在例子中,我只是用了100次的循環,由於若是用10000次循環的話,瀏覽器基本上就卡住不動了,可是即便是100次的循環,咱們看看下面的執行結果。
能夠看到的是,這簡直是一個驚人的結果,僅僅100次的循環,出現瞭如此之大的差異。這裏還要注意的是,通常影響頁面重繪的不只僅是innerHTML,若是改變元素的樣式,位置等狀況都會觸發頁面重繪,因此在平時必定要注意這點。
for循環
for循環是咱們常常會遇到的狀況,咱們先看看下面例子:
<input type="button" value="效率低" onclick="func1()" /> <input type="button" value="效率高" onclick="func2()" />
var arr = []; for(var i = 0; i < 10000; i++){ arr[i] = "<div>" + i + "</div>"; } document.body.innerHTML += arr.join(""); //效率低的 function func1(){ var divs = document.getElementsByTagName("div"); var start = new Date().getTime(); for(var i = 0; i < divs.length; i++){ //"效率低" } var end = new Date().getTime(); alert("用時:" + (end - start) + "毫秒"); } //效率高的 function func2(){ var divs = document.getElementsByTagName("div"); var start = new Date().getTime(); for(var i = 0, len = divs.length; i < len; i++){ //"效率高" } var end = new Date().getTime(); alert("用時:" + (end - start) + "毫秒"); }
這裏for循環在執行中,第一種狀況會每次都計算一下長度,而第二種狀況倒是在開始的時候計算長度,並把其保存到一個變量中,因此其執行效率要高點,因此在咱們使用for循環的時候,特別是須要計算長度的狀況,咱們應該開始將其保存到一個變量中。
<input type="button" value="效率低" onclick="func1()" /> <input type="button" value="效率高" onclick="func2()" />
var arr2 = []; for(var i = 0; i < 10000; i++){ arr2[i] = "<div>" + i + "</div>"; } //效率低的 function func1(){ var start = new Date().getTime(); for(var i = 0; i < arr2.length; i++){ //"效率低" } var end = new Date().getTime(); alert("用時:" + (end - start) + "毫秒"); } //效率高的 function func2(){ var start = new Date().getTime(); for(var i = 0, len = arr2.length; i < len; i++){ //"效率高" } var end = new Date().getTime(); alert("用時:" + (end - start) + "毫秒"); }
對於for循環的優化,也有人提出不少點,有人認爲用-=1,或者從大到小的方式循環等等,我認爲是徹底沒有必要的,這些優化每每實際狀況下根本沒有表現出來,換句話說只是計算機級別的微小的變化,可是給咱們帶來的倒是代碼的可讀性大大的下降,因此實在是得不償失。
減小做用域鏈上的查找次數
咱們知道,js代碼在執行的時候,若是須要訪問一個變量或者一個函數的時候,它須要遍歷當前執行環境的做用域鏈,而遍歷是從這個做用域鏈的前端一級一級的向後遍歷,直到全局執行環境,因此這裏每每會出現一個狀況,那就是若是咱們須要常常訪問全局環境的變量對象的時候,咱們每次都必須在當前做用域鏈上一級一級的遍歷,這顯然是比較低效一點的。
避免雙重解釋
雙重解釋的狀況也是咱們常常會碰到的,有的時候咱們沒怎麼考慮到這種狀況會影響到效率,雙重解釋通常在咱們使用eval、new Function和setTimeout等狀況下會遇到,咱們看看下面的例子:
<div id="demo"></div> <input id="but1" type="button" onclick="func1()" value="效率低"/> <input id="but2" type="button" onclick="func2()" value="效率高"/>
var sum, num1 = 1, num2 = 2; function func1(){ var start = new Date().getTime(); for(var i = 0; i < 10000; i++){ var func = new Function("sum+=num1;num1+=num2;num2++;"); func(); } var end = new Date().getTime(); alert("用時 " + (end - start) + " 毫秒"); } function func2(){ var start = new Date().getTime(); for(var i = 0; i < 10000; i++){ sum+=num1; num1+=num2; num2++; } var end = new Date().getTime(); alert("用時 " + (end - start) + " 毫秒"); }
var sum, num1 = 1, num2 = 2; function func1(){ var start = new Date().getTime(); for(var i = 0; i < 1000; i++){ eval("sum+=num1;num1+=num2;num2++;"); } var end = new Date().getTime(); alert("用時 " + (end - start) + " 毫秒"); } function func2(){ var start = new Date().getTime(); for(var i = 0; i < 1000; i++){ sum+=num1; num1+=num2; num2++; } var end = new Date().getTime(); alert("用時 " + (end - start) + " 毫秒"); }
雙重解釋是有必定開銷的,因此在實際當中要儘可能避免雙重解釋。
使用一次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('<p>' + i + '</p>'); } document.body.innerHTML = html.join('');
條件分支
將條件分支,按可能性順序從高到低排列:能夠減小解釋器對條件的探測次數
在同一條件子的多(>2)條件分支時,使用switch優於if:switch分支選擇的效率高於if,在IE下尤其明顯。4分支的測試,IE下switch的執行時間約爲if的一半。
使用三元運算符替代條件分支
if (a > b) { num = a; } else { num = b; } //能夠替換爲: num = a > b ? a : b;
循環引用
若是循環引用中包含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的引用,從而打斷循環引用。
釋放dom元素佔用的內存
將dom元素的innerHTML設置爲空字符串,能夠釋放其子元素佔用的內存。
在rich應用中,用戶也許會在一個頁面上停留很長時間,可使用該方法釋放積累得愈來愈多的dom元素使用的內存。
釋放javascript對象
在rich應用中,隨着實例化對象數量的增長,內存消耗會愈來愈大。因此應當及時釋放對對象的引用,讓GC可以回收這些內存控件。
對象:obj = null
對象屬性:delete obj.myproperty
數組item:使用數組的splice方法釋放數組中不用的item
函數節流(throttle)與函數去抖(debounce)
函數去抖:
若是用手指一直按住一個彈簧,它將不會彈起直到你鬆手爲止。
也就是說當調用動做n毫秒後,纔會執行該動做,若在這n毫秒內又調用此動做則將從新計算執行時間。
var debounce = function(idle, action){ var last return function(){ var ctx = this, args = arguments clearTimeout(last) last = setTimeout(function(){ action.apply(ctx, args) }, idle) } }
函數節流:
若是將水龍頭擰緊直到水是以水滴的形式流出,那你會發現每隔一段時間,就會有一滴水流出。
也就是會說預先設定一個執行週期,當調用動做的時刻大於等於執行週期則執行該動做,而後進入下一個新週期。
var throttle = function(delay, action){ var last = Date.now(); return function(){ var curr = Date.now(); if (curr - last > delay){ action.apply(this, arguments) last = curr } } }