今天在web上任何大一點的網(wǎng)站或應用程序都包含大量的html,css和javascript。隨著互聯(lián)網(wǎng)的發(fā)展和我們對互聯(lián)網(wǎng)越來越依賴,計劃組織和維護你的前端代碼是絕對必要的。
今天大一點的web公司,越來越多的人接觸越到比例越來越大的前端代碼。因此,大多數(shù)公司努力保持他們的代碼模塊化。改變一個應用程序的一部分經(jīng)常無意中影響到看似不相關的其他部分。
防止意外的影響不是一個容易解決的問題,特別是由于HTML、CSS和JavaScript本身是相互關聯(lián)的。
更糟糕的是,傳統(tǒng)的計算機科學原則如分離關注點,一直是服務器端開發(fā)的一部分,卻很少被討論在前端代碼中。
在本文中,我將談談我是如何學會分離我的HTML、CSS和JavaScript。據(jù)我的經(jīng)驗和我所知道的其他人的經(jīng)驗,完成目標的最好的方式是不明顯,反直覺的,有時不利于大量所謂的“最佳實踐”的。
HTML、CSS和JavaScript之間總是會有一些耦合。不管怎樣,這些技術被用來互相作用。舉個例子,一個fly-in transition(飛入變換)可能在樣式表中定義了一個類選擇器,但它通常是由用戶交互來觸發(fā),通過JavaScript來執(zhí)行,并且通過在html中添加一個類來初始化。
由于在你的前端代碼中存在一些耦合是不可避免的。你的目標不應該是簡單地完全消除耦合,而應該是最小化耦合,使這部分代碼不必依賴其他部分。
后端開發(fā)人員應該能夠改變HTML模板中的標記而不用擔心不小心破壞一個CSS規(guī)則或一些JavaScript功能。隨著今天的Web團隊規(guī)模的增大和專業(yè)化,這一目標是比以往更重要。
前端代碼是不是緊密耦合并不是明顯的。然而雪上加霜的是,從一個角度看似松散耦合從另一個角度看事實上卻是緊密耦合的。
以下都是反模式,包括我多次見過或我親自做過并從我的錯誤中學會的。對每一個,我試圖解釋為什么耦合是不好的,以及如何避免。
CSS禪意花園向世界證明了你可以完全改變整個網(wǎng)站的外觀并絲毫沒有改變標記。這是語義Web的運動的海報,和它的一個主要原則是避免使用表象的類。
乍一看,CSS禪意花園可能看起來像一個解耦的很好的例子。畢竟,它的全部意義是將樣式從標記分離。然而,問題是,要做到這一點,你通常需要在你的樣式表看起來像下面這樣的選擇器:
#sidebar section:first-child h3 + p { }
CSS禪意花園中,HTML幾乎完全脫離了CSS,但CSS對html是超級耦合和需要結構的標記的知識非常了解。
這看上去還不錯如果一個人即維護CSS還維護HTML,但是一旦你增加一些更多的人到你的組合,它很快就會失控。如果一個開發(fā)者來臨并添加一個<div>在<section>之前,上述規(guī)則將不會工作,開發(fā)人員可能不知道自己做了什么。
CSS禪意花園是一個偉大的想法,只要你的網(wǎng)站的標記很少改變。但今天的Web應用程序通常不會有這樣的情形。
代替冗長,復雜的CSS選擇器,最好(只要有可能)風格是你所有的可視化組件有一個或多個類在根元素組件自身上。例如,如果你在你的邊欄里有一個子菜單,僅僅添加submenu類到每一個子菜單元素,而不要像下面這樣:
ul.sidebar > li > ul {
/* submenu styles */
}
這種方法最終需要更多的類在HTML中,但它會降低(html,css)之間的耦合,從長遠來看這使CSS代碼更可重用和可維護。它也可以讓你的標記標記標記自我(self-documenting)。如果在HTML中沒有類,開發(fā)人員不清楚CSS無法預知她的標記變化將影響其他的代碼。另一方面,如果在HTML有類那很明顯元素被應用的樣式和功能。
有時一個類即用于樣式的目的,還作為一個JavaScript鉤子(javascript選擇符)。雖然這可能看起來像一個節(jié)約(因為它需要一個更少的類標記)實際上一個元素表示和行為的耦合。
<button class="add-item">Add to Cart</button>
上面的例子展示了一個“添加到購物車”按鈕樣式與添加物品類。
如果開發(fā)者想要給這個元素添加一個單擊事件監(jiān)聽器,它可以很容易將已經(jīng)存在的類當作掛鉤。我的意思是,如果一個(類)已經(jīng)存在,為什么要添加另一個呢?
但是想象一下,在整個網(wǎng)站里,有很多這些按鈕,他們?nèi)伎雌饋硪粯?并且全部調(diào)用相同的JavaScript功能。然后想象一下營銷團隊想要其中一個按鈕看起來完全與眾不同。也許它需要很多更大的更吸引眼球的顏色。
這里有一個問題,因為JavaScript代碼里單擊事件監(jiān)聽器期待 add-item 類繼續(xù)被使用,但是你的新按鈕現(xiàn)在不能使用這個類(或它必須復原一切已經(jīng)聲明的(樣式)并且重置新樣式)。更大的問題是,如果你有一些測試代碼,也期待著add-item類出現(xiàn),所以你也會有另一個地方的代碼(測試代碼)需要更新。
更糟糕的是如果你的 “Add to Cart” 功能使用的不僅僅是這個應用程序。如果代碼已經(jīng)被抽象到一個獨立的模塊然后一個簡單的風格改變現(xiàn)在可能在完全不同的應用程序間引入bug。
使用類作為JavaScript鉤子是完全可以接受的(實際上鼓勵),但是如果你要這樣做,要按這種方法,避免樣式類和行為類之間的耦合。
我個人的建議是為所有JavaScript鉤子使用前綴。我使用 js - *。這樣,當一個開發(fā)人員在HTML源碼中看到這樣一個類(帶前綴),她就清楚知道到哪里去找使用這個類的目的。
上面的例子中 “Add to Cart” 將被重寫為:
<button class="js-add-to-cart add-item">Add to Cart</button>
現(xiàn)在,如果一個特定的 “Add to Cart”按鈕需要看起來不同,您可以簡單地改變樣式類和分離行為類本身。
<button class="js-add-to-cart add-item-special">Add to Cart</button>
同樣,JavaScript可以使用類來查找DOM中的元素,它也可以添加或刪除類來改變元素的樣式。但這可能產(chǎn)生一個問題,如果這些類不是明顯區(qū)別于出現(xiàn)在頁面加載中的類。
如果JavaScript對關于組件樣式代碼知道的太多,下面的情況將變得非常平常,一個CSS開發(fā)人員修改樣式表并沒有意識到他正在破壞關鍵的功能。
這并不是說JavaScript不應該更改組件的外觀在用戶與他們交互后,但應該通過一種一致的接口。它應該使用與定義的默認樣式類有明顯區(qū)別的類。
類似于 js-* 前綴的類,我建議使用 is-* 前綴定義改變可視化組件狀態(tài)的類選擇器。一個CSS規(guī)則示例可能看起來像這樣:
.pop-up.is-visible { }
注意,狀態(tài)類(is-visible)和組件類(pop-up)是交集關系,這一點是重要的。因為狀態(tài)規(guī)則描述組件的狀態(tài),他們不應該獨立出現(xiàn)。這區(qū)別有助于進一步區(qū)分狀態(tài)樣式是自默認組件樣式的特殊情況。
此外,為了使用一個類似 is-* 的前綴,我們可以編寫測試程序來確保我們遵循了規(guī)則。一個測試這些類型的規(guī)則方法是使用 CSSLint 和 HTML Inspector。
更多關于特定狀態(tài)類的信息中可以在優(yōu)秀的書 SMACSSJonathan Snook 中找到。
jQuery 和新的APIs 如document.querySelectorAll使用戶非常容易在DOM中通過語言他們熟悉的CSS選擇器查找元素。雖然這是非常強大的,但它有已經(jīng)CSS選擇器出現(xiàn)的同樣的問題。
JavaScript“選擇器”不應該過度依賴于DOM結構。這樣的選擇器是非常慢的,而且需要非常了解HTML知識。
參考第一個示例,開發(fā)人員在一個HTML模板上工作應該能夠對標記做基本更改而不用擔心會基本功能產(chǎn)生影像。如果功能是可以打破的,它應該在標記能明顯看出。
我已經(jīng)提到過 js-* 前綴的類應該作為JavaScript鉤子。除了將樣式類從功能類分離,他們還表達標記的意圖。當有人在編輯HTML是看到一個 js-*前綴的類,他們就會知道這是用來做什么的。如果JavaScript代碼查詢元素不依賴特定的標記結構,在標記中不是明顯的什么功能正在發(fā)生。
代替長DOM結構復雜的選擇器,堅持單類選擇器或id選擇器。
考慮下面的代碼:
var saveBtn = document.querySelector("#modal div:last-child > button:last-child")
這長的選擇器使您不必給HTML添加一個類,也會讓你的代碼很容易受標記的變化的影像。如果設計師突然決定把保存按鈕和取消按鈕交換位置,上面的選擇器將不再匹配。
一個更好的方法(使用上面提到的前綴的方法)僅僅是使用一個類。
var saveBtn = document.querySelector(".js-save-btn")
現(xiàn)在的標記可以隨意改變,只要類一直在正確的元素上,一切都將很好地工作。
幾乎所有類型的HTML、CSS和JavaScript之間的耦合可以被減輕通過適當?shù)氖褂玫念惡鸵粋€可預測的類命名約定。
乍一看在標記中使用大量的類這可能看起來像一個高耦合的信號,因為HTML需要知道這些類的名字。但我認為使用類的做法非常類似傳統(tǒng)的程序設計事件模型(隱式風格調(diào)用)或外觀模式(觀察者模式或發(fā)布訂閱模式)。
在事件驅動編程中,取而代之對象 a 調(diào)用對象 b 一個方法,對象 a 只是簡單的在一個給定的情況會下會觸發(fā)特定的事件,而對象b可以訂閱該事件。這種方式,對象a不需要知道任何關于對象b 的接口,只需要知道要監(jiān)聽的事件。
可以說事件系統(tǒng)存在一個形式的耦合由于對象B需要知道事件的名字從而訂閱,但和對象a需要知道對象B的公共方法相比它是相當松散的耦合。
給HTML添加類是非常相似的。取而代之在CSS文件定義復雜的選擇器(這相當于知道HTML的內(nèi)部接口),它可以通過一個類簡單地定義組件的視覺外觀。HTML可能會選擇使用或不使用這個類,CSS無需關心。
同樣,JavaScript不需要復雜DOM序列功能,這也需要非常了解html結構知識,它可以簡單的監(jiān)聽用戶的交互元素,通過協(xié)商的類名。
類應該是將你的HTML、CSS和JavaScript連接在一起的紐帶。在我的經(jīng)驗中這是是最簡單和最棒的方法來連接這三個技術而沒有將他們混合在一起太多。
WHATWG目前正在為Web組件規(guī)范工作,這將允許開發(fā)人員將HTML、CSS和JavaScript壓在一起作為單獨的組件或模塊從而從其余的頁面封裝。
當有一天規(guī)范在大多數(shù)瀏覽器中實現(xiàn),我在這篇文章中給的很多建議將變得不那么關鍵(因為它會更加明顯代碼是為了什么工作);然而,它仍然是重要的,理解更廣泛的原則和為什么需要他們。
即使在Web組件的時代這些做法變得不那么重要,這一理論仍然適用。用于大型的團隊和大型應用程序的做法,仍然會工作對于個體開發(fā)人員寫的小模塊。相反是不必要的情況。
編寫可維護的HTML、CSS和JavaScript的要點是個體開發(fā)人員可以很容易地和自信地編輯部分的代碼庫而這些變化不經(jīng)意間影響其他不相關的部分。
防止意想不到的后果的一個最好的方法來是通過一組可預見的類來連接這三個技術,任何開發(fā)人員遇到這些類都能清楚它們的意圖和選擇的他們目的。
為了避免上面的反模式,牢記下面的原則:
這種方式往往在HTML中呈現(xiàn)大量類,但在可預見性和可維護性上的收益是值得的。畢竟,在HTML中添加類是相當容易,并大多數(shù)開發(fā)人員的技術水平可以做到。引用 Nicolas Gallagher的一段話:
當你選擇修改HTML和CSS,試圖減少你花在寫作和編輯CSS上的大量時間,這包括接受,你必須花更多的時間更改HTML元素的類,如果你想改變元素的風格。這被證明是相當實用的,包括前端和后端開發(fā)人員——任何人都可以重新排列預先構建的“樂高積木”;事實證明,沒有人可以執(zhí)行css煉金術。
更多建議: