經過多年的開發、教學和編寫不唐突的JavaScript, 我發現了下面的一些準則.我希望它們可以幫助你對「為什麼這樣設計和執行JavaScript比較好」有一點理解.這些規則曾經幫助我更快地交付產品,並且產品的質量更高,也更容易維護.
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選擇器取代:
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類,那麼設計師就很可以容易地定義文檔的靜態版本和動態版本.
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 關鍵字來初始化所有的變數.假設我們編寫了下面的腳本:
var nav = document.getElementById('nav'); function init(){ // do stuff } function show(){ // do stuff } function reset(){ // do stuff } |
上面的代碼中包含了一個叫做nav的全局變數和名字分別為 init,show 和 reset 的三個函數.這些函數都可以訪問到nav這個變數並且可以通過函數名互相訪問:
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 } |
你可以將代碼封裝到一個對象中來避免上面的那種全局式編碼,這樣就可以將函數變成對象中的方法,將全局變數變成對象中的屬性. 你需要使用「名字 冒號」的方式來定義方法和屬性,並且需要在每個屬性或方法後面加上逗號作為分割符.
var myScript = { nav:document.getElementById('nav'), init:function(){ // do stuff }, show:function(){ // do stuff }, reset:function(){ // do stuff } } |
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)模式:
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語句中只返回一個別名:
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(){ } //只返回指向那些你想要訪問的私有方法和屬性的指針 return { public:public, foo:foo } }(); |
這就保證了代碼風格一致性,並且你可以使用短一點的別名來訪問其中的方法或屬性.
如果你不想對外部暴露任何的方法或屬性,你可以將所有的代碼封裝到一個匿名方法中,並在它的定義結束后立刻執行它:
(function(){ // these are all private methods and properties var nav = document.getElementById('nav'); function init(){ // do stuff show(); // 這裡不需要類名前綴 } function show(){ // do stuff } function reset(){ // do stuff } })(); |
對於那些只執行一次並且對其它函數沒有依賴的代碼模塊來說,這種模式非常好.
通過遵循上面的那些規則,你的代碼更好地為用戶工作,也可以使你的代碼在機器上更好地運行並與其他開發者的代碼和睦相處.不過,還有一個群體需要考慮到.
7.為接手的開發者考慮(使維護更加容易)
使你的腳本真正地unobtrusive的一步是在編寫完代碼之後仔細檢查一遍,並且要照顧到一旦腳本上線之後要接手你的代碼的開發者.考慮下面的問題:
所有的變數和函數名字是否合理並且易於理解?
代碼是否經過了合理的組織?從頭到尾都很流暢嗎?
所有的依賴都顯而易見嗎?
在那些可能引起混淆的地方都添加了註釋嗎?
最重要的一點是:要認識到文檔中的HTML和CSS代碼相對於JavaScript來說更有可能被改變(它們負責視覺效果).所以不要在腳本代碼中包含任何可以讓終端用戶看到的class和ID,而是要將它們分離出來放到一個保存配置信息的對象中.
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 }; }(); |
這樣維護者就知道去哪裡修改這些屬性,而不需要改動其他代碼.
[火星人 ] 不唐突的JavaScript的七條準則已經有891次圍觀