批量修改style採取哪種方式好

本文原發表於我在JavaEye的技術部落格

fins同志向我提了個問題。因這個問題其實可以展開討論,所以提出來大家共同探討。

fins 寫道

在同類元素 例如 td 很多的情況下, "一次性改變元素的class對應的styleSheet"
和 "在循環裏改變每一個元素style" 哪個更好

ext的代碼不知道你看過沒
在ext 1 裏 改變表格列寬的方式 就是用的 改變那一列的 td對應的class裏的 width
而ext 2裏變成了 用循環 依次改變每一個 td的style.width
兩種方法哪個好呢? 我一直喜歡第一種
不過實在不明白爲什麼 ext 2裏換了方式


對於這一問題,我的意見是,就這個個例來說,兩種方式都不好。因爲table中一個column的width不應該在每個td上都做設置,而應該 設置在table的col或colgroup元素上。注意,col元素上有效的css prop是很少的,這關係到另一個問題,這裏不做展開,不過,width恰好屬於那少數幾個有效的css prop之一。

當然,我對ext不熟,不知道在ext上是否能做這件事情。比方說如果ext產生的datagrid並不用col/colgroup,那你就幹不 了這個事情。但是有個變通方式。對於table-layout:fixed的表格來說(注意:對於大表格來說,應儘可能使用fixed),其td寬度只取 決於第一行上的td的寬度。所以,可以把width設在第一行對應的td上。


本身,這個問題大體就是這樣了。但是我想fins提出的這個問題,其實有更廣泛的意義。那就是對於批量動態修改style,到底採取哪種方式好。

批量修改style的具體形式千變萬化,但是萬變不離其宗,歸根到底就是兩種:

A. 動態修改多個元素。
B. 動態修改stylesheet上的單個cssdeclaration。

差別就是,前者選擇待修改元素的集合(符合條件的snapshot)的過程是由腳本完成的(比如用一個getElementsByTagName 選出一批元素,現在有了jQuery之類的,就更方便了,直接拿css selector來選擇元素集合),並需要遍歷所有元素一一進行修改;而後者選擇待修改對象集合(這個集合是live的)的過程是由css selector自動完成的,然後style的變更也是自動分發到所有對象的。

這兩種方式本身,在具體實踐時還有一些可以注意的地方。

比如對於A方式來說,直接修改inline style實際上是把style混入了script中,味道通常不好,可考慮在stylesheet中創建若干個class來表示幾組不同的style, 然後script中只要修改className就好。這適合那些樣式切換的需求,因爲樣式切換往往隱含語義,所以確實也應該用class來顯式的標記這些 語義。但是對於修改width這樣的例子,就並不適合了。修改width,是一個純粹UI上的操作,它並不帶有語義價值(我指對應用的功能來說UI並不 significant——當然某些應用除外,例如UI編輯器)。

又如對於B方式來說,我們應該記得,selector很好很強大,因此不要老是給許多元素碼上無數的class,完全可以用其他的 selector方式,例如父子關係的,不必每個li都li class=mylist,完全可以ul class=mylist,然後樣式表寫ul.mylist>li{...}。

如果這些地方都注意了,你會發現,真正需要批量修改的地方其實很少。大多數場合,只需要修改某個父級元素的className就可以了。

我們下面談論的是除了這些情況之外,真正需要批量修改style的地方。

一種比較理論化的方案,是判斷真正的需求,是要批量修改符合特定模式(實際是根據語義來匹配)的某些元素的樣式(採用方式B),還是批量修改只是 臨時碰巧符合某個結構的(實際上沒有持久有效的語義,甚至可能是隨機的,比如由用戶臨時指定的)一批元素的屬性(採用方式A)。但是許多時候,這個界限並 不清楚,或者難以明確。就好象,在寫樣式表的時候,我是寫
div#div1 {font-size:small}
div#div2 {font-size:small}
div#div3 {font-size:small}
還是抽象出一個div.class1,然後
div.class1 {font-size:small}

這個事情脫離環境,是無法判斷的!因爲有時候你知道這裏要達到這個效果,但是你並沒有花精力去判斷,font-size爲small這個事情是純 粹偶然,還是這三個div真的具有某種一體的聯繫?況且,很多時候,追究這一點是不經濟的(或者根本不可能,比如身爲程序員的你無法找到真正懂需求的人, 或者做UI設計的人根本沒有這個概念,無法回答你的問題)。

撇開對“需求的真正含義”的判斷,我們假設,就個案來說已經存在確定的匹配模式(不管它有語義還是碰巧),也就是說不考慮你進行額外抽象的負擔 (比如原來頁面上並沒有某個class,而你決定額外加入一個class,並給一些元素加上class——這不僅是一種額外負擔,在匹配模式其實是碰巧的 情況下,長久來看其實可能是起了反作用),然後來關注純技術層面的問題,那麼:

通常來說,B方式看上去更好一些。fins同志也是這樣認爲的。因爲它只修改了一處,而且這個修改是單點可控的,不可能受到外部因素的破壞。而方 式A則沒有那麼安全,因爲樣式與元素的匹配,只是一次性的,不存在始終有效的綁定。如果在完成批量改變style的操作之後,我們可能某個時候要再做一次 操作批量去除所附加的style。其潛臺詞是在這個期間,這些元素仍舊符合原先所設的條件。

如果元素由於某種外部因素的影響,不再符合原始的條件(例如一個元素被移動到了DOM樹的其他地方),或者有新的符合條件的元素出現(例如插入了 一個新元素),既有的約定就被破壞了,結果自然是可能出現bug,而且幾乎無法跟蹤。即使你可以監聽某些變化,成本也非常之高。使用css selector的jQuery之類的,尤其如此,因爲css selector的模式匹配是如此強大,以至於完全無法track一個元素是否發生了一個變化就不再匹配既定模式了。

所以實際上,採用A方式,意味着DOM結構(包括所有影響你選擇元素集合的因素)至少在一定範圍內最好是靜態的。幸好,這個需求在許多應用中還是 符合的,在受控的框架中通常也是可以保證的。還有一個例子是組件庫,組件內部的DOM結構一般是確定不變的,然後可變的部分已經被封裝爲它的外部接口了。 對於組件來說,使用A方式還有一個副作用是,它能隔絕外部css對它內部元素的樣式的影響,因爲inline style優先級最高。當然組件可以正常工作的前提是,你的代碼(或者你用的第三方代碼)不會破壞它的封裝,比如不破壞它內部的DOM結構。這一點其實存 在一點隱患,比如假設你搞來一個自動圓角的庫,然後加諸於某個組件之上,因爲這個圓角庫會自動插入一堆b啦i啦的元素,結果你的脆弱的組件就完蛋了。

而B方式,因爲它依賴的是聲明性的selector,模式匹配是自動的,所以沒有A方式的問題。DOM結構你隨便變吧。但是B方式並不是沒有自己 的問題。首先,stylesheet是全局的,對於一個rule修改所產生的影響也是全局的。在現有的CSS中缺乏將一部分style局域化的能力 (CSS 3有草案http://www.w3.org/TR/css-style-attr,html5 中有對style元素的局域化定義,但是得到瀏覽器普遍支持還需時日)。而組件需要局域化style。現在我們需要用id或者class來限定 stylesheet的scope。就ext的例子來說,它需要td上有一個class,而且每個table上的class都不能一樣。維護局域化標識, 同時在全局stylesheet中維護局域化的style,這樣的操作,其實是較爲複雜的一件事情,目前似乎還沒有一個庫提供這方面的支持。

其次,A方式的stylesheet是靜態的,而B方式的stylesheet是動態的。既然stylesheet是一種聲明性的東西,那麼通常 stylesheet本身就傾向於保持靜態。這允許一些針對stylesheet的前處理和後處理。比如dean edwards的IE7,它會重新解析stylesheets。而我之前也寫過預編譯stylesheet來產生兼容ie的css的思路。 然而,如果stylesheet是動態可變的,對這些方案就存在很大的挑戰。因爲監聽stylesheet的變化如果不是不可能,那至少是非常非常困難 的。而且無論是跟蹤你修改的rules對應哪些實際的rules,還是整個重新編譯樣式表,成本可能都很巨大。要解決這個問題,需要投入很大的努力。 (dean edwards的IE7還有更嚴重的問題,因爲它實際上內部是使用A方式的,所以是對AJAX不友好的,不過這與這裏的討論關係不大。)

以上,就是我對fins所提出的問題所做的一些考慮。結論其實是,首先看看是否你真的需要批量修改style。也許有90%的情況,你應該改爲修 改單一元素上的一個className。又有90%的情況,你可能只需修改單一元素上的inline style。雖然jquery、mootools、prototype等框架先後提供了css selector的功能,未來瀏覽器甚至會提供原生的querySelector功能,但是沒有必要濫用這個能力。

剩下1%的情形,那就要根據你的情況進行權衡。理想上,方式B更好一些,但是也存在一些現實問題。更多時候,我們見到的是大量方式A的寫法,因爲帶有selector query功能的工具使得這樣做更容易,而且在絕大多數時候,方式A也是可行的。

下一篇,我們將探討由本文所引發的對於我們到底希望一種怎樣的編程方式的思考。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章