Javascript與當前項目的思考


 

 

主體 分為以下三部分,能力、經驗有限,歡迎拍磚。

1.低效的代碼

2.面向對象的重構重復利用代碼

3.調試的經驗總結

 

第一部分 日常中低效的代碼

 

  • 加載和運行
<html>

<head>
<title>Script Example</title>

</head> <body>

<p>

<script type="text/javascript">

document.write("The date is " + (new Date()).toDateString());

</script> </p>

</body> </html>

 

當瀏覽器遇到一個<script>標簽時,正如上面 HTML 頁面中那樣,無法預知 JavaScript 是否在<p>標簽中 添加內容。因此,瀏覽器停下來,運行此 JavaScript 代碼,然后再繼續解析、翻譯頁面。同樣的事情發生 在使用 src 屬性加載 JavaScript 的過程中。瀏覽器必須首先下載外部文件的代碼,這要占用一些時間,然后 解析並運行此代碼。此過程中,頁面解析和用戶交互是被完全阻塞的。

一個<script>標簽可以放在 HTML 文檔的<head><body>標簽中,可以在其中多次 出現。傳統上,<script>標簽用於加載外部 JavaScript 文件

第一個 JavaScript 文件開始下載,並阻塞了其他文件的下載過程。進 一步,在 file1.js 下載完之后和 file2.js 開始下載之前有一個延時,這是 file1.js 完全運行所需的時間。每個文件必須等待前一個文件下載完成並運行完之后,才能開始自己的下載過程。當這些文件下載時,用戶面 對一個空白的屏幕。這就是幾年前(現在當網速較慢時,仍可重現這個問題)大多數瀏覽器的行為模式。

因為腳本阻塞其他頁面資源的下載過程,所以推薦的辦法是:將所有<script>標簽放在盡可能接近<body> 標簽底部的位置,盡量減少對整個頁面下載的影響。例如:

<html>

<head>
<title>Script Example</title>

<link rel="stylesheet" type="text/css" href="styles.css"> </head>
<body>

<p>Hello world!</p>
<-- Example of recommended script positioning --> 

<script type="text/javascript" src="file1.js"></script> 

<script type="text/javascript" src="file2.js"></script> 

<script type="text/javascript" src="file3.js"></script>

</body> 

</html>

 

  • 數據訪問

 

 

數據存儲在哪里, 關系到代碼運行期間數據被檢索到的速度。在 JavaScript 中,此問題相對簡單,因為數據存儲只有少量方 式可供選擇。正如其他語言那樣,數據存儲位置關系到訪問速度。在 JavaScript 中有四種基本的數據訪問 位置:

直接量(Literal values)
 

直接量僅僅代表自己,而不存儲於特定位置。

 JavaScript 的直接量包括:

字符串(string),數字(Number),布爾值(Boolean),對象(Object), 數組(Array),函數(Function),正則表達式(RegExp),具有特殊意義的空值(null),以及未定義(undefined)。

變量(Variables)
 

我們使用 var 關鍵字創建用於存儲數據值。

數組項(Array items)

具有數字索引,存儲一個 JavaScript 數組對象。

對象成員(Object members)

具有字符串索引,存儲一個 JavaScript 對象。

每一種數據存儲位置都具有特定的讀寫操作負擔。大多數情況下,對一個直接量和一個局部變量數據訪問的性能差異是微不足道的。 

 

管理作用域(Managing Scope)

 

作用域概念是理解 JavaScript 的關鍵,不僅從性能的角度,而且從功能的角度。作用域對 JavaScript 有 許多影響,從確定哪些變量可以被函數訪問,到確定 this 的值,首先要理解作用域的工作原理。

作用域鏈和標識符解析(Scope Chains and Identifier Resolution)

 

每一個 JavaScript 函數都被表示為對象。進一步說,它是一個函數實例。函數對象正如其他對象那樣, 擁有你可以編程訪問的屬性,和一系列不能被程序訪問,僅供 JavaScript 引擎使用的內部屬性。 

 內部[Scope]屬性包含一個函數被創建的作用域中對象的集合。此集合被稱為函數的作用域鏈,它決定哪些數據可由函數訪問。此函數作用域鏈中的每個對象被稱為一個可變對象,每個可變對象都以“鍵值對”

的形式存在。當一個函數創建后,它的作用域鏈被填充以對象,這些對象代表創建此函數的環境中可訪問的數據。例如下面這個全局函數:

function add(num1, num2)

{ 

  var sum = num1 + num2; 

  return sum;

}

 

 

add()函數創建后,它的作用域鏈中填入一個單獨的可變對象,此全局對象代表了所有全局范圍定義的變量。此全局對象包含諸如窗口(window)、瀏覽器(browser)和文檔(DOM)之類的訪問接口。(注意: 此圖中只畫出全局變量中很少的一部分,其他部分還很多)。

 

 

 

 

add()函數的作用域鏈

 

add 函數的作用域鏈將會在運行時用到。假設運行下面的代碼: var total = add(5, 10); 

運行此 add 函數時建立一個內部對象,稱作運行期上下文。一個運行期上下文定義了一個函數運行時的環境。對函數的每次運行而言,每個運行期上下文都是獨一的,所以多次調用同一個函數就會導致多次創建運行期上下文。當函數執行完畢,運行期上下文就被銷毀。

 

一個運行期上下文有它自己的作用域鏈,用於標識符解析。當運行期上下文被創建時,它的作用域鏈被 初始化,連同運行函數的[[Scope]]屬性中所包含的對象。這些值按照它們出現在函數中的順序,被復制到 運行期上下文的作用域鏈中。這項工作一旦完成,一個被稱作激活對象的新對象就為運行期上下文創建 好了。此激活對象作為函數執行期的一個可變對象,包含訪問所有局部變量,命名參數,參數集合,和 this 的接口。然后,此對象被推入作用域鏈的前端。當作用域鏈被銷毀時,激活對象也一同銷毀。下圖顯示 了前面實例代碼所對應的運行期上下文和它的作用域鏈。

 

 

在函數運行過程中,每遇到一個變量,標識符識別過程要決定從哪里獲得或者存儲數據。此過程搜索運 行期上下文的作用域鏈,查找同名的標識符。搜索工作從運行函數的激活目標之作用域鏈的前端開始。如 果找到了,那么就使用這個具有指定標識符的變量;如果沒找到,搜索工作將進入作用域鏈的下一個對象。 此過程持續運行,直到標識符被找到,或者沒有更多對象可用於搜索,這種情況下標識符將被認為是未定 義的。函數運行時每個標識符都要經過這樣的搜索過程,例如前面的例子中,函數訪問 sum,num1,num2 時都會產生這樣的搜索過程。正是這種搜索過程影響了性能。

在運行期上下文的作用域鏈中, 一個標識符所處的位置越深,它的讀寫速度就越慢。所以,函數中局部變量的訪問速度總是最快的,而全 局變量通常是最慢的(優化的 JavaScript 引擎在某些情況下可以改變這種狀況)。請記住,全局變量總是 處於運行期上下文作用域鏈的最后一個位置,所以總是最遠才能觸及的。

 

最好盡可能使用局部變量。一個好的經驗法則 是:用局部變量存儲本地范圍之外的變量值,如果它們在函數中的使用多於一次。考慮下面的例子:

function initUI(){
  var 
    bd = document.body,     links = document.getElementsByTagName_r("a"),
     i = 0,     len = links.length;   
  
   while(i < len){     update(links[i++]); }     document.getElementById("go-btn").onclick = function(){ start();    };    bd.className = "active"; }

 

 此函數包含三個對 document 的引用,document 是一個全局對象。搜索此變量,必須遍歷整個作用域鏈, 直到最后在全局變量對象中找到它。你可以通過這種方法減輕重復的全局變量訪問對性能的影響:首先將 全局變量的引用存儲在一個局部變量中,然后使用這個局部變量代替全局變量。例如,上面的代碼可以重 寫如下:

function initUI(){

    var doc = document,
    bd = doc.body,
    links = doc.getElementsByTagName_r("a"),
    i = 0,
   len = links.length;
  
   while(i < len){      update(links[i++]);
   }    doc.getElementById(
"go-btn").onclick = function(){
    start();
   };   bd.className
= "active";
}

 

DOM 編程(DOM Scripting)

 

DOM 操作代價昂貴,在富網頁應用中通常是一個性能瓶頸。 

ECMAScript 需要訪 問 DOM 時,你需要過橋,交一次過橋費。你操作 DOM 次數越多,費用就越高。一般的建議是盡量減 少過橋次數,努力停留在 ECMAScript 島上。本章將對此問題給出詳細解答,告訴你應該關注什么地方, 以提高用戶交互速度。

 

為了給你一個關於 DOM 操作問題的量化印象,考慮下面的例子:

function innerHTMLLoop() {
  for (var count = 0; count < 15000; count++) {

    document.getElementById('here').innerHTML += 'a';

  } 
}

 

此函數在循環中更新頁面內容。這段代碼的問題是,在每次循環單元中都對 DOM 元素訪問兩次:一次 讀取 innerHTML 屬性能容,另一次寫入它。

 一個更有效率的版本將使用局部變量存儲更新后的內容,在循環結束時一次性寫入:

function innerHTMLLoop2() {
  var content = '';
  for (var count = 0; count < 15000; count++) {

    content += 'a'; 
  }   document.getElementById(
'here').innerHTML += content;
}

 

 

你訪問 DOM 越多,代碼的執行速度就越慢。 

 

事件托管(Event Delegation)

當頁面中存在大量元素,而且每個元素有一個或多個事件句柄與之掛接(例如 onclick)時,可能會影 響性能。連接每個句柄都是有代價的,無論其形式是加重了頁面負擔(更多的頁面標記和 JavaScript 代碼) 還是表現在運行期的運行時間上。你需要訪問和修改更多的 DOM 節點,程序就會更慢,特別是因為事件 掛接過程都發生在 onload(或 DOMContentReady)事件中,對任何一個富交互網頁來說那都是一個繁忙的 時間段。掛接事件占用了處理時間,另外,瀏覽器需要保存每個句柄的記錄,占用更多內存。當這些工作 結束時,這些事件句柄中的相當一部分根本不需要(因為並不是 100%的按鈕或者鏈接都會被用戶點到), 所以很多工作都是不必要的。

 

一個簡單而優雅的處理 DOM 事件的技術是事件托管。它基於這樣一個事實:事件逐層冒泡總能被父元 素捕獲。采用事件托管技術之后,你只需要在一個包裝元素上掛接一個句柄,用於處理子元素發生的所有 事件。

According to the DOM standard, each event has three phases: 根據 DOM 標准,每個事件有三個階段:

  1. 捕獲
  2. 到達目標
  3. 冒泡

當用戶點擊了“menu #1”鏈接,點擊事件首先被<a>元素收到。然后它沿着 DOM 樹冒泡,被<li>元素收 到,然后是<ul>,接着是<div>,等等,一直到達文檔的頂層,甚至 window。這使得你可以只在父元素上 掛接一個事件句柄,來接收所有子元素產生的事件通知。

假設你要為圖中所顯示的文檔提供一個逐步增強的 Ajax 體驗。如果用戶關閉了 JavaScript,菜單中的鏈 接仍然可以正常地重載頁面。但是如果 JavaScript 打開而且用戶代理有足夠能力,你希望截獲所有點擊, 阻止默認行為(轉入鏈接),發送一個 Ajax 請求獲取內容,然后不刷新頁面就能夠更新部分頁面。使用 事件托管實現此功能,你可以在 UL"menu"單元掛接一個點擊監聽器,它封裝所有鏈接並監聽所有 click 事 件,看看他們是否發自一個鏈接。

document.getElementById('menu').onclick = function(e) {

  e = e || window.event;
  var target = e.target || e.srcElement; 
  var pageid, hrefparts;


  if (target.nodeName !== 'A') {

    return; 
  }   hrefparts
= target.href.split('/');   pageid = hrefparts[hrefparts.length - 1]; pageid = pageid.replace('.html', '');   ajaxRequest('xhr.php?page=' + id, updatePageContents);   if (typeof e.preventDefault === 'function') {
    e.preventDefault();     e.stopPropagation();   }
else {     e.returnValue = false;
    e.cancelBubble = true;   }
};

 

正如你所看到的那樣,事件托管技術並不復雜;你只需要監聽事件,看看他們是不是從你感興趣的元素 中發出的。這里有一些冗余的跨瀏覽器代碼,如果你將它們移入一個可重用的庫中,代碼就變得相當干凈。 

 

  • 算法和流 程控制

 

要熟悉javascript的所有循環方法,不只是單純for

 

在大多數編程語言中,代碼執行時間多數在循環中度過。在一系列編程模式中,循環是最常用的模式之 一,因此也是提高性能必須關注的地區之一。理解 JavaScript 中循環對性能的影響至關重要,因為死循環 或者長時間運行的循環會嚴重影響用戶體驗。

for 循環,與類 C 語言使用同樣的語法:

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

//loop body

}

 

for 循環大概是最常用的 JavaScript 循環結構。它由四部分組成:初始化體,前測條件,后執行體,循環 體。當遇到一個 for 循環時,初始化體首先執行,然后進入前測條件。如果前測條件的計算結果為 true, 則執行循環體。然后運行后執行體。for 循環封裝上的直接性是開發者喜歡的原因。

 

第二種循環是 while 循環。while 循環是一個簡單的預測試循環,由一個預測試條件和一個循環體構成:

var i = 0; 
while(i < 10){
//loop body i++;
}

 

 

在循環體執行之前,首先對前測條件進行計算。如果計算結果為 true,那么就執行循環體;否則循環體 將被跳過。任何 for 循環都可以寫成 while 循環,反之亦然。

第三種循環類型是 do-while 循環。do-while 循環是 JavaScript 中唯一一種后測試的循環,它包括兩部分: 循環體和后測試條件體:

var i = 0; 

do { //loop body } while (i++ < 10);

 

在一個 do-while 循環中,循環體至少運行一次,后測試條件決定循環體是否應再次執行。

 

第四種也是最后一種循環稱為 for-in 循環。此循環有一個非常特殊的用途:它可以枚舉任何對象的命名 屬性。其基本格式如下:

for (var prop in object){

//loop body

}

 

每次循環執行,屬性變量被填充以對象屬性的名字(一個字符串),直到所有的對象屬性遍歷完成才返 回。返回的屬性包括對象的實例屬性和它從原型鏈繼承而來的屬性。

 一個典型的數組處理循環,可使用三種循環的任何一種。最常用的代碼寫法如下:

//original loops

for (var i=0; i < items.length; i++){
   process(items[i]); }
var j=0; while (j < items.length){   process(items[j++]]);

}
var k=0;
do {
  process(items[k
++]);
}
while (k < items.length);

 

 在每個循環中,每次運行循環體都要發生如下幾個操作:

1. 在控制條件中讀一次屬性(items.length)
2. 在控制條件中執行一次比較(i < items.length)
3. 比較操作,察看條件控制體的運算結果是不是 true(i < items.length == true)

4. 一次自加操作(i++)
5.  一次數組查找(items[i])

6. 一次函數調用(process(items[i]))

在這些簡單的循環中,即使沒有太多的代碼,每次迭代也要進行許多操作。代碼運行速度很大程度上由 process()對每個項目的操作所決定,即使如此,減少每次迭代中操作的總數可以大幅度提高循環整體性能。

優化循環工作量的第一步是減少對象成員和數組項查找的次數。正如第 2 章討論的,在大多數瀏覽器上, 這些操作比訪問局部變量或直接量需要更長時間。前面的例子中每次循環都查找 items.length。這是一種浪 費,因為該值在循環體執行過程中不會改變,因此產生了不必要的性能損失。你可以簡單地將此值存入一 個局部變量中,在控制條件中使用這個局部變量,從而提高了循環性能:

//minimizing property lookups

for (var i=0, len=items.length; i < len; i++){
   process(items[i]); }
var j=0, count = items.length;

while (j < count){   process(items[j++]]);
}
var k=0, num = items.length;

do {   process(items[k++]);

} while (k < num);

 

以下兩個部分還沒有整理好,爭取在周末發出來。

2.面向對象的重構重復利用代碼

 

3.調試的經驗總結

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM