不唐突的JavaScript的七條準則

來源:互聯網
上載者:User
1.不要做任何假設
(JavaScript是一個不可靠的助手)

可能不唐突的JavaScript 的最重要的一個特性就是——你要停止任何假設:

    * 不要假設JavaScript是可用的,你最好認為它很有可能是停用,而不是直接依賴於它。
    * 在你經過測試確認一些方法和屬性可以使用之前,不要假設瀏覽器支援它們。
    * 不要假設HTML代碼如你想象的那樣正確,每次都要進行檢查,並且當其停用時候就什麼也不要做。
    * 讓JavaScript的功能獨立於輸入裝置
    * 要記住其他的指令碼可能會影響你的JavaScript的功能,所以要保證你的指令碼的範圍儘可能地安全。

在開始設計你的指令碼之前,要考慮的第一件事情就是檢查一下你要為其編寫指令碼的HTML代碼,看看有什麼東西可以協助你達到目的。

2.找出鉤子和節點關係
(HTML是指令碼的基石)

在開始編寫指令碼之前,要先看一下你要為之編寫JavaScript的HTML。如果HTML是未經組織的或者未知的,那麼你幾乎不可能有一個好的指令碼編寫方案——很可能就會出現下面的情況:要麼是會用JavaScript建立太多標記,要麼就是應用太依賴於JavaScript。

在HTML中有一些東西需要考慮,那就是鉤子和節點關係。

<1>.HTML 鉤子

HTML最初的和最重要的鉤子就是ID,而且ID可以通過最快的DOM方法——getElementById 訪問到。如果在一個有效HTML文檔中所有的ID都是獨一無二的話(在IE中關於name 和 ID 有一個bug,不過有些好的類庫解決了這個問題),使用ID就是安全可靠的,並且易於測試。

其他一些鉤子就是是HTML元素和CSS類,HTML元素可以通過getElementsByTagName方法訪問,而在多數瀏覽器中都還不能通過原生的DOM方法來訪問CSS類。不過,有很多外部類庫提供了可以訪問CSS類名(類似於 getElementsByClassName) 的方法。

<2>.HTML 節點關係

關於HTML的另外比較有意思的一點就是標記之間的關係,思考下面的問題:

    * 要怎樣才可以最容易地、通過最少的DOM遍曆來到達目標節點?
    * 通過修改什麼標記,可以儘可能多地訪問到需要修改的子節點?
    * 一個給定的元素有什麼屬性或資訊可以用來到達另外一個元素?

遍曆DOM很耗資源而且速度很慢,這就是為什麼要盡量使用瀏覽器中已經在使用的技術來做這件事情。

3.把遍曆交給專家來做
(CSS,更快地遍曆DOM)

有關DOM的指令碼和使用方法或屬性(getElementsByTagName, nextSibling, previousSibling, parentNode以及其它)來遍曆DOM似乎迷惑了很多人,這點很有意思。而有趣的是,我們其實早已經通過另外一種技術—— CSS ——做了這些事情。

CSS 是這樣一種技術,它使用CSS選取器,通過遍曆DOM來訪問目標元素並改變它們的視覺屬性。一段複雜的使用DOM的JavaScript可以用一個CSS選取器取代: Java代碼

  1. var n = document.getElementById('nav');   
  2. if(n){   
  3. var as = n.getElementsByTagName('a');   
  4. if(as.length > 0){   
  5. for(var i=0;as[i];i++){   
  6. as[i].style.color = ‘#369′;   
  7. as[i].style.textDecoration = ‘none’;   
  8. }   
  9. }   
  10. }   
  11.   
  12. /* 下面的代碼與上面功能一樣 */  
  13.   
  14. #nav a{   
  15. color:#369;   
  16. text-decoration:none;   
  17. }  
var n = document.getElementById('nav');if(n){var as = n.getElementsByTagName('a');if(as.length > 0){for(var i=0;as[i];i++){as[i].style.color = ‘#369′;as[i].style.textDecoration = ‘none’;}}}/* 下面的代碼與上面功能一樣 */#nav a{color:#369;text-decoration:none;}

這是一個可以好好利用的很強大的技巧。你可以通過動態為DOM中高層的元素添加class 或者更改元素ID來實現這一點。如果你使用DOM為文檔的body添加了一個CSS類,那麼設計師就很可以容易地定義文檔的靜態版本和動態版本。

Java代碼
  1. JavaScript:   
  2.   
  3. var dynamicClass = 'js';   
  4. var b = document.body;   
  5. b.className = b.className ? b.className + ' js' : 'js';   
  6.   
  7. CSS:   
  8.   
  9. /* 靜態版本 */  
  10.   
  11. #nav {   
  12. ....   
  13. }   
  14.   
  15. /* 動態版本 */  
  16.   
  17. body.js #nav {   
  18. ....   
  19. }  
JavaScript:var dynamicClass = 'js';var b = document.body;b.className = b.className ? b.className + ' js' : 'js';CSS:/* 靜態版本 */#nav {....}/* 動態版本 */body.js #nav {....}

4.理解瀏覽器和使用者
(在既有的使用模式上建立你所需要的東西)

不唐突的JavaScript 中很重要的一部分就是理解瀏覽器是如何工作的(尤其是瀏覽器是如何崩潰的)以及使用者期望的是什麼。不考慮瀏覽器你也可以很容易地使用JavaScript 建立一個完全不同的介面。拖拽介面,摺疊地區,捲軸和滑動塊都可以使用JavaScript建立,但是這個問題並不是個簡單的技術問題,你需要思考下面的問題:

    * 這個新介面可以獨立於輸入裝置嗎?如果不能,那麼可以依賴哪些東西?
    * 我建立的這個新介面是否遵循了瀏覽器或者其它富介面的準則(你可以通過滑鼠在多級菜單中直接切換嗎?還是需要使用tab鍵?)
    * 我需要提供什麼功能但是這個功能是依賴於JavaScript的?

最後一個問題其實不是問題,因為如果需要你就可以使用DOM來憑空建立HTML。關於這點的一個例子就是“列印”連結,由於瀏覽器沒有提供一個非 JavaScript的列印文檔功能,所以你需要使用DOM來建立這類連結。同樣地,一個實現了展開和收縮內容模組的、可以點擊的標題列也屬於這種情況。標題列不能被鍵盤啟用,但是連結可以。所以為了建立一個可以點擊的標題列你需要使用JavaScript將連結加入進去,然後所有使用鍵盤的使用者就可以收縮和展開內容模組了。

解決這類問題的極好的資源就是設計模式庫。至於要知道瀏覽器中的哪些東西是獨立於輸入裝置的,那就要靠經驗的積累了。首先你要理解的就是事件處理機制。

5.理解事件
(事件處理會引起改變)

事件處理是走向不唐突的JavaScript的第二步。重點不是讓所有的東西都變得可以拖拽、可以點擊或者為它們添加內聯處理,而是理解事件處理是一個可以完全分離出來的東西。我們已經將HTML,CSS和JavaScript分離開來,但是在事件處理的分離方面卻沒有走得很遠。

事件處理器會監聽發生在文檔中元素上的變化,如果有事件發生,處理器就會找到一個很奇妙的對象(一般會是一個名為e的參數),這個對象會告訴元素髮生了什麼以及可以用它做什麼。

對於大多數事件處理來說,真正有趣的是它不止發生在你想要訪問的元素上,還會在DOM中較高層級的所有元素上發生(但是並不是所有的事件都是這樣,focus和blur事件是例外)。舉例來說,利用這個特性你可以為一個導航列表只添加一個事件處理器,並且使用事件處理器的方法來擷取真正觸發事件的元素。這種技術叫做事件委託,它有幾點好處:

    * 你只需要檢查一個元素是否存在,而不需要檢查每個元素
    * 你可以動態地添加或者刪除子節點而並不需要刪除相應的事件處理器
    * 你可以在不同的元素上對相同的事件做出響應

需要記住的另一件事是,在事件向父元素傳播的時候你可以停止它而且你可以覆寫掉HTML元素(比如連結)的預設行為。不過,有時候這並不是個好主意,因為瀏覽器賦予HTML元素那些行為是有原因的。舉個例子,連結可能會指向頁面內的某個目標,不去修改它們能確保使用者可以將頁面當前的指令碼狀態也加入書籤。

6.為他人著想
(命名空間,範圍和模式)

你的代碼幾乎從來不會是文檔中的唯一的指令碼代碼。所以保證你的代碼裡沒有其它指令碼可以覆蓋的全域函數或者全域變數就顯得尤為重要。有一些可用的模式可以來避免這個問題,最基礎的一點就是要使用 var 關鍵字來初始化所有的變數。假設我們編寫了下面的指令碼:

Java代碼
  1. var nav = document.getElementById('nav');   
  2. function init(){   
  3. // do stuff   
  4. }   
  5. function show(){   
  6. // do stuff   
  7. }   
  8. function reset(){   
  9. // do stuff   
  10. }  
var nav = document.getElementById('nav');function init(){// do stuff}function show(){// do stuff}function reset(){// do stuff}

上面的代碼中包含了一個叫做nav的全域變數和名字分別為 init,show 和 reset 的三個函數。這些函數都可以訪問到nav這個變數並且可以通過函數名互相訪問:

Java代碼
  1. var nav = document.getElementById('nav');   
  2. function init(){   
  3. show();   
  4. if(nav.className === 'show'){   
  5. reset();   
  6. }   
  7. // do stuff   
  8. }   
  9. function show(){   
  10. var c = nav.className;   
  11. // do stuff   
  12. }   
  13. function reset(){   
  14. // do stuff   
  15. }  
var nav = document.getElementById('nav');function init(){show();if(nav.className === 'show'){reset();}// do stuff}function show(){var c = nav.className;// do stuff}function reset(){// do stuff}

你可以將代碼封裝到一個對象中來避免上面的那種全域式編碼,這樣就可以將函數變成對象中的方法,將全域變數變成對象中的屬性。 你需要使用“名字+冒號”的方式來定義方法和屬性,並且需要在每個屬性或方法後面加上逗號作為分割符。

Java代碼
  1. var myScript = {   
  2. nav:document.getElementById('nav'),   
  3. init:function(){   
  4. // do stuff   
  5. },   
  6. show:function(){   
  7. // do stuff   
  8. },   
  9. reset:function(){   
  10. // do stuff   
  11. }   
  12. }  
var myScript = {nav:document.getElementById('nav'),init:function(){// do stuff},show:function(){// do stuff},reset:function(){// do stuff}}

所有的方法和屬性都可以通過使用“類名+點操作符”的方式從外部和內部訪問到。 Java代碼

  1. var myScript = {   
  2. nav:document.getElementById('nav'),   
  3. init:function(){   
  4. myScript.show();   
  5. if(myScript.nav.className === 'show'){   
  6. myScript.reset();   
  7. }   
  8. // do stuff   
  9. },   
  10. show:function(){   
  11. var c = myScript.nav.className;   
  12. // do stuff   
  13. },   
  14. reset:function(){   
  15. // do stuff   
  16. }   
  17. }  
var myScript = {nav:document.getElementById('nav'),init:function(){myScript.show();if(myScript.nav.className === 'show'){myScript.reset();}// do stuff},show:function(){var c = myScript.nav.className;// do stuff},reset:function(){// do stuff}}

這種模式的缺點就是,你每次從一個方法中訪問其它方法或屬性都必須在前面加上對象的名字,而且對象中的所有東西都是可以從外部存取的。如果你只是想要部分代碼可以被文檔中的其他指令碼訪問,可以考慮下面的模組(module)模式: Java代碼

  1. var myScript = function(){   
  2. //這些都是私人方法和屬性   
  3. var nav = document.getElementById('nav');   
  4. function init(){   
  5. // do stuff   
  6. }   
  7. function show(){   
  8. // do stuff   
  9. }   
  10. function reset(){   
  11. // do stuff   
  12. }   
  13. //公有的方法和屬性被使用對象文法封裝在return 語句裡面   
  14. return {   
  15. public:function(){   
  16.   
  17. },   
  18. foo:'bar'  
  19. }   
  20. }();  
var myScript = function(){//這些都是私人方法和屬性var nav = document.getElementById('nav');function init(){// do stuff}function show(){// do stuff}function reset(){// do stuff}//公有的方法和屬性被使用對象文法封裝在return 語句裡面return {public:function(){},foo:'bar'}}();

你可以使用和前面的代碼同樣的方式訪問返回的公有的屬性和方法,在本樣本中可以這麼訪問:myScript.public() 和 myScript.foo 。但是這裡還有一點讓人覺得不舒服:當你想要從外部或者從內部的一個私人方法中訪問公有方法的時候,還是要寫一個冗長的名字(對象的名字可以非常長)。為了避免這一點,你需要將它們定義為私人的並且在return語句中只返回一個別名: Java代碼

  1. var myScript = function(){   
  2. // 這些都是私人方法和屬性   
  3. var nav = document.getElementById('nav');   
  4. function init(){   
  5. // do stuff   
  6. }   
  7. function show(){   
  8. // do stuff   
  9. // do stuff   
  10. }   
  11. function reset(){   
  12. // do stuff   
  13. }   
  14. var foo = 'bar';   
  15. function public(){   
  16.   
  17. }  
var myScript = function(){// 這些都是私人方法和屬性var nav = document.getElementById('nav');function init(){// do stuff}function show(){// do stuff// do stuff}function reset(){// do stuff}var foo = 'bar';function public(){}

//只返回指向那些你想要訪問的私人方法和屬性的指標 Java代碼

  1. return {   
  2. public:public,   
  3. foo:foo   
  4. }   
  5. }();  
return {public:public,foo:foo}}();

這就保證了代碼風格一致性,並且你可以使用短一點的別名來訪問其中的方法或屬性。

如果你不想對外部暴露任何的方法或屬性,你可以將所有的代碼封裝到一個匿名方法中,並在它的定義結束後立刻執行它:

Java代碼
  1. (function(){   
  2. // these are all private methods and properties   
  3. var nav = document.getElementById('nav');   
  4. function init(){   
  5. // do stuff   
  6. show(); // 這裡不需要類名首碼   
  7. }   
  8. function show(){   
  9. // do stuff   
  10. }   
  11. function reset(){   
  12. // do stuff   
  13. }   
  14. })();  
(function(){// these are all private methods and propertiesvar nav = document.getElementById('nav');function init(){// do stuffshow(); // 這裡不需要類名首碼}function show(){// do stuff}function reset(){// do stuff}})();

對於那些只執行一次並且對其它函數沒有依賴的代碼模組來說,這種模式非常好。

通過遵循上面的那些規則,你的代碼更好地為使用者工作,也可以使你的代碼在機器上更好地運行並與其他開發人員的代碼和睦相處。不過,還有一個群體需要考慮到。

7.為接手的開發人員考慮
(使維護更加容易)

使你的指令碼真正地unobtrusive的最後一步是在編寫完代碼之後仔細檢查一遍,並且要照顧到一旦指令碼上線之後要接手你的代碼的開發人員。考慮下面的問題:

    * 所有的變數和函數名字是否合理並且易於理解?
    * 代碼是否經過了合理的組織?從頭到尾都很流暢嗎?
    * 所有的依賴都顯而易見嗎?
    * 在那些可能引起混淆的地方都添加了注釋嗎?

最重要的一點是:要認識到文檔中的HTML和CSS代碼相對於JavaScript來說更有可能被改變(因為它們負責視覺效果)。所以不要在指令碼代碼中包含任何可以讓終端使用者看到的class和ID,而是要將它們分離出來放到一個儲存配置資訊的對象中。

Java代碼
  1. myscript = function(){   
  2. var config = {   
  3. navigationID:'nav',   
  4. visibleClass:'show'  
  5. };   
  6. var nav = document.getElementById(config.navigationID);   
  7. function init(){   
  8. show();   
  9. if(nav.className === config.visibleClass){   
  10. reset();   
  11. };   
  12. // do stuff   
  13. };   
  14. function show(){   
  15. var c = nav.className;   
  16. // do stuff   
  17. };   
  18. function reset(){   
  19. // do stuff   
  20. };   
  21. }();  
myscript = function(){var config = {navigationID:'nav',visibleClass:'show'};var nav = document.getElementById(config.navigationID);function init(){show();if(nav.className === config.visibleClass){reset();};// do stuff};function show(){var c = nav.className;// do stuff};function reset(){// do stuff};}();

這樣維護者就知道去哪裡修改這些屬性,而不需要改動其他代碼。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.