解耦HTML、CSS和JavaScript
當(dāng)前在互聯(lián)網(wǎng)上,任何一個(gè)稍微復(fù)雜的網(wǎng)站或者應(yīng)用程序都會(huì)包含許多HTML、CSS和JavaScript。隨著互聯(lián)網(wǎng)運(yùn)用的發(fā)展以及我們對(duì)它的依賴性日益增加,設(shè)定一個(gè)關(guān)于組織和維護(hù)你的前端代碼的計(jì)劃是絕對(duì)需要的。
當(dāng)今的一些大型互聯(lián)網(wǎng)公司,由于越來越多的人會(huì)接觸到日益增加的前端代碼,它們會(huì)試圖去堅(jiān)持代碼的模塊化。這樣更改程序的部分代碼,并不會(huì)無意中過多地影響后續(xù)不相關(guān)部分的執(zhí)行過程。
防止意想不到的后果不是一個(gè)容易解決的問題,尤其是HTML,CSS和JavaScript本質(zhì)上是相互依賴的。更糟糕的是,當(dāng)涉及到前端代碼時(shí),一些傳統(tǒng)計(jì)算機(jī)科學(xué)原則,比如關(guān)注分離,這一長期運(yùn)用在服務(wù)端開發(fā)中,很少會(huì)討論到。
在本文中,我將會(huì)講講我所學(xué)到的如何去解耦我的HTML,CSS和JavaScript代碼。從個(gè)人以及他人經(jīng)驗(yàn)所得,這種的最好辦法并不是那么顯而易見,而通常是不直觀的,而且有時(shí)還會(huì)與許多所謂的最佳實(shí)踐相違背。
目標(biāo)
HTML,CSS和JavaScript之間總會(huì)存在耦合關(guān)聯(lián)。不管怎樣,這些技術(shù)與生俱來就是要和其它進(jìn)行交互。舉個(gè)例子,一種飛閃轉(zhuǎn)換效果可能 會(huì)在樣式表中用帶有類選擇器定義,但它經(jīng)常由HTML初始化,并通過用戶交互,如編寫JavaScript,來觸發(fā)。由于前端代碼的有些耦合是不可避免 的,你的目標(biāo)就不應(yīng)該是簡單地消除之間的耦合,而應(yīng)該是減少代碼間不必要的依賴耦合關(guān)系。一個(gè)后端開發(fā)者應(yīng)該能夠?qū)TML模板中的標(biāo)記進(jìn)行更改,而無需 擔(dān)心意外破壞CSS規(guī)則或者一些JavaScript功能。由于當(dāng)今的web團(tuán)隊(duì)日漸增大且專業(yè)化,這個(gè)目標(biāo)比以往更甚。
反模式
前端代碼的緊耦合現(xiàn)象并不總是很明顯。事實(shí)上復(fù)雜的是,一方面看起來似乎松耦合,但從另一方面則是緊耦合。以下是我曾經(jīng)多次做過或者看過,以及吸取我的過錯(cuò)中,總結(jié)的所有的反模式。對(duì)每一個(gè)模式,我會(huì)嘗試去解釋為何耦合這么糟糕,并且指出如何去避免它。
過度復(fù)雜的選擇器
CSS Zen Garden向世界展示了你可以完全改變整個(gè)網(wǎng)站的外觀而無需更改任意一個(gè)的HTML標(biāo)記。這是語義網(wǎng)運(yùn)動(dòng)的典型代表,主要原則之一就是去避免使用表象 類。乍一看,CSS Zen Garden可能看起來像是一個(gè)很好的解耦合例子,畢竟,把樣式從標(biāo)記語言中分離出來是它的重點(diǎn)所在。但是,若按照這樣做,問題就來了,你會(huì)經(jīng)常需要在你 的樣式表里有這樣的選擇器,如下:
#sidebar section:first-child h3 + p { }
CSS Zen Garden中,雖然HTML幾乎與CSS完全分離,但CSS會(huì)強(qiáng)耦合到HTML中去,此時(shí)就需要你對(duì)標(biāo)記語言的結(jié)構(gòu)有深層次的理解。這可能看起來似乎并 不是很糟糕,尤其是某人維護(hù)著CSS,同時(shí)需要維護(hù)HTML,但一旦你增加了許多人手進(jìn)去,這種情況就變得無法控制了。如果某個(gè)開發(fā)者在某種情況下在第一 個(gè)<section>前增加了<div>,上面的規(guī)則就無法生效,然而他也不清楚其中緣由。
只要你網(wǎng)站的標(biāo)記很少改動(dòng),CSS Zen Garden就是一個(gè)非常不錯(cuò)的主意。但是這和當(dāng)今的Web應(yīng)用不盡然都是這種情況。與冗長而又復(fù)雜的CSS選擇器相比,最好的辦法是在可視化組件本身的 根元素增加一個(gè)或多個(gè)類選擇器。比如,如果側(cè)邊欄有子菜單,只需要為每個(gè)子菜單元素增加submenu類選擇器,而不要用這樣的形式:
ul.sidebar > li > ul { /* submenu styles */ }
這種方式的結(jié)果是在HTML中需要更多的類選擇器,但從長遠(yuǎn)來看,這又降低了耦合度,以及讓代碼更可重用和可維護(hù),并且還能讓你的標(biāo)記自文檔化。如 果HTML里沒有類選擇器,那些對(duì)CSS不熟悉的開發(fā)者就不清楚HTML的改動(dòng)如何影響了其它代碼。另一方面,在HTML中使用類選擇器能很清晰地看到那 些樣式或者功能被使用到了。
多個(gè)類選擇器的職責(zé)
一個(gè)類選擇器往往是用來同時(shí)作為樣式和JavaScript的鉤子。雖然這看起來似乎很節(jié)約(因?yàn)橹辽贉p少了一個(gè)類標(biāo)記),但事實(shí)上,這是把元素的表現(xiàn)和功能耦合起來了。
<button class="add-item">Add to Cart</button>
以上例子描述了一個(gè)帶有add-item類樣式的”添加到購物車”按鈕。
如果開發(fā)者想為此元素添加一個(gè)單擊事件監(jiān)聽器,用已經(jīng)存在的類選擇器作為鉤子非常的容易。我的意思是,既然已經(jīng)存在了一個(gè),為何要添加另一個(gè)呢? 但是想想看,有很多像這樣的按鈕,遍布了整個(gè)網(wǎng)站,都調(diào)用了相同的JavaScript功能。再想想看,如果市場團(tuán)隊(duì)想要其中一個(gè)和其它看起來完全不同但 功能相同的按鈕呢。也許這樣就需要更多顯著的色彩了。
問題就來了,因?yàn)楸O(jiān)聽單擊事件的JavaScript代碼希望add-item類選擇器被使用到,但是你新的按鈕又無法使用這個(gè)樣式(或者它必須清 除所有聲明的,然后再重置新的樣式)。還有,如果你測(cè)試的代碼同時(shí)也希望使用add-item類選擇器,那么你不得不要去更新那么代碼用到的地方。更糟糕 的是,如果這個(gè)”添加到購物車”功能不僅僅是當(dāng)前應(yīng)用用到的話,也就是說,把這份代碼抽象出來作為一個(gè)獨(dú)立的模塊,那么即使一個(gè)簡單的樣式修改,可能會(huì)在 完全不同的應(yīng)用中引發(fā)問題。
使用javaScript鉤子最好的(事實(shí)上也是比較鼓勵(lì)的)做法是,如果你需要這么做,使用一種方式來避免樣式和行為類選擇器之間的耦合。
我的個(gè)人建議是讓JavaScript鉤子使用前綴,比如:js-*。這樣的話,當(dāng)開發(fā)者在HTML源代碼中看到這樣的類選擇器,他就完全明白個(gè)中原因了。所以,上述的”添加到購物車”的例子可以重寫成這樣:
<button class="js-add-to-cart add-item">Add to Cart</button>
現(xiàn)在,如果需要一個(gè)看起來不同的按鈕,你可以很簡單地修改下樣式類選擇器,而不管行為的類選擇器。
<button class="js-add-to-cart add-item-special">Add to Cart</button>
JavaScript更多的樣式操作
JavaScript能用類選擇器去DOM中查找元素,同樣,它也能通過增加或移除類選擇器來改變?cè)氐臉邮?。但如果這些類選擇器和當(dāng)初加載頁面時(shí) 不同的話也會(huì)有問題。當(dāng)JavaScript代碼使用太多的組成樣式操作時(shí),那些CSS開發(fā)者就會(huì)輕易去改變樣式表,卻不知道破壞了關(guān)鍵功能。也并不是 說,JavaScript不應(yīng)該在用戶交互之后改變可視化組件的外觀,而是如果這么做,就應(yīng)該使用一種一致的接口,應(yīng)該使用和默認(rèn)樣式不一致的類選擇器。
和js-*前綴的類選擇器類似,我推薦使用is-*前綴的類選擇器來定義那些要改變可視化組件的狀態(tài),這樣的CSS規(guī)則可以像這樣:
.pop-up.is-visible { }
注意到狀態(tài)類選擇器(is-visible)是連接在組件類選擇器(pop-up)后,這很重要。因?yàn)闋顟B(tài)規(guī)則是描述一個(gè)的狀態(tài),不應(yīng)該單獨(dú)列出。如此不同就可以用來區(qū)分更多和默認(rèn)組件樣式不同的狀態(tài)樣式。
另外,可以讓我們可以編寫測(cè)試場景來保證像is-*這樣的前綴約定是否遵從。一種測(cè)試這些規(guī)則的方式是使用CSSLint和HTML Inspector。
更多關(guān)于特定狀態(tài)類選擇可以查閱Jonathan Snnok編寫的非常優(yōu)秀的SMACSS書籍。
JavaScript”選擇器”
jQuery和新的API,像document.querySelectorAll,讓用戶非常簡單地通過一種他們已經(jīng)非常熟悉的語言–CSS選擇 器來查找DOM中的元素。雖然如此強(qiáng)大,但同樣有CSS選擇器已經(jīng)存在的相同的問題。JavaScript選擇器不應(yīng)過度依賴于DOM結(jié)構(gòu)。這樣的選擇器 非常慢,并且需要更深入認(rèn)識(shí)HTML知識(shí)。
就第一個(gè)例子來講,負(fù)責(zé)HTML模板的開發(fā)者應(yīng)該能在標(biāo)記上做基本的改動(dòng),而不需擔(dān)心破壞基本的功能。如果有個(gè)功能會(huì)被破壞,那么它就應(yīng)該在標(biāo)記上顯而易見。
我已經(jīng)提及到應(yīng)該用js-*前綴的類選擇器來表示JavaScript鉤子。另外針對(duì)消除樣式和功能類選擇器之間的二義性,需要在標(biāo)記中表達(dá)出來。 當(dāng)某人編寫HTML看到j(luò)s-*前綴的類選擇器時(shí),他就會(huì)明白這是別有用途的。但如果JavaScript代碼使用特定的標(biāo)記結(jié)構(gòu)查找元素時(shí),正在觸發(fā)的 功能在標(biāo)記上就不那么明顯了。
為了避免使用冗長而又復(fù)雜的選擇器遍歷DOM,堅(jiān)持使用單一的類或者ID選擇器。 考慮以下代碼:
var saveBtn = document.querySelector("#modal div:last-child > button:last-child")
這么長的選擇器是可以節(jié)省你在HTML中添加一個(gè)類選擇器,但同樣讓你的代碼對(duì)于標(biāo)記更改非常容易受到影響。如果設(shè)計(jì)者突然決定要把保持按鈕放在左邊,而讓取消按鈕放在右邊,這樣的選擇器就不再匹配了。
一個(gè)更好的方式(使用上述的前綴方法)是僅僅使用類選擇器。
var saveBtn = document.querySelector(".js-save-btn")
現(xiàn)在標(biāo)記可以更改它想改的,并且只要類選擇還是在正確的元素上,一切都會(huì)很正常。
類選擇器就是你的契約
使用合適的類選擇器以及可預(yù)測(cè)的類名約定可以減少幾乎每一種HTML,CSS和JavaScript之間的耦合。起初由于為了展現(xiàn)HTML需要知道 很多類選擇器的名稱,這種在標(biāo)記中使用很多類選擇器看起來像是強(qiáng)耦合的跡象。但是我發(fā)覺,使用類選擇器和傳統(tǒng)編程設(shè)計(jì)中的事件或者觀察者模式非常相似。在 事件驅(qū)動(dòng)編程中,為了不直接在對(duì)象A上調(diào)用對(duì)象B,而是對(duì)象A簡單地在提供的環(huán)境中發(fā)布一個(gè)特定的事件,然后對(duì)象B能夠訂閱那個(gè)事件。這樣,對(duì)象B就不需 要知道任何關(guān)于對(duì)象A的接口,而僅僅需要知道監(jiān)聽什么事件。按理說,事件系統(tǒng)需要某種形式上的耦合,因?yàn)閷?duì)象B需要知道訂閱的事件名稱,但和對(duì)象A需要知 道對(duì)象B的公共方法相比,這已經(jīng)更松散的耦合了。
HTML類選擇器都非常相似。與CSS文件中定義復(fù)雜的選擇器(就像HTML的內(nèi)部接口一樣)不同的是,它可以通過單一類選擇器簡單定義一個(gè)可視化 組件的外觀。CSS文件不需要關(guān)心HTML對(duì)類選擇器的使用與否。同樣,JavaScript不用那些需要更深入理解HTML結(jié)構(gòu)的復(fù)雜DOM遍歷功能, 而是僅僅監(jiān)聽與類名一致的元素的用戶交互。類選擇器應(yīng)該像是膠水一樣,把HTML,CSS和JavaScript連接在一起。從個(gè)人經(jīng)驗(yàn)得知,它們也是最 容易以及最好的方式把三者技術(shù)連接起來,而不是混合過度。
未來
網(wǎng)頁超文本技術(shù)工作小組(WHATWG)正在致力于web組件的規(guī)范,能讓開發(fā)者把HTML,CSS和JavaScript綁定一起作為一個(gè)單獨(dú)的 組件或者模塊,并與其它的頁面元素進(jìn)行交互封裝。如果這個(gè)規(guī)范已經(jīng)在大多數(shù)的瀏覽器中實(shí)現(xiàn)的話,那么我在本文中提供的很多建議就變得不那么重要了(因?yàn)榇?碼和誰交互變得很清晰);但是無論如何,理解這些更廣泛的原則以及為何需要它們?nèi)匀缓苤匾?。即使這些實(shí)踐在Web組件時(shí)代會(huì)變得不那么重要,但其中的理論 仍然適用。在大型團(tuán)隊(duì)和大型應(yīng)用中的實(shí)踐仍然要適用于小模塊的編寫中,反之則不需要。
結(jié)論
可維護(hù)的HTML,CSS和JavaScript的標(biāo)志是每個(gè)開發(fā)者可以容易并且很自信地編寫代碼庫的每個(gè)部分,而不需擔(dān)心這些修改會(huì)無意中影響到 其它不相關(guān)部分。阻止這樣意想不到的后果的最佳方式之一是,通過一組能夠表達(dá)其義的,任何開發(fā)者碰到時(shí)能想出它的用途的,可預(yù)測(cè)的人性化的類選擇器名,把 這三者技術(shù)結(jié)合在一起。
為避免上述的反模式,請(qǐng)把下述的原則謹(jǐn)記于心:
- 1. 在CSS和JavaScript里,優(yōu)先考慮顯式組件和行為類選擇器,而不是復(fù)雜的CSS選擇器。
- 2. 命名組件要基于它們是什么,而不是它們?cè)谀睦?/li>
- 3. 不用為樣式和行為使用相同的類選擇器去
- 4. 把狀態(tài)樣式和默認(rèn)樣式區(qū)分開來
在HTML中這樣運(yùn)用類選擇器經(jīng)常會(huì)需要很多需要表現(xiàn)的類選擇器,但獲取的是可預(yù)見性和可維護(hù)性,這點(diǎn)值得肯定。畢竟,為HTML增加類選擇器是相當(dāng)容易的,不需要開發(fā)者有多少技能。摘自Nicolas Gallagher的原話:
當(dāng)你要尋找一種方式來減少花費(fèi)在編寫和修改CSS的時(shí)間上來制作HTML和CSS時(shí),這就涉及到你必須接受如果你想更改樣式,你是不想花費(fèi)更多時(shí)間 去更改HTML元素上的類選擇器。這對(duì)前端和后端開發(fā)者都有一定的實(shí)用性,任何人都可以重新安排預(yù)構(gòu)建的樂高積木。這樣沒有人會(huì)去展示CSS的魔
原文鏈接: