论坛首页 Web前端技术论坛

批量修改style采取哪种方式好(答fins)

浏览 10501 次
该帖已经被评为精华帖
作者 正文
   发表时间:2008-02-23  
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也是可行的。

其实,我们期望的理想的编程方式也许是这样:http://hax.iteye.com/blog/164614
   发表时间:2008-02-24  
绝对是一篇研究型的大作,收藏了。

另外我觉得,在能够用不需要使用javascript解决的问题尽量不要用javascript解决,毕竟javascript在无障碍性和安全性方面还不是非常完美。
0 请登录后投票
   发表时间:2008-02-24  
非常感谢你的回答, 值得我好好学习一下

再多问一句 不从设计等角度考虑 只是从性能上讲 哪个方法更快呢???

眼前还有一个相对具体点的问题 关于 <col><colgroup>的问题
ext确实没有用 而且我研究过一些其他的表格组件 也都没有用
col是不是有什么潜在的兼容性问题呢?

另外关于
"对于table-layout:fixed的表格来说(注意:对于大表格来说,应尽可能使用fixed),其td宽度只取决于第一行上的td的宽度。所以,可以把width设在第一行对应的td上"

我以前也是这么做的 但是有个很头痛的问题.
改变第一个单元格的宽度后 下面的宽度会变化,
但是如果单元格内部的 div宽度是 100% 那么他们不会跟着变
这个怎么解决呢?

谢谢了
0 请登录后投票
   发表时间:2008-02-24  
同等条件下,性能应该是改样式表要快。因为这个变更分发是由浏览器做的,肯定是native调用,比JS调用要快。

还有你说的div宽度不会变,应该只有在IE中该td被设了width的情况时发生。如果该td的width保持为auto(默认就是这样),应该不会出现这样的情况吧。
0 请登录后投票
   发表时间:2008-02-24  
本文续篇:http://hax.iteye.com/blog/164614
0 请登录后投票
   发表时间:2008-02-24  
再次感谢.  引出另外一个问题.

您说:"
对于table-layout:fixed的表格来说(注意:对于大表格来说,应尽可能使用fixed),其td宽度只取决于第一行上的td的宽度。"

在EXT中 作者始终坚持只对 ie和 safari下的table使用table-layout:fixed.

.ext-ie .x-grid3 table,.ext-safari .x-grid3 table {
    table-layout:fixed;
}

您能帮忙分析分析这事为什么吗? table-layout:fixed是不是在 这两个浏览器下和在其他浏览器下有什么不同呢?
另外 为什么 ext的作者坚持不用 <col> 呢
还有 既然修改 styleSheet更快 那为什么ext的作者一定要便利每个单元格呢?

请原谅我总是拿EXT做例子 毕竟在EXT面前 我这样的半吊子只有资格去崇拜和学习 还没有资格去做理性的分析和批判
所以还请楼主能再帮 我 分析一下这两个问题 谢谢了
0 请登录后投票
   发表时间:2008-02-24  
有关ext的问题,我也回答不了啊。我对ext也不了解,也没有读过它的源码。我只能给出我所知的一些考量因素。但是ext为什么这样做,那还得问ext的作者啦。

否则我们只能猜测。比如如果ext只对ie和safari用fixed,那可能是他认为FF和Opera的性能足够好,不需要用fixed。
不用col,也许是他认为用td就可以了,没有必要多引入一个元素,或者担心col存在某种未知的问题。
还有,虽然修改stylesheet快,但是这个性能快一点可能在ext作者看来关系不大。况且如果他是从ext1到ext2做了改变,可能有一些其他的考虑。

总之,这些只有作者自己才能回答。我不过是瞎猜啦。你真的好奇,就应该直接去问jack。
0 请登录后投票
   发表时间:2008-02-24  
唉 我的英语 

四级30多分的水平 ...

而且现在又退步了 tears...
0 请登录后投票
   发表时间:2008-02-24  
col 只有ie才支持。
0 请登录后投票
   发表时间:2008-02-24  
我印象中也是
但是 按hax的说法似乎都支持
我记得以前我在ff1.5下试过 不支持 于是之后我就不再用了

不知道现在怎么样
0 请登录后投票
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics