在過去那個寫JavaScript漫不經心的年代,HTML中混雜著JS程式碼很正常,反正那時JS也做不了什麼大事;之後JS興起、前端工程蓬勃發展之後,JS開發者被告誡不能這麼做,然而事情無絕對,真正重要的,也不是要求HTML與JS分手這件事。

非侵入式JS

歷史總是會重演,義大利麵(spaghetti code)的年代也不是只有前端,在後端技術發展之初,HTML與後端程式碼夾雜(例如,JSP中HTML與Java程式碼混雜)是常見之事,因此,也有了各式框架來解決義大利麵式的頁面。

在JS翻身之後,類似的情境搬到了前端,JS程式碼經常與HTML標籤放在一起,這使得文件結構與行為混雜,也使得網頁設計師與JS開發者難以分工合作,為此,有了擁抱非侵入式(Unobtrusive)JS的年代。

若以採用非侵入式JS作法的觀點來看,不應該讓JS程式碼侵入HTML,任何JS運算式或陳述句,只要被放到頁面的body標籤中就不對,就算是作為HTML元素的屬性(像是onclick),或者在頁面本體最底部以外的其他地方放置指令稿區塊,也都是不對的;現代JS開發者應該很熟悉怎麼實作非侵入式JS了,藉由id、name或CSS選擇器選取元素,並且相對應地進行事件註冊,而在事件發生時,執行商務邏輯、改變頁面等動作。

並不是單純地分離HTML與JS,就是實作了非侵入式JS,最重要的是分離文件結構與行為,這表示JS開發者不能對文件結構作過多的假設,只關心應該改變的DOM元素片段,因此,大範圍的DOM樹走訪是不建議的,這實際上是JS程式碼侵入(或說是干涉)結構,後果就是稍微改變HTML頁面,JS程式碼可能就無法運作了。

非侵入式JS的代價就是,需要的程式碼更多,就算只是小範圍的走訪或改變DOM樹,也需要一定的工作量,因此,在那個年代不少程式碼致力於減輕DOM操作的負擔,而jQuery是代表之一。

前端的MVC

非侵入式JS的概念流行過一陣子,jQuery等程式庫也風光過一時,前端需求也複雜到產生新問題了,從HTML中分離的JS程式碼中,開始有不小的部份會用於處理事件、撮合頁面、商務邏輯與應用程式狀態,雖然可以將這部份獨立儲存為JS檔案,以便於清楚知道這些JS檔案是用於處理頁面相關邏輯;然而,事件發生時,該怎麼改變應用程式狀態,而狀態更新時,畫面中哪些DOM又必須同步變化等相關的負擔依舊。

另一方面,為了實現非侵入式JavaScript,利用的是HTML標籤上的id、name或相關屬性來取得目標DOM元素,HTML頁面上看不到哪個標籤會有哪些行為,或者標籤與哪些物件的狀態有關,這從網頁設計師的角度來看是件好事,然而,對JS開發者來說,卻未必如此,單看HTML的話,難以得知哪些標籤片段有哪些特定行為,或是哪些元素是用於顯示應用程式的哪個狀態。

談到狀態更新,不免令人聯想到開發桌面應用程式時的MVC模式,事件發生時委由對應的控制器(Controller)處理,控制器操作對應商務元件,或者是進一步更新模型(Model)狀態,若模型的狀態有異,可以通知對其註冊的呈現(View)元件,呈現元件回頭提取模型狀態並更新外觀;由於前端本質上更像個桌面應用程式,實現桌面MVC模式相對於後端來說更為自然。

AngularJS是前端MVC框架的代表之一,並且也支援模版技術,有著表達式、ng-if、ng-repeat等支援頁面呈現邏輯,透過ng-model可以清楚地知道HTML元件與模型狀態的綁定關係,透過ng-controller與對應的ng-click等,可以知道哪個控制器會負責處理哪個事件等,例如:

<div ng-controller="fooController">
<button ng-click="doSome()">Chili</button>
<p>The result is {{foo}}!</p>
</div>

嗯?ng-click的寫法沒問題嗎?是不是違反了非侵入式JS?如果要這麼想的話,ng-controller也算是了,但實際上並非如此,非侵入式JS強調的是關切點分離,而不是形式上的JS與HTML分離,如果頁面呈現邏輯、事件與控制器的綁定等是必須同時關切的點,它們就不應該被分離!

可重用的頁面組件

使用模版技術來實現呈現邏輯等,只是為了讓呈現邏輯的程式碼看起來像HTML的一部份;然而,若頁面中的動態元素,本來就是JS開發者關心的對象,為什麼不能直接用JS寫呈現邏輯呢?

另一方面,事件發生時,雖然知道是控制器處理了,然而實際上到底是怎麼處理的呢?如果對於某些元素,經常必須在HTML與JS間來回查看,是否表示它們是同一個關切點,而不應該被分離呢?

事實上,React框架就是想解決這類問題,開發者可以將HTML與JS寫在一起(也可以直接用標籤上的onClick來註冊事件):

class HelloWorld extends Component {
render() {
const value = 'hello';
return <div>{value + ' world'}</div>;
}
}

對於非侵入式JS,若只是形式上在意JS與HTML分離,那麼,看到React這類框架,第一時間應該會被驚嚇到「啥?HTML跟JavaScript寫在一起?」實際上,這並不是HTML,也不是JavaScript,而是JSX,是React中基於JavaScript而擴充的語言,JSX語法絕大多數與JavaScript並無二致,最顯眼的差異是在支援HTML標籤的部份,標籤資訊會用來轉譯為React.createElement等JS程式碼,以建立對應的DOM元素。

如果設計Web應用程式的頁面元素在設計時,事件的處理、狀態綁定與呈現邏輯等,有著密切的關係而必須放在一起查看,透過React的話,可以設計為獨立、可重用的元件,元件的獨立性並不代表它不能與其他元件互動,只是必須透過組件樹來傳遞狀態(而不是元件之間自由自在地交換狀態)。

透過組件樹來傳遞狀態既是種風格也是種限制,實際上,React在多個地方也要求或建議遵循不可變(Immutable)特性,以避免寫出相依性高的組件,另一方面,跟MVC框架不同,React本身只處理呈現元件的部份,模型或控制器必須藉由其他方案來補足或自行實作。

關切點分離的真意

關切點分離談的是,如果彼此關切的點不同,那麼,它們就不該被放在一起,應該分開來避免互相干擾,如果是網頁設計人員,當然不會想看到一堆JS程式碼混在HTML中,自然要分離出來。

然而,JS開發者應該要關心HTML嗎?無關的HTML當然不用注意,然而,如果經常必須操作某個HTML片段,當然就要關切它,如果需經常來回查看而造成負擔,那就考慮將它們放在一起。

開發者要瞭解關切的點在哪、是否被分離了、放在一起會有哪些好處(與限制),以便取得平衡,並挑選適當的方案。

作者簡介


Advertisement

更多 iThome相關內容