澳门威利斯人解耦你的HTML

JavaScript”选择器”

jQuery和新的API,像document.querySelectorAll,让用户非常简单地通过一种他们已经非常熟悉的语言–CSS选择器来查找DOM中的元素。虽然如此强大,但同样有CSS选择器已经存在的相同的问题。JavaScript选择器不应过度依赖于DOM结构。这样的选择器非常慢,并且需要更深入认识HTML知识。

就第一个例子来讲,负责HTML模板的开发者应该能在标记上做基本的改动,而不需担心破坏基本的功能。如果有个功能会被破坏,那么它就应该在标记上显而易见。

我已经提及到应该用js-*前缀的类选择器来表示JavaScript钩子。另外针对消除样式和功能类选择器之间的二义性,需要在标记中表达出来。当某人编写HTML看到js-*前缀的类选择器时,他就会明白这是别有用途的。但如果JavaScript代码使用特定的标记结构查找元素时,正在触发的功能在标记上就不那么明显了。

为了避免使用冗长而又复杂的选择器遍历DOM,坚持使用单一的类或者ID选择器。 考虑以下代码:

JavaScript

var saveBtn = document.querySelector("#modal div:last-child > button:last-child")

1
var saveBtn = document.querySelector("#modal div:last-child > button:last-child")

这么长的选择器是可以节省你在HTML中添加一个类选择器,但同样让你的代码对于标记更改非常容易受到影响。如果设计者突然决定要把保持按钮放在左边,而让取消按钮放在右边,这样的选择器就不再匹配了。

一个更好的方式(使用上述的前缀方法)是仅仅使用类选择器。

JavaScript

var saveBtn = document.querySelector(".js-save-btn")

1
var saveBtn = document.querySelector(".js-save-btn")

现在标记可以更改它想改的,并且只要类选择还是在正确的元素上,一切都会很正常。

结论

编写可维护的HTML、CSS和JavaScript的要点是个体开发人员可以很容易地和自信地编辑部分的代码库而这些变化不经意间影响其他不相关的部分。

防止意想不到的后果的一个最好的方法来是通过一组可预见的类来连接这三个技术,任何开发人员遇到这些类都能清楚它们的意图和选择的他们目的。

为了避免上面的反模式,牢记下面的原则:

  • 在CSS和JavaScript用明确组件和行为类代替复杂的CSS选择器。
  • 风格组件依赖于它们是什么,而不是他们的位置。
  • 样式和行为不要使用相同的类。
  • 从默认的样式分离出状态样式。

这种方式往往在HTML中呈现大量类,但在可预见性和可维护性上的收益是值得的。毕竟,在HTML中添加类是相当容易,并大多数开发人员的技术水平可以做到。引用 Nicolas Gallagher的一段话:

当你选择修改HTML和CSS,试图减少你花在写作和编辑CSS上的大量时间,这包括接受,你必须花更多的时间更改HTML元素的类,如果你想改变元素的风格。这被证明是相当实用的,包括前端和后端开发人员——任何人都可以重新排列预先构建的“乐高积木”;事实证明,没有人可以执行css炼金术。

如果你觉得我翻译的不错你可以关注我的微博

CSS架构

2013/04/24 · CSS · CSS

英文原文:CSS Architecture,编译:CSDN-张红月

Philip Walton 在AppFolio担任前端工程师,他在Santa Barbara on Rails的聚会上提出了CSS架构和一些最佳实践,并且在工作中一直沿用。

擅长CSS的Web开发人员不仅可以从视觉上复制实物原型,还可以用代码进行完美的呈现。无需使用表格、尽可能少的使用图片。如果你是个名副其实的高手,你可以快速把最新和最卓越的技术应用到你的项目中,比如媒体查询、过渡、滤镜、转换等。虽然这些都是一个真正的CSS高手所具备的,但CSS很少被人单独拿出来讨论,或者用它去评估某个人的技能。

有趣的是,我们很少这样去评价其他语言。Rails开发人员并不会因为其代码比较规范,就认为他是一名优秀的开发人员。这仅仅是个基准。当然,他的代码得必须规范。另外,还需集合其他方面考虑,比如代码是否可读?是否容易修改或扩展……

这都是些很自然的问题,CSS和它们并没有什么不同之处。今天的Web应用程序要比以往更加庞大。一个缺乏深思熟虑的CSS架构往往会削弱发展,是时候像评估其他语言那样,来评估一下CSS架构了,这些都不应该放在“事后”考虑或者单单属于设计师们的事情。

澳门威利斯人 1

1.良好的CSS架构目标

在CSS社区,很难提出某个最佳实践已经成为大家的普遍共识。纯粹地从Hacker News的评论上判断和开发者们对CSS Lint发布后的反应来看,大多数人对基本的CSS东西是持反对意见的。所以,并不是为自己的最佳实践奠定一套基本的论据,而应该确定真正的目标。

好的CSS架构目标并不同于开发一个好的应用程序,它必须是可预测、可重用、可维护和可伸缩的。

可预测

可预测意味着可以像预期的那样规范自己的行为。当你添加或者修改某个规则时,它并不会影响到没有指定的部分。对于一个小网站来说,一些微乎其微的改变并不算什么。而对于拥有成千上万个页面的大网站来说,可预测却是必须的。

可重用

CSS规则应具备抽象和解耦性,这样你就可以在现有的基础上快速构建新的组件,无需重新修改编码模式。

可维护

当把新组件放置到网站上,并且执行添加、修改或者重新设计操作时,无需重构现有CSS,并且新添加的X并不会打破原有页面的Y组件。

可扩展

当网站发展到一定规模后,都需要进行维护和扩展。可扩展的CSS意味着网站的CSS架构可以由个人或者团队轻易地管理,无需花费太多的学习成本。

 

2.常见的错误实践

在实现良好的CSS架构目标之前,我们来看一些常见的错误做法,这对我们达成目标是有好处的。

下面的这些例子虽然都可以很好的执行,但却会给你带来很多烦恼,尽管我们的意图和愿望都是美好的,但是这些开发模式会让你头疼。

几乎在每个网站上,都会有一个特定的虚拟元素看起来与其他页面是完全一样的,然而只有一个页面除外。当面对这样一种情况时,几乎每个新手CSS开发人员(甚至是经验丰富的)都会以同样的方式来修改。你应该为该页面找出些与众不同之处(或者自己创建),然后再写一个新规则去操作。

基于父组件来修改组件

CSS

.widget { background: yellow; border: 1px solid black; color: black; width: 50%; } #sidebar .widget { width: 200px; } body.homepage .widget { background: white; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.widget {  
  background: yellow;  
  border: 1px solid black;  
  color: black;  
  width: 50%;  
}  
 
#sidebar .widget {  
  width: 200px;  
}  
 
body.homepage .widget {  
  background: white;  
}

初看,这绝对是段无害的代码,但让我们来看看它是否达到了我们所设置的目标。

首先,例子中的widget是不可预见的。当这些小部件出现在页面两侧或者主页面时,开发人员期望它们以某种特定的方式显示出来,且又不失特色。另外,它也是不可重用或不可扩展的。

另外,它也比较难维护。一旦这个widget需要重新设计,那么你不得不修改其他几个CSS样式。想象一下,如果这段代码是使用其他语言编写的,它基本就是一个类定义,然后在代码的另一部分使用该类定义并做出扩展。这直接违反了软件开发的开放/闭合(open/close)原则。

软件实体(类,模块,函数等)应对扩展开放,对修改闭合。

过于复杂的选择器

偶尔,会有些文章介绍CSS选择器对整个网站的展示起着非常重要的作用,并且宣称无需使用任何类选择器或者ID选择器。

但伴随着越深入的开发,我越会远离这种复杂的选择器。一个选择器越复杂,与HTML就越耦合。依靠HTML标签和组合器可以保持HTML代码干干净净,但却让CSS更加毛重和凌乱。

XHTML

#main-nav ul li ul li div { } #content article h1:first-child { } #sidebar > div > h3 + p { }

1
2
3
#main-nav ul li ul li div { }  
#content article h1:first-child { }  
#sidebar > div > h3 + p { }

对上面代码进行简单的理解。第一个可能是对下拉菜单进行样式化;第二个想说明文章的主标题应该与其他页面的H1元素不同;最后一个表示在第一段的侧边栏区域添加一些额外的空间。

如果这个HTML是永远不变的,那就无可说之处,但这根本毫不现实。过于复杂的选择器会让人印象深刻,它可以让HTML摆脱掉表面上的复杂,但对于实现良好的CSS架构目标却毫无用处。

上面提到的例子都是不具备可预测性、可重用、可扩展和可维护这四大特性的。例如第一个选择器(下来菜单)例子,如果一个外观非常相似的下拉列表需要用在不同的页面上,并且#main-nav并不属于内部元素,那么你是否需要重新设计?假设开发者想要修改第三个例子里div里面部分标记,那么整个规则都会被打破。

过于通用的类名

当创建可重用的设计组件时,在组件的类选择器中覆盖附件的子元素是很常见的现象。例如:

XHTML

<div class="widget"> <h3 class="title">...</h3> <div class="contents"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. In condimentum justo et est dapibus sit amet euismod ligula ornare. Vivamus elementum accumsan dignissim. <button class="action">Click Me!</button> </div> </div>

1
2
3
4
5
6
7
8
9
<div class="widget">  
  <h3 class="title">...</h3>  
  <div class="contents">  
    Lorem ipsum dolor sit amet, consectetur adipiscing elit.  
    In condimentum justo et est dapibus sit amet euismod ligula ornare.  
    Vivamus elementum accumsan dignissim.  
    <button class="action">Click Me!</button>  
  </div>  
</div>

CSS

.widget {} .widget .title {} .widget .contents {} .widget .action {}

1
2
3
4
.widget {}  
.widget .title {}  
.widget .contents {}  
.widget .action {}

像.title、.contents、.action这些子元素类选择器可以被安全地进行样式命名,无需担心这些样式会蔓延到拥有相同类名的其他元素中。这是千真万确的。但它并没有阻止相同样式类名称会蔓延到这个组件上。

在一些大型项目上,像.title这样的名称很有可能会被用在另外一个页面或者本身。如果这样的情况发生,那么整个标题部分明显会和预期的不一样。

过于通用的类选择器名称会导致许多不可预测的CSS样式发生。

一个规则做太多事

有时,你要在网站的左上角区域做一个20pixels的可视化组件。

CSS

.widget { position: absolute; top: 20px; left: 20px; background-color: red; font-size: 1.5em; text-transform: uppercase; }

1
2
3
4
5
6
7
8
.widget {  
  position: absolute;  
  top: 20px;  
  left: 20px;  
  background-color: red;  
  font-size: 1.5em;  
  text-transform: uppercase;  
}

下面,你需要在网站的其他区域使用该组件,那么上面的这个代码明显是错误的,不可重用的。

问题的关键是你让.widget这个选择器做的事情太多,不仅对该组件的位置进行了规定,还对它的外观和感觉方面进行了样式。外观和感觉可以通用,而位置是不可以的。有时候,把它们整合起来使用反而会大打折扣。

虽然这些看起来并无害处,对一些缺乏经验的CSS程序员来说,复制和粘贴已经成为一种习惯。如果一个新团队需要一个特定组件,比如.infobox,他们会尝试使用这个类选择器。但如果该信息框没有按照期望的那样,在每个需要的地方正确显示出来。这时,你认为他们会怎么做?以我的经验来看,他们会打破可重用这一规则,相反,他们会简单地把这些代码复制粘贴到每个需要的地方。做些不必要的重复工作。

3.原因

上面列举的这些常规错误实践都有一个相似性,CSS样式承担过多。

对这样的说法你会感到奇怪,毕竟,它是一个样式表,难道不应该承担大多数(如果不是全部)的样式吗?那不正是我们想要的吗?

的确。但是通常来讲,事情并没有那么简单。内容与表现(presentation)相分离是件好事,但CSS从HTML中独立出来并不意味着内容也需要从表现中分离。换句话说,如果CSS请求深入分析HTML架构,那么从HTML中分拆所有的显示代码并不一定会实现所有的目标。

此外,HTML很少会只包含内容,也表示整体框架。通常,架构是会包含container元素,允许CSS隔离一些固定元素。即使没有表象类(presentational classes),也能混合HTML清晰地把内容展示出来。

我相信,鉴于当前的HTML和CSS状态,把HTML和CSS明智地结合起来,当做表现层是非常需要的。而通过模板和局部模板(partials)也可以把内容层进行分离。

 

4.解决方案。

如果把HTML和CSS结合起来,作为一个Web应用程序的表现层,那么它们需要采取一些方式更好地促进优秀CSS架构的形成。

最好的方法是CSS中尽可能少的包含HTML架构。CSS则是应该定义元素的视觉效果,无论该视觉元素在哪里。如果有一些特定的组件需要在不同的场合显示不同的效果,那么应该赋予不同的名称。例如,CSS通过.button类选择器定义了一个按钮组件。如果HTML想要一个特定的元素看起来像按钮,那么就可以使用.button。如果这里有特殊要求,这里的按钮与其他的有所不同(有可能更大和宽些),那么CSS需要定义一个新的类,HTML可以使用新的类来赋予该元素新的视觉效果。

CSS赋予元素的外在特征,HTML在页面上进行调用。更少的CSS能被更多的HTML架构调用是最好的。

准确地在HTML中声明元素不仅可以清晰表达设计意图,其他开发者也可以清晰地查看标记并且知道元素将呈现的样子。如果没有这种实践,它很难区分一个元素的外观设置是有意或无意的,这样很容易导致团队混乱。

在标记中填入大量的类(classes)是种常见缺陷,这样做往往需要花费额外的精力。一个CSS样式可以给一个特定组件引用上千次。那么,为了在标记里面进行显示声明,就真的值得去重复编写这样的类吗?

虽然这种担心是有效的,但它可能会产生误导。言下之意就是无论你在CSS中使用一个父选择器还是亲手编写上千个Class,这里都会有些额外的选择。在Rails或者其他框架里查看同级别抽象很大程度上可以在HTML中保持很好的视觉外观,并且无需在类中一遍又一遍地编写相同的类。

 

5.最佳实践。

针对上面的种种错误,我进行了很好地总结,并且根据自身经验提出了一些建议,希望它们能帮助您更好地实现良好的CSS架构目标。

专注

确保选择器对一些元素不进行无关样式的最好方法是不给它们机会。例如像#main-nav ul li ul li div这样的选择器可能很容易地应用于不想要的元素上。另一方面,像.subnav这样的选择器就不会给它们任何机会。把类选择器直接应用于你想要的元素上是最好的方式,并且可以保持元素的可预测性。

CSS

/* Grenade */ #main-nav ul li ul { } /* Sniper Rifle */ .subnav { }

1
2
3
4
5
/* Grenade */
#main-nav ul li ul { }  
 
/* Sniper Rifle */
.subnav { }

模块化

一个组织结构良好的组件层可以帮助解决HTML架构与CSS那种松散的耦合性。此外,CSS组件本身应该是模块化的。组件应该知道如何进行样式和更好地工作,但是关于布局、定位以及它们与周围元素的关系不应该做太多的假设。

一般而言,CSS要定义的应该是组件的外观,而不是布局或者位置。同样在使用background、color和font这些属性时也要遵循原则使用。

布局和位置应当由一个单独的布局类或者单独的容器元素构成(请记住,有效地把内容与展示进行分离其实就是把内容与容器进行分离)。

给类进行命名空间

我们已经检查出为什么父选择器不能在封闭和防止交叉样式污染上面发挥100%的功效。而一个更好的解决方案就是在类上应用命名空间。如果一个元素是可视化组件的一员,那么该元素的每个子元素都应该使用基于命名空间的组件。

CSS

/* High risk of style cross-contamination */ .widget { } .widget .title { } /* Low risk of style cross-contamination */ .widget { } .widget-title { }

1
2
3
4
5
6
7
/* High risk of style cross-contamination */
.widget { }  
.widget .title { }  
 
/* Low risk of style cross-contamination */
.widget { }  
.widget-title { }

给类进行命名空间可以保持组件独立性和模块化。它可以把现有类冲突降至最小并且减少子元素的一些特殊要求。

创建修饰符类来扩展组件

当一个现有组件需要在一个特定的语境中有所不同时,可以创建一个修饰符类(modifier class)来扩展它。

CSS

/* Bad */ .widget { } #sidebar .widget { } /* Good */ .widget { } .widget-sidebar { }

1
2
3
4
5
6
7
/* Bad */
.widget { }  
#sidebar .widget { }  
 
/* Good */
.widget { }  
.widget-sidebar { }

正如我们看到的,基于父元素的缺点对组件进行修改,需要重申:一个修饰符类可以在任何地方使用。基于位置的覆盖只能被用在一个特定的位置,修饰符类也可以根据需要被多次使用。显然,修饰符类是符合HTML开发者需求的。

把CSS组织成逻辑结构

Jonathan Snook在其非常优秀的《SMACSS》书中提到,CSS可以被分成四个不同的类:基础(base)、布局(layout)、模块(modules)和状态(state)。基础包括了复位原则和元素缺省值;布局是对站点范围内的元素进行定位以及像网格系统那样作为一种通用布局助手;模块即是可重用的视觉元素;状态即指样式,可以通过JavaScript进行开启或关闭。

组件是一个独立的视觉元素。模板在另一方面则是构建块。模板很少独自站在自己的角度去描述视觉和感觉,相反,它们是单一的、可重用的模式,可以放在一起形成组件。

为了提供更详细的例子,一个组件可能就是一个模式对话框。该模式可能在头部包含渐变的网站签名、或者在周围会有阴影、在右上角会有关闭按钮、位置固定在垂直与水平线中间。这四个模式可能被网站重复多次使用,所以在每次使用的时候,你都很少会想到重新编码与设计。这些所有的模板即形成了一个模块组件。

因样式和风格使用类

有过大型网站建设的人可能有个这样的经验,一个拥有类的HTML元素可能完全不知道其用途。你想删除它,但是又犹豫不决,因为它的作用你可能还未意识到。一旦这样的事情一遍又一遍发生的时候,随着时间的推移,项目中将会有越来越多这样的类,只因为团队成员都不敢删除。

在Web前端开发中,类承担了太多的责任,因此才会产生这样的问题。样式化HTML元素、扮演着JavaScript hook角色、功能检测、自动化测试等。当这么多应用程序在使用类时,让你从HTML中删除它们将会变的非常艰难。

然而,使用一些成熟的约定(惯例)即可完全避免这种问题。当在HTML中看到一个类时,你应该立即明白它的目的。我建议在前面使用前缀,例如用于JavaScript的在前面加.js,表示Modernizr classes可以在前面加.supports,没有加前缀的即用于表示样式。

这样来发现未使用的类和从HTML中移除它们将会变得非常简单。你甚至可以自动完成这一个过程,在JavaScript中通过交叉引用HTML中的document.styleSheets对象。如果在document.styleSheets中没有发现该类,即可安全移除。

一般来说,最佳做法是把内容与演示相分离,另外把功能分离开来也同样重要。使用样式类像JavaScript hook在某种程度上可以加深CSS与JavaScript之间的耦合,但在不打破功能性的前提下很难或者根本不可能更改外观。

有逻辑的命名类

大多数写CSS的人喜欢使用连字符来分隔命名词,但连字符并不足以区分不同类型之间的类。

Nicolas Gallagher最近针对遇到的问题写了一个解决方案,并且取得了巨大的成功(略有改动),为了说明命名约定,可以考虑以下格式:

CSS

/* A component */ .button-group { } /* A component modifier (modifying .button) */ .button-primary { } /* A component sub-object (lives within .button) */ .button-icon { } /* Is this a component class or a layout class? */ .header { }

1
2
3
4
5
6
7
8
9
10
11
/* A component */
.button-group { }  
 
/* A component modifier (modifying .button) */
.button-primary { }  
 
/* A component sub-object (lives within .button) */
.button-icon { }  
 
/* Is this a component class or a layout class? */
.header { }

从上述类中可以发现其很难正确区分类型规则。这不但会困惑,而且连自动测试CSS和HTML也变的很难。一个结构化的命名约定应该是初看就能够知道其类名与其他类之间的关系,并且知道它出现在HTML中的位置——使命名更加简单和容易测试。

CSS

/* Templates Rules (using Sass placeholders) */ %template-name %template-name--modifier-name %template-name__sub-object %template-name__sub-object--modifier-name /* Component Rules */ .component-name .component-name--modifier-name .component-name__sub-object .component-name__sub-object--modifier-name /* Layout Rules */ .l-layout-method .grid /* State Rules */ .is-state-type /* Non-styled JavaScript Hooks */ .js-action-name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* Templates Rules (using Sass placeholders) */
%template-name  
 
%template-name--modifier-name  
 
%template-name__sub-object  
 
%template-name__sub-object--modifier-name  
 
 
/* Component Rules */
.component-name  
.component-name--modifier-name  
.component-name__sub-object  
.component-name__sub-object--modifier-name  
 
/* Layout Rules */
.l-layout-method  
.grid  
 
/* State Rules */
.is-state-type  
 
/* Non-styled JavaScript Hooks */
.js-action-name

重做第一个例子:

CSS

/* A component */ .button-group { } /* A component modifier (modifying .button) */ .button--primary { } /* A component sub-object (lives within .button) */ .button__icon { } /* A layout class */ .l-header { }

1
2
3
4
5
6
7
8
9
10
11
/* A component */
.button-group { }  
 
/* A component modifier (modifying .button) */
.button--primary { }  
 
/* A component sub-object (lives within .button) */
.button__icon { }  
 
/* A layout class */
.l-header { }

 

6.工具

维护一个高效且组织良好的CSS架构是非常困难的,尤其是在大型团队中。下面向大家推荐几款很好的工具来帮你管理网站CSS架构。

CSS Preprocessor

CSS预处理器采用PHP5编写,有预处理器的常见功能,可以帮你快速编写CSS。另外有些号称“功能”的预处理器实际上并不会对CSS架构产生良好作用。下面我提供一个列表,在使用时一定要避免:

  • 切勿纯粹为了组织代码来嵌套规则。只有当输出你真正想要的CSS时才可以。
  • 在无需传递参数的时候切勿使用mixin,不带参数的mixin更适合用作模板,易扩展。
  • 切勿在选择器上使用@extend,它不是个单一的类。从设计角度来看是毫无意义的,它会膨胀编译过的CSS。
  • 在运用组件修饰符规则时,切勿使用@extend UI组件,这样会失去基础链。

@extend和%placeholder是预处理器里面非常好的两个功能。它们可以帮你轻松管理CSS抽象并且无需添加bloat和大量的基类到CSS和HTML里,否则将会很难管理。

当你初次使用@extend时,常会与修饰符类一起使用,例如:

CSS

.button { /* button styles */ } /* Bad */ .button--primary { @extend .button; /* modification styles */ }

1
2
3
4
5
6
7
8
9
.button {  
  /* button styles */
}  
 
/* Bad */
.button--primary {  
  @extend .button;  
  /* modification styles */
}

这样做会让你在HTML中失去继承链。很难使用JavaScript选择所有的按钮实例。

作为一般规则,很少去扩展UI组件或者在知道类型后做些什么。这是区分模板和组件的一种方式,模板无需参与到应用程序的逻辑,并且可以使用预处理器进行安全扩展。

下面是一个引用上面的模式例子:

CSS

.modal { @extend %dialog; @extend %drop-shadow; @extend %statically-centered; /* other modal styles */ } .modal__close { @extend %dialog__close; /* other close button styles */ } .modal__header { @extend %background-gradient; /* other modal header styles */ }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.modal {  
  @extend %dialog;  
  @extend %drop-shadow;  
  @extend %statically-centered;  
  /* other modal styles */
}  
 
.modal__close {  
  @extend %dialog__close;  
  /* other close button styles */
}  
 
.modal__header {  
  @extend %background-gradient;  
  /* other modal header styles */
}

CSS Lint

CSS Lint是由Nicole Sullivan和Nicholas Zakas编写的一款代码质量检测工具,帮助CSS开发人员写出更好的代码。他们的网站上是这样介绍CSS Lint的:

CSS Lint是一个用来帮你找出CSS代码中问题的工具,它可做基本的语法检查以及使用一套预设的规则来检查代码中的问题,规则是可以扩展的。

使用CSS Lint建议:

  • 1.不要在选择器中出现ID。
  • 2.在多部分规则中,不要使用非语义(non-semantic)类型选择器,例如DIV、SPAN等。
  • 3.在一个选择器中使用的连接符(combinator)不要超过2个。
  • 4.任何类名都不要以“js-”开始。
  • 5.如果在非“I-”前缀规则里经常使用布局和定位应给予警告
  • 6.如果一个类定义后被重新定义成子类,也应给予警告。

 

总结

CSS不仅仅是视觉设计,也不要因为你编写CSS就随便抛出编程的最佳实践。像OOP、DRY、打开/闭合、与内容分离等这些规则应该应用到CSS里面。无论你如何组织代码,都要确保方法真正帮助到你,并且使你的开发更加容易和可维护的。

赞 3 收藏 评论

澳门威利斯人 2

反模式

前端代码的紧耦合现象并不总是很明显。事实上复杂的是,一方面看起来似乎松耦合,但从另一方面则是紧耦合。以下是我曾经多次做过或者看过,以及吸取我的过错中,总结的所有的反模式。对每一个模式,我会尝试去解释为何耦合这么糟糕,并且指出如何去避免它。

过度复杂的选择器

CSS Zen Garden向世界展示了你可以完全改变整个网站的外观而无需更改任意一个的HTML标记。这是语义网运动的典型代表,主要原则之一就是去避免使用表象类。乍一看,CSS Zen Garden可能看起来像是一个很好的解耦合例子,毕竟,把样式从标记语言中分离出来是它的重点所在。但是,若按照这样做,问题就来了,你会经常需要在你的样式表里有这样的选择器,如下:

JavaScript

#sidebar section:first-child h3 + p { }

1
#sidebar section:first-child h3 + p { }

CSS Zen Garden中,虽然HTML几乎与CSS完全分离,但CSS会强耦合到HTML中去,此时就需要你对标记语言的结构有深层次的理解。这可能看起来似乎并不是很糟糕,尤其是某人维护着CSS,同时需要维护HTML,但一旦你增加了许多人手进去,这种情况就变得无法控制了。如果某个开发者在某种情况下在第一个<section>前增加了<div>,上面的规则就无法生效,然而他也不清楚其中缘由。

只要你网站的标记很少改动,CSS Zen Garden就是一个非常不错的主意。但是这和当今的Web应用不尽然都是这种情况。与冗长而又复杂的CSS选择器相比,最好的办法是在可视化组件本身的根元素增加一个或多个类选择器。比如,如果侧边栏有子菜单,只需要为每个子菜单元素增加submenu类选择器,而不要用这样的形式:

CSS

ul.sidebar > li > ul { /* submenu styles */ }

1
2
3
ul.sidebar > li > ul {
  /* submenu styles */
}

这种方式的结果是在HTML中需要更多的类选择器,但从长远来看,这又降低了耦合度,以及让代码更可重用和可维护,并且还能让你的标记自文档化。如果HTML里没有类选择器,那些对CSS不熟悉的开发者就不清楚HTML的改动如何影响了其它代码。另一方面,在HTML中使用类选择器能很清晰地看到那些样式或者功能被使用到了。

在本文中,我将谈谈我是如何学会分离我的HTML、CSS和JavaScript。据我的经验和我所知道的其他人的经验,完成目标的最好的方式是不明显,反直觉的,有时不利于大量所谓的“最佳实践”的。

未来

网页超文本技术工作小组(WHATWG)正在致力于web组件的规范,能让开发者把HTML,CSS和JavaScript绑定一起作为一个单独的组件或者模块,并与其它的页面元素进行交互封装。如果这个规范已经在大多数的浏览器中实现的话,那么我在本文中提供的很多建议就变得不那么重要了(因为代码和谁交互变得很清晰);但是无论如何,理解这些更广泛的原则以及为何需要它们仍然很重要。即使这些实践在Web组件时代会变得不那么重要,但其中的理论仍然适用。在大型团队和大型应用中的实践仍然要适用于小模块的编写中,反之则不需要。

防止意外的影响不是一个容易解决的问题,特别是由于HTML、CSS和JavaScript本身是相互关联的。

多个类选择器的职责

一个类选择器往往是用来同时作为样式和JavaScript的钩子。虽然这看起来似乎很节约(因为至少减少了一个类标记),但事实上,这是把元素的表现和功能耦合起来了。

XHTML

<button class="add-item">Add to Cart</button>

1
<button class="add-item">Add to Cart</button>

以上例子描述了一个带有add-item类样式的”添加到购物车”按钮。

如果开发者想为此元素添加一个单击事件监听器,用已经存在的类选择器作为钩子非常的容易。我的意思是,既然已经存在了一个,为何要添加另一个呢? 但是想想看,有很多像这样的按钮,遍布了整个网站,都调用了相同的JavaScript功能。再想想看,如果市场团队想要其中一个和其它看起来完全不同但功能相同的按钮呢。也许这样就需要更多显著的色彩了。

问题就来了,因为监听单击事件的JavaScript代码希望add-item类选择器被使用到,但是你新的按钮又无法使用这个样式(或者它必须清除所有声明的,然后再重置新的样式)。还有,如果你测试的代码同时也希望使用add-item类选择器,那么你不得不要去更新那么代码用到的地方。更糟糕的是,如果这个”添加到购物车”功能不仅仅是当前应用用到的话,也就是说,把这份代码抽象出来作为一个独立的模块,那么即使一个简单的样式修改,可能会在完全不同的应用中引发问题。

使用javaScript钩子最好的(事实上也是比较鼓励的)做法是,如果你需要这么做,使用一种方式来避免样式和行为类选择器之间的耦合。

我的个人建议是让JavaScript钩子使用前缀,比如:js-*。这样的话,当开发者在HTML源代码中看到这样的类选择器,他就完全明白个中原因了。所以,上述的”添加到购物车”的例子可以重写成这样:

XHTML

<button class="js-add-to-cart add-item">Add to Cart</button>

1
<button class="js-add-to-cart add-item">Add to Cart</button>

现在,如果需要一个看起来不同的按钮,你可以很简单地修改下样式类选择器,而不管行为的类选择器。

XHTML

<button class="js-add-to-cart add-item-special">Add to Cart</button>

1
<button class="js-add-to-cart add-item-special">Add to Cart</button>

一个类有超过一个职责

有时一个类即用于样式的目的,还作为一个JavaScript钩子(javascript选择符)。虽然这可能看起来像一个节约(因为它需要一个更少的类标记)实际上一个元素表示和行为的耦合。

<button class="add-item">Add to Cart</button>

上面的例子展示了一个“添加到购物车”按钮样式与添加物品类。

如果开发者想要给这个元素添加一个单击事件监听器,它可以很容易将已经存在的类当作挂钩。我的意思是,如果一个(类)已经存在,为什么要添加另一个呢?

但是想象一下,在整个网站里,有很多这些按钮,他们全都看起来一样,并且全部调用相同的JavaScript功能。然后想象一下营销团队想要其中一个按钮看起来完全与众不同。也许它需要很多更大的更吸引眼球的颜色。

这里有一个问题,因为JavaScript代码里单击事件监听器期待 add-item 类继续被使用,但是你的新按钮现在不能使用这个类(或它必须复原一切已经声明的(样式)并且重置新样式)。更大的问题是,如果你有一些测试代码,也期待着add-item类出现,所以你也会有另一个地方的代码(测试代码)需要更新。

更糟糕的是如果你的 "Add to Cart" 功能使用的不仅仅是这个应用程序。如果代码已经被抽象到一个独立的模块然后一个简单的风格改变现在可能在完全不同的应用程序间引入bug。

使用类作为JavaScript钩子是完全可以接受的(实际上鼓励),但是如果你要这样做,要按这种方法,避免样式类和行为类之间的耦合。

我个人的建议是为所有JavaScript钩子使用前缀。我使用 js - *。这样,当一个开发人员在HTML源码中看到这样一个类(带前缀),她就清楚知道到哪里去找使用这个类的目的。

上面的例子中 "Add to Cart" 将被重写为:

<button class="js-add-to-cart add-item">Add to Cart</button>

现在,如果一个特定的 "Add to Cart"按钮需要看起来不同,您可以简单地改变样式类和分离行为类本身。

<button class="js-add-to-cart add-item-special">Add to Cart</button>

解耦 HTML、CSS 和 JavaScript

2013/06/22 · CSS, JavaScript · 2 评论 · CSS, Javascript

本文由 伯乐在线 - 蝈蝈 翻译。未经许可,禁止转载!
英文出处:Philip Walton。欢迎加入翻译组。

当前在互联网上,任何一个稍微复杂的网站或者应用程序都会包含许多HTML、CSS和JavaScript。随着互联网运用的发展以及我们对它的依赖性日益增加,设定一个关于组织和维护你的前端代码的计划是绝对需要的。

当今的一些大型互联网公司,由于越来越多的人会接触到日益增加的前端代码,它们会试图去坚持代码的模块化。这样更改程序的部分代码,并不会无意中过多地影响后续不相关部分的执行过程。

防止意想不到的后果不是一个容易解决的问题,尤其是HTML,CSS和JavaScript本质上是相互依赖的。更糟糕的是,当涉及到前端代码时,一些传统计算机科学原则,比如关注分离,这一长期运用在服务端开发中,很少会讨论到。

在本文中,我将会讲讲我所学到的如何去解耦我的HTML,CSS和JavaScript代码。从个人以及他人经验所得,这种的最好办法并不是那么显而易见,而通常是不直观的,而且有时还会与许多所谓的最佳实践相违背。

过于复杂的选择器

CSS禅意花园向世界证明了你可以完全改变整个网站的外观并丝毫没有改变标记。这是语义Web的运动的海报,和它的一个主要原则是避免使用表象的类。

乍一看,CSS禅意花园可能看起来像一个解耦的很好的例子。毕竟,它的全部意义是将样式从标记分离。然而,问题是,要做到这一点,你通常需要在你的样式表看起来像下面这样的选择器:

#sidebar section:first-child h3 + p { }

CSS禅意花园中,HTML几乎完全脱离了CSS,但CSS对html是超级耦合和需要结构的标记的知识非常了解。

这看上去还不错如果一个人即维护CSS还维护HTML,但是一旦你增加一些更多的人到你的组合,它很快就会失控。如果一个开发者来临并添加一个< div >在<section>之前,上述规则将不会工作,开发人员可能不知道自己做了什么。

CSS禅意花园是一个伟大的想法,只要你的网站的标记很少改变。但今天的Web应用程序通常不会有这样的情形。

代替冗长,复杂的CSS选择器,最好(只要有可能)风格是你所有的可视化组件有一个或多个类在根元素组件自身上。例如,如果你在你的边栏里有一个子菜单,仅仅添加submenu类到每一个子菜单元素,而不要像下面这样:

ul.sidebar > li > ul {
  /* submenu styles */
}

这种方法最终需要更多的类在HTML中,但它会降低(html,css)之间的耦合,从长远来看这使CSS代码更可重用和可维护。它也可以让你的标记标记标记自我(self-documenting)。如果在HTML中没有类,开发人员不清楚CSS无法预知她的标记变化将影响其他的代码。另一方面,如果在HTML有类那很明显元素被应用的样式和功能。

结论

可维护的HTML,CSS和JavaScript的标志是每个开发者可以容易并且很自信地编写代码库的每个部分,而不需担心这些修改会无意中影响到其它不相关部分。阻止这样意想不到的后果的最佳方式之一是,通过一组能够表达其义的,任何开发者碰到时能想出它的用途的,可预测的人性化的类选择器名,把这三者技术结合在一起。

为避免上述的反模式,请把下述的原则谨记于心:

  • 1. 在CSS和JavaScript里,优先考虑显式组件和行为类选择器,而不是复杂的CSS选择器。
    1. 命名组件要基于它们是什么,而不是它们在哪里
    1. 不用为样式和行为使用相同的类选择器去
    1. 把状态样式和默认样式区分开来

在HTML中这样运用类选择器经常会需要很多需要表现的类选择器,但获取的是可预见性和可维护性,这点值得肯定。毕竟,为HTML增加类选择器是相当容易的,不需要开发者有多少技能。摘自Nicolas Gallagher的原话:

当你要寻找一种方式来减少花费在编写和修改CSS的时间上来制作HTML和CSS时,这就涉及到你必须接受如果你想更改样式,你是不想花费更多时间去更改HTML元素上的类选择器。这对前端和后端开发者都有一定的实用性,任何人都可以重新安排预构建的乐高积木。这样没有人会去展示CSS的魔力了。

1 赞 收藏 2 评论

JavaScript对样式知道的太多

同样,JavaScript可以使用类来查找DOM中的元素,它也可以添加或删除类来改变元素的样式。但这可能产生一个问题,如果这些类不是明显区别于出现在页面加载中的类。

如果JavaScript对关于组件样式代码知道的太多,下面的情况将变得非常平常,一个CSS开发人员修改样式表并没有意识到他正在破坏关键的功能。

这并不是说JavaScript不应该更改组件的外观在用户与他们交互后,但应该通过一种一致的接口。它应该使用与定义的默认样式类有明显区别的类。

类似于 js-* 前缀的类,我建议使用 is-* 前缀定义改变可视化组件状态的类选择器。一个CSS规则示例可能看起来像这样:

.pop-up.is-visible { }

注意,状态类(is-visible)和组件类(pop-up)是交集关系,这一点是重要的。因为状态规则描述组件的状态,他们不应该独立出现。这区别有助于进一步区分状态样式是自默认组件样式的特殊情况。

此外,为了使用一个类似 is-* 的前缀,我们可以编写测试程序来确保我们遵循了规则。一个测试这些类型的规则方法是使用 CSSLint 和 HTML Inspector。

更多关于特定状态类的信息中可以在优秀的书 SMACSSJonathan Snook 中找到。

关于作者:蝈蝈

澳门威利斯人 3

(新浪微博:@烈焱石) 个人主页 · 我的文章

澳门威利斯人 4

今天在web上任何大一点的网站或应用程序都包含大量的html,css和javascript。随着互联网的发展和我们对互联网越来越依赖,计划组织和维护你的前端代码是绝对必要的。

JavaScript更多的样式操作

JavaScript能用类选择器去DOM中查找元素,同样,它也能通过增加或移除类选择器来改变元素的样式。但如果这些类选择器和当初加载页面时不同的话也会有问题。当JavaScript代码使用太多的组成样式操作时,那些CSS开发者就会轻易去改变样式表,却不知道破坏了关键功能。也并不是说,JavaScript不应该在用户交互之后改变可视化组件的外观,而是如果这么做,就应该使用一种一致的接口,应该使用和默认样式不一致的类选择器。

和js-*前缀的类选择器类似,我推荐使用is-*前缀的类选择器来定义那些要改变可视化组件的状态,这样的CSS规则可以像这样:

JavaScript

.pop-up.is-visible { }

1
.pop-up.is-visible { }

注意到状态类选择器(is-visible)是连接在组件类选择器(pop-up)后,这很重要。因为状态规则是描述一个的状态,不应该单独列出。如此不同就可以用来区分更多和默认组件样式不同的状态样式。

另外,可以让我们可以编写测试场景来保证像is-*这样的前缀约定是否遵从。一种测试这些规则的方式是使用CSSLint和HTML Inspector。

更多关于特定状态类选择可以查阅Jonathan Snnok编写的非常优秀的SMACSS书籍。

注:本文为翻译文章,原文<Decoupling Your HTML, CSS, and JavaScript>

目标

HTML,CSS和JavaScript之间总会存在耦合关联。不管怎样,这些技术与生俱来就是要和其它进行交互。举个例子,一种飞闪转换效果可能会在样式表中用带有类选择器定义,但它经常由HTML初始化,并通过用户交互,如编写JavaScript,来触发。由于前端代码的有些耦合是不可避免的,你的目标就不应该是简单地消除之间的耦合,而应该是减少代码间不必要的依赖耦合关系。一个后端开发者应该能够对HTML模板中的标记进行更改,而无需担心意外破坏CSS规则或者一些JavaScript功能。由于当今的web团队日渐增大且专业化,这个目标比以往更甚。

更糟糕的是,传统的计算机科学原则如分离关注点,一直是服务器端开发的一部分,却很少被讨论在前端代码中。

类选择器就是你的契约

使用合适的类选择器以及可预测的类名约定可以减少几乎每一种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组件的时代这些做法变得不那么重要,这一理论仍然适用。用于大型的团队和大型应用程序的做法,仍然会工作对于个体开发人员写的小模块。相反是不必要的情况。

今天大一点的web公司,越来越多的人接触越到比例越来越大的前端代码。因此,大多数公司努力保持他们的代码模块化。改变一个应用程序的一部分经常无意中影响到看似不相关的其他部分。

目标

HTML、CSS和JavaScript之间总是会有一些耦合。不管怎样,这些技术被用来互相作用。举个例子,一个fly-in transition(飞入变换)可能在样式表中定义了一个类选择器,但它通常是由用户交互来触发,通过JavaScript来执行,并且通过在html中添加一个类来初始化。

由于在你的前端代码中存在一些耦合是不可避免的。你的目标不应该是简单地完全消除耦合,而应该是最小化耦合,使这部分代码不必依赖其他部分。

后端开发人员应该能够改变HTML模板中的标记而不用担心不小心破坏一个CSS规则或一些JavaScript功能。随着今天的Web团队规模的增大和专业化,这一目标是比以往更重要。

反模式

前端代码是不是紧密耦合并不是明显的。然而雪上加霜的是,从一个角度看似松散耦合从另一个角度看事实上却是紧密耦合的。

以下都是反模式,包括我多次见过或我亲自做过并从我的错误中学会的。对每一个,我试图解释为什么耦合是不好的,以及如何避免。

类是你的钥匙

几乎所有类型的HTML、CSS和JavaScript之间的耦合可以被减轻通过适当的使用的类和一个可预测的类命名约定。

乍一看在标记中使用大量的类这可能看起来像一个高耦合的信号,因为HTML需要知道这些类的名字。但我认为使用类的做法非常类似传统的程序设计事件模型(隐式风格调用)或外观模式(观察者模式或发布订阅模式)。

在事件驱动编程中,取而代之对象 a 调用对象 b 一个方法,对象 a 只是简单的在一个给定的情况会下会触发特定的事件,而对象b可以订阅该事件。这种方式,对象a不需要知道任何关于对象b 的接口,只需要知道要监听的事件。

可以说事件系统存在一个形式的耦合由于对象B需要知道事件的名字从而订阅,但和对象a需要知道对象B的公共方法相比它是相当松散的耦合。

给HTML添加类是非常相似的。取而代之在CSS文件定义复杂的选择器(这相当于知道HTML的内部接口),它可以通过一个类简单地定义组件的视觉外观。HTML可能会选择使用或不使用这个类,CSS无需关心。

同样,JavaScript不需要复杂DOM序列功能,这也需要非常了解html结构知识,它可以简单的监听用户的交互元素,通过协商的类名。

类应该是将你的HTML、CSS和JavaScript连接在一起的纽带。在我的经验中这是是最简单和最棒的方法来连接这三个技术而没有将他们混合在一起太多。

JavaScript "选择符"

jQuery 和新的APIs 如document.querySelectorAll使用户非常容易在DOM中通过语言他们熟悉的CSS选择器查找元素。虽然这是非常强大的,但它有已经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")

现在的标记可以随意改变,只要类一直在正确的元素上,一切都将很好地工作。

本文由澳门威斯尼人平台登录发布于Web前端,转载请注明出处:澳门威利斯人解耦你的HTML

相关阅读