做者 Christian Heilmann · 2007年12月19日javascript
本文翻譯自 The seven rules of Unobtrusive JavaScriptjava
在開發、教學和實現 Unobtrusive JavaScript 的工做中,我總結了下面七條規則。本文實際是關於 Unobtrusive javascript 的一次會議上(Paris Web conference 2007 ,巴黎)的發言稿提綱。設計模式
我但願本文能幫你理解爲何要如此編寫 JavaScript 代碼。它曾幫助我更快的交付產品、提升產品質量並減輕維護工做量。瀏覽器
Unobtrusive JavaScript 最重要的規則就是中止作任何假設:安全
不要假設 JavaScript 必定可使用,不該依賴 JavaScript 而應當把它看成助手。app
不要假設瀏覽器支持某些方法及具備某些屬性,在使用前測試是否可用ide
不要假設 HTML 代碼是正確的,使用前最好先檢查器正確性函數
保持功能獨立於輸入設備測試
你要考慮到其餘腳本可能會影響你的代碼,應讓你的代碼做用域儘量安全spa
在開始編寫腳邊以前需作的第一件事就是查看要用腳本加強的HTML代碼,並想一想有哪些能夠加以利用。
在開始編寫腳本以前先看看 HTML 文檔。若是 HTML 結構混亂,很難爲之建立漂亮的腳本方案——要麼你不得不用 JavaScript 建立大量的標記;要麼你的網頁要依靠 JavaScript 。.
HTML 代碼中須要考慮下面問題:鉤子和關係。
最重要的 HTML 鉤子是 ID,由於可經過最快的 DOM 方法 getElementById
獲取標記元素。合法的 HTML 文檔中 ID 是惟一的 ( IE 中有一個關於 name 和 ID 的 bug,但好的庫都能繞開此 bug ),因此在其中使用 ID 和安全的。
其餘鉤子是可以使用 getElementsByTagName
獲取的 HTML 元素和 CSS class——大部分瀏覽器中沒有原生 DOM 方法可用於獲取 CSS class ( Mozilla 之後將有,Opera 9.5 已經包含);但有不少庫提供 getElementsByClassName
以訪問 CSS class。
HTML 另外一個頗有趣的地方時標記間的關係。請問問本身下面問題:
如何最快最簡單的遍歷 DOM 並得到所需元素?
當須要改變多個子元素時,應該改變哪一個元素?
元素的哪些屬性或信息能夠幫助訪問其餘元素?
遍歷 DOM 樹是較慢的操做,這就是爲何最好採用已在瀏覽器中被使用的技術。
DOM 提供了遍歷 DOM 的方法和屬性 ( getElementsByTagName
, nextSibling
, previousSibling
, parentNode
等),這些屬性經常讓人迷惑。有趣的是咱們已有達到此目的的技術: CSS。
CSS 經過 CSS 選擇器遍歷 DOM 以訪問所需的元素並改變其視覺屬性。可用簡單的 CSS 選擇器取代複雜的 JavaScript DOM 函數:
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'; } } } /* is the same as */ #nav a{ color:#369; text-decoration:none }
CSS 是強大的能夠加以利用的技術。能夠動態地給高層的 DOM 元素添加 class,或者修改 ID 。如使用 DOM 給 body 添加 class,那網頁設計者能夠輕鬆地定義網頁的靜態和動態版本:
JavaScript:var dynamicClass = 'js'; var b = document.body; b.className = b.className ? b.className + ' js' : 'js';CSS:/* static version */ #nav { .... } /* dynamic version */ body.js #nav { .... }
Unobtrusive JavaScript 的一個重要部分就是理解瀏覽器如何工做 (特別理解爲什麼瀏覽器會不能正常工做),並理解用戶指望是什麼。開發者很容易輕率的使用 JavaScript 建立徹底不一樣的界面:可拖拽界面、可摺疊區域、滾動條,這些均可以經過 JavaScript 實現,但這不只僅是技術問題。你應該問問你本身:
個人新用戶界面獨立於輸入設備麼?若是答案是否,那備用方案是什麼?
新界面是否符合瀏覽器規則或富界面( richer interface)規則?(你能使用光標在多級菜單中導航麼?仍是須要使用 tab ?)
什麼功能是須要提供的,但又依賴於 JavaScript 的?
最後一項不是問題,在須要時可使用 DOM 建立 HTML 。一個很好的例子就是"打印此頁"連接 —— 瀏覽器沒有提供非 JavaScript 方式打印網頁,因此你須要使用DOM建立此類連接。一樣的,可點擊並可展開/收縮其內容的標題也須要使用 DOM 建立。沒法使用鍵盤激活標題,但能夠激活連接。爲了建立可點擊的標題,你應該使用JavaScript爲其添加連接 —— 這樣即便是鍵盤用戶也能夠展開/收縮內容。
解決此類問題的很好的資源就是設計模式庫。知道瀏覽器中什麼特性獨立與輸入設備是一個經驗問題。首先須要理解事件處理概念。
事件處理是 Unobtrusive JavaScript 的重要一環。重點不是讓全部元素均可以點擊並拖拽或添加內聯處理;重點是理解事件處理是真正的分離。咱們分離了 HTML, CSS 和 JavaScript ,但有了事件處理可更進一步。
網頁中的元素等待處理器監聽狀態變化。變化發生後,處理器得到「神奇的」對象 (一般是名爲e
的參數),此對象會告知哪些元素髮生了哪些事件,及如何應變。
事件處理很酷的一點是事件不只發生在一個元素中,事件會達到 DOM 樹中全部的祖先元素 (全部事件都有此特性,但 focus 和 blur 除外)。這容許你爲多個元素設置同一個事件處理器(如爲導航列表),使用事件處理器方法便可獲取真正觸發事件的元素。此技巧被稱做事件委託( event delegation ),其優勢以下:
你只需測試一個元素是否存在,沒必要測試全部元素
能夠動態添加或刪除子元素,而不須要移除或添加相應新事件處理器
能夠響應不一樣元素的同一個事件
另外能夠中止事件向父節點傳播,也能夠重載 HTML 元素(如連接)的默認行爲。但有時這不是一個好主意,瀏覽器這麼作是有緣由的。好比,指向頁內目標的連接,容許它們被跟隨能夠確保用戶可將腳本狀態也加入書籤。
你的代碼不可能只用於這一個網頁。所以須要確保你的代碼中沒有全局函數且沒有變量名稱不會和其餘腳本衝突。有不少方案能夠保證這一點。最簡單的方法就是爲每個變量初始化使用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:documentgetElementById('nav'), init:function(){ // do stuff }, show:function(){ // do stuff }, reset:function(){ // do stuff } }
在對象內外均可以經過使用對象名加'.'來使用對象屬性或者對象方法。
var myScript = { nav:documentgetElementById('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 } }
這種方法的缺點是每當須要使用變量或方法時都須要重複輸入對象名,且對象中全部變量和方法都是能夠公開訪問的。若是你想讓部分腳本成爲公開訪問的呢?可使用模塊模式:
var myScript = function(){ // these are all private methods and properties var nav = document.getElementById('nav'); function init(){ // do stuff } function show(){ // do stuff } function reset(){ // do stuff } // public methods and properties wrapped in a return // statement and using the object literal return { public:function(){ }, foo:'bar' } }();
能夠公開訪問的屬性和方法被封住進 return 語句。上例中 myScript.public()
和 myScript.foo
能夠在外部使用。煩人的是若是你想在另外一個公開方法或者私有方法中使用某個公開方法,你必需要使用很長的名稱 (主對象名可能會很長)。爲克服此缺點,將其定義爲私有方法並返回別名:
var myScript = function(){ // these are all private methods and properties 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 pointers to the private methods and // properties you want to reveal return { public: public, foo:foo } }();
這保持了代碼風格的一致性並可以使用短一些的名稱訪問。
若是你不想讓外面代碼調用函數,可將代碼封裝在匿名函數中並在定義後當即使用:
(function(){ // these are all private methods and properties var nav = document.getElementById('nav'); function init(){ // do stuff show(); // no need for prepended object name } function show(){ // do stuff } function reset(){ // do stuff } })();
這對那些只執行一遍且不依賴其餘函數的函數來講是一個很好的模式。
遵循上述規則可讓你的代碼更好的爲用戶服務、並更好的在機器中與其餘人的代碼相互配合工做。但此外你還須要考慮另外一羣人。
編寫Unobtrusive JavaScript的最後一個環節是在開發結束後從新審視代碼,併爲腳本上線後接手此代碼的下一位開發者着想:
每個變量名和函數名都符合邏輯且易懂麼?
代碼結構如何?你能順暢的從頭讀到尾麼?
全部的依賴性都顯而易見麼?
在那些容易引發疑惑的地方你添加註釋了麼?
須要牢記的是 HTML 和 CSS 相比JavaScript來講更可能被改變 (由於他們構成了視覺輸出)。所以不要讓 class 、 ID 名稱及要顯示給用戶的字符串被深深「埋藏」在你的代碼中,最好將其統一放置在 config 對象中。
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 }; }();
這樣代碼維護人員可知道去何處修改代碼,且沒必要改動其餘代碼。