`
hax
  • 浏览: 952777 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

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

    博客分类:
  • AJAX
阅读更多
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
分享到:
评论
13 楼 storm119 2008-02-25  
实在是看不完。
12 楼 hax 2008-02-25  
fins 写道
问题再次延伸!

hax 写道
同等条件下,性能应该是改样式表要快。因为这个变更分发是由浏览器做的,肯定是native调用,比JS调用要快。


当 styleSheet 里的 class的内容变化时
浏览器内部是怎么刷新响应的页面元素的?
会不会是 遍历所有的 页面元素, 如果元素的class含有被改变的class那么就刷新?

如果是这样 那页面元素非常多非常多的时候 会不会出现比 js调用更慢的情况呢?

毕竟js调用是有目的的, 例如一个列表1000行 那么js遍历的就是1000个td
而native调用是遍历所有的页面元素.

也就是说 当页面复杂到一定程度 会不会出现native调用比js调用更慢的情况呢?


Great question.

我也想过这个问题,比如我修改了body上的一个class,到底会发生什么事情。这实际上得去看浏览器的CSS引擎是怎么实现的。我没看过,所以回答不能算权威。

现在我只能做一些大体定性的判断。

首先native调用通常比js调用要快上几个数量级。
其次,我原文也加上了“同等条件下”,如果浏览器需要遍历所有元素来判断class的话,那js程序也一样,如果js程序自己存着一个数组,而不做任何模式匹配,这个比较就不是同等条件了。不过考虑到native调用实在比js快,比如js做一个循环就要花上不少时间,所以基本上可以肯定还是native的快。
其三,CSS本来就是一个很复杂的机制(虽然用的人很爽),所以引擎必然做了很多优化,不会每次变化都去遍历所有元素,重新计算所有的规则和最终的样式。

比如,如果我来实现引擎的话,可能会让每个cssrule对象内部保持着所有适用rule的selector的元素的引用数组,然后如果rule的cssdeclaration发生变化,就通知每个元素重新计算cascade后的样式。按照我之前写的selector api,也许代码类似这样(用JS表示):

rule.selector.getAll().forEach(function(e){e.recalcStyle()});

而recalcStyle肯定也可以做优化,而不必重新计算所有该元素适用的rule。比如只需要计算原来的cssdeclaration和现在的cssdeclaration中有不同的css properties。计算完成之后,再把变更部分通知到render引擎,render引擎进行页面的重绘。注意,这里recalcStyle的计算过程,以及页面重绘过程都可以是异步的。

假如selector部分发生变化,就复杂一点,因为要重新匹配。然而,考虑到这件事情JS也能做,比如所有的JS类库的querySelector功能干的都是这个事情,他们的算法在不断优化性能不断提高,那native的实现不用说干起来肯定快上不知多少!比如说,有些优化,JS做不到或者很难办的,对于native来说却轻而易举,例如,你可以给所有的class做索引,从一个hashmap里根据className反查,这样一个带有class的selector匹配起来根本不用遍历所有元素。总之,这个selector查询问题,就跟sql查询一样,有很多优化可以做。

相比较而言,最困难的一点,大概是我所说的问题,修改了body上的一个class,也就是DOM元素或属性的结构变化,也就是我前面说的selector match/unmatch的监听问题。因为理论上,一个元素发生某种结构变化时,它本身,它的所有子节点,它的sibling节点(主要是后续的兄弟节点)及其子节点,所有这些节点的匹配都可能发生变化。所有这些节点的所有selector匹配都要重新计算。当然也可能存在某种优化方式,例如如果只是一个class变更,那么只需要计算所有selector中包含这个class条件的那些selector,但是窃以为这种优化如果超出class之外就实在非常的困难,也许反而不美。

然而这样困难的问题,在现在所有浏览器中都得到了解决,而且我们日常所最常用的方式,恰恰是修改class这样的操作。所以其他问题跟这个比起来,也许不值一提了。

顺便说一句,至少IE中,class确实是受到特殊待遇的。比如通过CSS绑定的htc,当selector匹配生效或失效的时候,样式虽然会立即生效或失效(实际是异步的),但是htc并不会被自动attach和detach,这一点有些奇怪,也许是出于性能考虑,也许根本就是一个bug。为了强制进行重新计算,你可以这样:
var all = document.getElementsByTagName('*');
for (var n=all.length, i=0; i<n; i++) {all[i].className+=''}

也就是,className的变更,会触发元素对于其本身适用的样式进行刷新。
11 楼 hax 2008-02-25  
i_love_sc 写道
col 只有ie才支持。


FF早就支持了。但是正如我之前所说的,它对于col/colgroup上的css prop的支持有限,比如它不支持col/colgroup上的text-align。它也不支持html规范中所列出的align属性,因为本质上,FF是一个CSS浏览器而不是一个HTML浏览器,所以如果一个属性的格式化要求无法被转化为CSS,则就不可能得到支持。也许因此造成你有印象FF不支持col。

但是这是有原因的,我一直想写一篇blog来分析一下这个问题,不过一直没写。各位可直接看https://bugzilla.mozilla.org/show_bug.cgi?id=915,你可以注意到这个bug的编号是915,也就是很老很老很老很老的一个问题,实际上日期是1998-09-26,也就是说,至少10年以前那个时候(那时候我还在用386混DOS,根本上不了网,要用浏览器还得钻学校机房2块钱1小时啊),Mozilla已经支持col,否则就没有这个bug了。简单的原因可以看CSS21作者hixie的解说。各位不喜看英文的朋友,可暂时看看这个:http://forum.moztw.org/viewtopic.php?t=3905
10 楼 fins 2008-02-25  
问题再次延伸!

hax 写道
同等条件下,性能应该是改样式表要快。因为这个变更分发是由浏览器做的,肯定是native调用,比JS调用要快。


当 styleSheet 里的 class的内容变化时
浏览器内部是怎么刷新响应的页面元素的?
会不会是 遍历所有的 页面元素, 如果元素的class含有被改变的class那么就刷新?

如果是这样 那页面元素非常多非常多的时候 会不会出现比 js调用更慢的情况呢?

毕竟js调用是有目的的, 例如一个列表1000行 那么js遍历的就是1000个td
而native调用是遍历所有的页面元素.

也就是说 当页面复杂到一定程度 会不会出现native调用比js调用更慢的情况呢?




9 楼 fins 2008-02-24  
我印象中也是
但是 按hax的说法似乎都支持
我记得以前我在ff1.5下试过 不支持 于是之后我就不再用了

不知道现在怎么样
8 楼 i_love_sc 2008-02-24  
col 只有ie才支持。
7 楼 fins 2008-02-24  
唉 我的英语 

四级30多分的水平 ...

而且现在又退步了 tears...
6 楼 hax 2008-02-24  
有关ext的问题,我也回答不了啊。我对ext也不了解,也没有读过它的源码。我只能给出我所知的一些考量因素。但是ext为什么这样做,那还得问ext的作者啦。

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

总之,这些只有作者自己才能回答。我不过是瞎猜啦。你真的好奇,就应该直接去问jack。
5 楼 fins 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面前 我这样的半吊子只有资格去崇拜和学习 还没有资格去做理性的分析和批判
所以还请楼主能再帮 我 分析一下这两个问题 谢谢了
4 楼 hax 2008-02-24  
3 楼 hax 2008-02-24  
同等条件下,性能应该是改样式表要快。因为这个变更分发是由浏览器做的,肯定是native调用,比JS调用要快。

还有你说的div宽度不会变,应该只有在IE中该td被设了width的情况时发生。如果该td的width保持为auto(默认就是这样),应该不会出现这样的情况吧。
2 楼 fins 2008-02-24  
非常感谢你的回答, 值得我好好学习一下

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

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

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

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

谢谢了
1 楼 Lunatic Sun 2008-02-24  
绝对是一篇研究型的大作,收藏了。

另外我觉得,在能够用不需要使用javascript解决的问题尽量不要用javascript解决,毕竟javascript在无障碍性和安全性方面还不是非常完美。

相关推荐

Global site tag (gtag.js) - Google Analytics