首頁 >web前端 >js教程 >解耦 HTML、CSS 和 JS

解耦 HTML、CSS 和 JS

高洛峰
高洛峰原創
2017-02-06 15:43:411089瀏覽

目前在網路上,任何一個稍微複雜的網站或應用程式都會包含許多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,但一旦你增加了許多人手進去,這種情況就變得無法控制了。如果某個開發者在某種情況下在第一個

前增加了
,上面的規則就無法生效,然而他也不清楚其中緣由。


只要你網站的標記很少改動,CSS Zen Garden就是一個非常好的主意。但這和現今的網頁應用程式不盡然都是這種情況。與冗長而又複雜的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的魔力了。

更多解耦 HTML、CSS 和 JS相關文章請關注PHP中文網!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn