解耦HTML、CSS和JavaScript

jobbole發表於2013-09-16

  當前在網際網路上,任何一個稍微複雜的網站或者應用程式都會包含許多HTML、CSS和JavaScript。隨著網際網路運用的發展以及我們對它的依賴性日益增加,設定一個關於組織和維護你的前端程式碼的計劃是絕對需要的。

 

  當今的一些大型網際網路公司,由於越來越多的人會接觸到日益增加的前端程式碼,它們會試圖去堅持程式碼的模組化。這樣更改程式的部分程式碼,並不會無意中過多地影響後續不相關部分的執行過程。

  防止意想不到的後果不是一個容易解決的問題,尤其是HTML,CSS和JavaScript本質上是相互依賴的。更糟糕的是,當涉及到前端程式碼時,一些傳統電腦科學原則,比如關注分離,這一長期運用在服務端開發中,很少會討論到。

  在本文中,我將會講講我所學到的如何去解耦我的HTML,CSS和JavaScript程式碼。從個人以及他人經驗所得,這種的最好辦法並不是那麼顯而易見,而通常是不直觀的,而且有時還會與許多所謂的最佳實踐相違背。

 目標

  HTML,CSS和JavaScript之間總會存在耦合關聯。不管怎樣,這些技術與生俱來就是要和其它進行互動。舉個例子,一種飛閃轉換效果可能會在樣式表中用帶有類選擇器定義,但它經常由HTML初始化,並通過使用者互動,如編寫JavaScript,來觸發。由於前端程式碼的有些耦合是不可避免的,你的目標就不應該是簡單地消除之間的耦合,而應該是減少程式碼間不必要的依賴耦合關係。一個後端開發者應該能夠對HTML模板中的標記進行更改,而無需擔心意外破壞CSS規則或者一些JavaScript功能。由於當今的web團隊日漸增大且專業化,這個目標比以往更甚。

 反模式

  前端程式碼的緊耦合現象並不總是很明顯。事實上覆雜的是,一方面看起來似乎鬆耦合,但從另一方面則是緊耦合。以下是我曾經多次做過或者看過,以及吸取我的過錯中,總結的所有的反模式。對每一個模式,我會嘗試去解釋為何耦合這麼糟糕,並且指出如何去避免它。

  過度複雜的選擇器

  CSS Zen Garden向世界展示了你可以完全改變整個網站的外觀而無需更改任意一個的HTML標記。這是語義網運動的典型代表,主要原則之一就是去避免使用表象類。乍一看,CSS Zen Garden可能看起來像是一個很好的解耦合例子,畢竟,把樣式從標記語言中分離出來是它的重點所在。但是,若按照這樣做,問題就來了,你會經常需要在你的樣式表裡有這樣的選擇器,如下:

#sidebar section:first-child h3 + p { }

  CSS Zen Garden中,雖然HTML幾乎與CSS完全分離,但CSS會強耦合到HTML中去,此時就需要你對標記語言的結構有深層次的理解。這可能看起來似乎並不是很糟糕,尤其是某人維護著CSS,同時需要維護HTML,但一旦你增加了許多人手進去,這種情況就變得無法控制了。如果某個開發者在某種情況下在第一個<section>前增加了<div>,上面的規則就無法生效,然而他也不清楚其中緣由。

  只要你網站的標記很少改動,CSS Zen Garden就是一個非常不錯的主意。但是這和當今的Web應用不盡然都是這種情況。與冗長而又複雜的CSS選擇器相比,最好的辦法是在視覺化元件本身的根元素增加一個或多個類選擇器。比如,如果側邊欄有子選單,只需要為每個子選單元素增加submenu類選擇器,而不要用這樣的形式:

ul.sidebar > li > ul {
  /* submenu styles */
}

  這種方式的結果是在HTML中需要更多的類選擇器,但從長遠來看,這又降低了耦合度,以及讓程式碼更可重用和可維護,並且還能讓你的標記自文件化。如果HTML裡沒有類選擇器,那些對CSS不熟悉的開發者就不清楚HTML的改動如何影響了其它程式碼。另一方面,在HTML中使用類選擇器能很清晰地看到那些樣式或者功能被使用到了。

 多個類選擇器的職責

  一個類選擇器往往是用來同時作為樣式和JavaScript的鉤子。雖然這看起來似乎很節約(因為至少減少了一個類標記),但事實上,這是把元素的表現和功能耦合起來了。

<button class="add-item">Add to Cart</button>

  以上例子描述了一個帶有add-item類樣式的”新增到購物車”按鈕。

  如果開發者想為此元素新增一個單擊事件監聽器,用已經存在的類選擇器作為鉤子非常的容易。我的意思是,既然已經存在了一個,為何要新增另一個呢? 但是想想看,有很多像這樣的按鈕,遍佈了整個網站,都呼叫了相同的JavaScript功能。再想想看,如果市場團隊想要其中一個和其它看起來完全不同但功能相同的按鈕呢。也許這樣就需要更多顯著的色彩了。

  問題就來了,因為監聽單擊事件的JavaScript程式碼希望add-item類選擇器被使用到,但是你新的按鈕又無法使用這個樣式(或者它必須清除所有宣告的,然後再重置新的樣式)。還有,如果你測試的程式碼同時也希望使用add-item類選擇器,那麼你不得不要去更新那麼程式碼用到的地方。更糟糕的是,如果這個”新增到購物車”功能不僅僅是當前應用用到的話,也就是說,把這份程式碼抽象出來作為一個獨立的模組,那麼即使一個簡單的樣式修改,可能會在完全不同的應用中引發問題。

  使用javaScript鉤子最好的(事實上也是比較鼓勵的)做法是,如果你需要這麼做,使用一種方式來避免樣式和行為類選擇器之間的耦合。

  我的個人建議是讓JavaScript鉤子使用字首,比如:js-*。這樣的話,當開發者在HTML原始碼中看到這樣的類選擇器,他就完全明白箇中原因了。所以,上述的”新增到購物車”的例子可以重寫成這樣:

<button class="js-add-to-cart add-item">Add to Cart</button>

  現在,如果需要一個看起來不同的按鈕,你可以很簡單地修改下樣式類選擇器,而不管行為的類選擇器。

<button class="js-add-to-cart add-item-special">Add to Cart</button>

 JavaScript更多的樣式操作

  JavaScript能用類選擇器去DOM中查詢元素,同樣,它也能通過增加或移除類選擇器來改變元素的樣式。但如果這些類選擇器和當初載入頁面時不同的話也會有問題。當JavaScript程式碼使用太多的組成樣式操作時,那些CSS開發者就會輕易去改變樣式表,卻不知道破壞了關鍵功能。也並不是說,JavaScript不應該在使用者互動之後改變視覺化元件的外觀,而是如果這麼做,就應該使用一種一致的介面,應該使用和預設樣式不一致的類選擇器。

  和js-*字首的類選擇器類似,我推薦使用is-*字首的類選擇器來定義那些要改變視覺化元件的狀態,這樣的CSS規則可以像這樣:

.pop-up.is-visible { }

  注意到狀態類選擇器(is-visible)是連線在元件類選擇器(pop-up)後,這很重要。因為狀態規則是描述一個的狀態,不應該單獨列出。如此不同就可以用來區分更多和預設元件樣式不同的狀態樣式。

  另外,可以讓我們可以編寫測試場景來保證像is-*這樣的字首約定是否遵從。一種測試這些規則的方式是使用CSSLint和HTML Inspector。

  更多關於特定狀態類選擇可以查閱Jonathan Snnok編寫的非常優秀的SMACSS書籍。

 JavaScript”選擇器”

  jQuery和新的API,像document.querySelectorAll,讓使用者非常簡單地通過一種他們已經非常熟悉的語言–CSS選擇器來查詢DOM中的元素。雖然如此強大,但同樣有CSS選擇器已經存在的相同的問題。JavaScript選擇器不應過度依賴於DOM結構。這樣的選擇器非常慢,並且需要更深入認識HTML知識。

  就第一個例子來講,負責HTML模板的開發者應該能在標記上做基本的改動,而不需擔心破壞基本的功能。如果有個功能會被破壞,那麼它就應該在標記上顯而易見。

  我已經提及到應該用js-*字首的類選擇器來表示JavaScript鉤子。另外針對消除樣式和功能類選擇器之間的二義性,需要在標記中表達出來。當某人編寫HTML看到js-*字首的類選擇器時,他就會明白這是別有用途的。但如果JavaScript程式碼使用特定的標記結構查詢元素時,正在觸發的功能在標記上就不那麼明顯了。

  為了避免使用冗長而又複雜的選擇器遍歷DOM,堅持使用單一的類或者ID選擇器。 考慮以下程式碼:

var saveBtn = document.querySelector("#modal div:last-child > button:last-child")

  這麼長的選擇器是可以節省你在HTML中新增一個類選擇器,但同樣讓你的程式碼對於標記更改非常容易受到影響。如果設計者突然決定要把保持按鈕放在左邊,而讓取消按鈕放在右邊,這樣的選擇器就不再匹配了。

  一個更好的方式(使用上述的字首方法)是僅僅使用類選擇器。

var saveBtn = document.querySelector(".js-save-btn")

  現在標記可以更改它想改的,並且只要類選擇還是在正確的元素上,一切都會很正常。

 類選擇器就是你的契約

  使用合適的類選擇器以及可預測的類名約定可以減少幾乎每一種HTML,CSS和JavaScript之間的耦合。起初由於為了展現HTML需要知道很多類選擇器的名稱,這種在標記中使用很多類選擇器看起來像是強耦合的跡象。但是我發覺,使用類選擇器和傳統程式設計設計中的事件或者觀察者模式非常相似。在事件驅動程式設計中,為了不直接在物件A上呼叫物件B,而是物件A簡單地在提供的環境中釋出一個特定的事件,然後物件B能夠訂閱那個事件。這樣,物件B就不需要知道任何關於物件A的介面,而僅僅需要知道監聽什麼事件。按理說,事件系統需要某種形式上的耦合,因為物件B需要知道訂閱的事件名稱,但和物件A需要知道物件B的公共方法相比,這已經更鬆散的耦合了。

  HTML類選擇器都非常相似。與CSS檔案中定義複雜的選擇器(就像HTML的內部介面一樣)不同的是,它可以通過單一類選擇器簡單定義一個視覺化元件的外觀。CSS檔案不需要關心HTML對類選擇器的使用與否。同樣,JavaScript不用那些需要更深入理解HTML結構的複雜DOM遍歷功能,而是僅僅監聽與類名一致的元素的使用者互動。類選擇器應該像是膠水一樣,把HTML,CSS和JavaScript連線在一起。從個人經驗得知,它們也是最容易以及最好的方式把三者技術連線起來,而不是混合過度。

 未來

  網頁超文字技術工作小組(WHATWG)正在致力於web元件的規範,能讓開發者把HTML,CSS和JavaScript繫結一起作為一個單獨的元件或者模組,並與其它的頁面元素進行互動封裝。如果這個規範已經在大多數的瀏覽器中實現的話,那麼我在本文中提供的很多建議就變得不那麼重要了(因為程式碼和誰互動變得很清晰);但是無論如何,理解這些更廣泛的原則以及為何需要它們仍然很重要。即使這些實踐在Web元件時代會變得不那麼重要,但其中的理論仍然適用。在大型團隊和大型應用中的實踐仍然要適用於小模組的編寫中,反之則不需要。

 結論

  可維護的HTML,CSS和JavaScript的標誌是每個開發者可以容易並且很自信地編寫程式碼庫的每個部分,而不需擔心這些修改會無意中影響到其它不相關部分。阻止這樣意想不到的後果的最佳方式之一是,通過一組能夠表達其義的,任何開發者碰到時能想出它的用途的,可預測的人性化的類選擇器名,把這三者技術結合在一起。

  為避免上述的反模式,請把下述的原則謹記於心:

  • 1. 在CSS和JavaScript裡,優先考慮顯式元件和行為類選擇器,而不是複雜的CSS選擇器。
  • 2. 命名元件要基於它們是什麼,而不是它們在哪裡
  • 3. 不用為樣式和行為使用相同的類選擇器去
  • 4. 把狀態樣式和預設樣式區分開來

  在HTML中這樣運用類選擇器經常會需要很多需要表現的類選擇器,但獲取的是可預見性和可維護性,這點值得肯定。畢竟,為HTML增加類選擇器是相當容易的,不需要開發者有多少技能。摘自Nicolas Gallagher的原話:

  當你要尋找一種方式來減少花費在編寫和修改CSS的時間上來製作HTML和CSS時,這就涉及到你必須接受如果你想更改樣式,你是不想花費更多時間去更改HTML元素上的類選擇器。這對前端和後端開發者都有一定的實用性,任何人都可以重新安排預構建的樂高積木。這樣沒有人會去展示CSS的魔力了。

  原文連結: Philip Walton   翻譯: 伯樂線上 - 蟈蟈

相關文章