當前在互聯網上,任何一個稍微復雜的網站或者應用程序都會包含許多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 翻譯: 伯樂在線 - 蟈蟈
文章列表