抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

好程序绝非偶然天成。这并不是说开发人员天生懒惰或者不值得信赖,而是因为独立工作 的时候,我们针对同一个问题能提出各种不同的解决方案。不同于走迷宫,解决问题几乎 不会只有一种方法。我们每个人的经验、观点和习惯各异,因此解决同一个问题的方式也不尽相同

后端开发有着各种规范,而前端已经发展到不再是随便切切图的时代,业务越来越多,工作流程越来越复杂。我们不再浪费时间把一些 Photoshop 设计稿 重构成 CMS 模板页面。因为逐渐把设计的环节转移到浏览器中,并书写响应式的网页 框架,所以在实现 CMS 的界面之前,我们往往已经开始编写所有的 HTML 和 CSS 代 码。要实现这个颠覆性的角色转变,就需要改变现有的开发流程。

这一部分将帮助我们探讨如何提高 HTML、CSS 和 JavaScript 的代码质量,编写类、设计 函数,以及声明接口。

HTML

当使用模板引擎时,人们宁可写一堆标签和CSS类名,最终,他们编写的代码如下:

 <div id="header" class="clearfix"> 
     <div id="header-screen" class="clearfix"> 
         <div id="header-inner" class="container-12 clearfix"> 
             <div id="nav-header" role="navigation"> 
                 <div class="region region-navigation"> 
                     <div class="block block-system block-menu"> 
                         <div class="block-inner"> 
                             <div class="content"> 
                                 <ul class="menu"> 
                                 	<li class="first leaf"> 
     									<a href="/start">Get Started</a>

这只是一段简单的页面顶部,在使用CMS模板标记时,为了实现业务很有可能使用div乱炖,可能你也没注意过,通常会不止10层!

为更好地可维护性,可以使用BEM原则这样写

<nav class="nav"> 
     <ul class="nav__container"> 
         <li class="nav__item"> 
             <a href="/products" class="nav__link"> 
                 <ul class="nav__container--secondary"> 
                     <li class="nav__item--secondary"> 
                     	<a href="/socks" class="nav__link--secondary">

尽管这还是相当冗长,但我要说的是,它的冗 余程度其实是恰到好处的。给每个元素都添加了相应的 CSS 类名之后,我们就不再需要依赖那些只为了样式标签而存在的 CSS 类名或元素的层级关系来决定视觉外观了。相比动 态标记,这个标记更清晰,并且我敢说,这也让标记的组织形式更“模块化”了。这个导 航可以作为网站的导航通用模板,不用改任何一个标记就可以在多处复用。因此,这种标 记并不是先等 CMS 创建完成再另外添加样式标记的,而是创建的同时就添加了样式标记, 然后整合到网站的整个导航系统中。

这背后的设计系统

CSS理论几乎和JavaScript框架一样多,但CSS理论更多的是阐述HTML和CSS之间的关系,而不是预编译的代码库

OOCSS方法

<div class="toggle simple"> 
     <div class="toggle-control open"> 
     	<h1 class="toggle-title">Title 1</h1> 
     </div> 
     <div class="toggle-details open"> ... </div> 
     ... 
 </div>
<button class="btn btn-primary btn-lg"></button>

OOCSS(http://oocss.org/)有两个主要的原则:分离结构和外观,以及分离容器和内容。

分离结构和外观,意味着将视觉特性定义为可复用的单元。前面那段简单的切换就是一个 简短的可复用性强的例子,可以套用很多不同的外观样式。例如,当前的“simple”皮肤 使用直角,而“complex”皮肤可能使用圆角,btn-primary规范按钮的颜色,btn-lg规范按钮的大小

分离容器和内容,指的是不再将元素位置作为样式的限定词。和在容器内标记的 CSS 类名 不同,我们现在使用的是可复用的 CSS 类名,如 toggle-title,它应用于相应的文本处理 上,而不管这个文本的元素是什么。这种方式下,如果没有应用别的 CSS 类名,你可以让 H1 标签以默认的样式呈现

‎这样的规范让开发者能设计出一套可经由「组合」而产生多种样式结构,让程式码更精简、便于管理与维护。‎

bootstrap 就是个很好的例子

SMACSS方法

同样以切换组件为例,按照 SMACSS(Scalable and Modular Architecture for CSS),模块化 架构的可扩展 CSS)方法,写出来的代码如下:

<div class="toggle toggle-simple container dark"> 
     <div class="toggle-control is-active"> 
     	<h2 class="toggle-title">Title 1</h2> 
     </div> 
     <div class="toggle-details is-active"> 
     	... 
     </div> 
     ... 
</div>
  • 基础

    如果不添加 CSS 类名,标记会以什么外观呈现。

  • 布局

    把页面分成一些区域。

  • 模块

    设计中的模块化、可复用的单元。

  • 状态

    描述在特定的状态或情况下,模块或布局的显示方式。

  • 主题

    一个可选的视觉外观层,可以让你更换不同主题。

BEM方法

BEM(Block Element Modifier,块元素修饰符)写出的组件代码

 <div class="toggle toggle--simple"> 
     <div class="toggle__control toggle__control--active"> 
     	<h2 class="toggle__title">Title 1</h2> 
     </div> 
     <div class="toggle__details toggle__details--active"> 
     	... 
     </div> 
 ... 
</div>
  • 块名

    所属组件的名称。

  • 元素

    元素在块里面的名称。

  • 修饰符

    任何与块或元素相关联的修饰符

依照 Block、Element 和 Modifier 來命名,toggle__details–active 描述这里的 details 是元素,active 是修饰符,这个约定使得 CSS 类名非常清晰。使用双横杠 是为了避免块名被混淆为修饰符

CSS

CSS没有作用域的概念,我们写的CSS都是从全局作用域开始开发,一层层增加细节,当项目复杂起来,通常会出现以下问题

<body> 
    <div class="main"> 
        <h2>I'm a Header</h2> 
    </div> 
    <div id="sidebar"> 
        <h2>I'm a Sidebar Header</h2> 
    </div> 
</body> 
<style> 
h2 { 
 font-size: 24px; 
 color: red; 
} 
#sidebar h2 { 
 font-size: 20px; 
 background: red; 
 color: white; 
} 
</style>

现在在sidebar增加一个日历,也有h2标签(如下),现在希望日历的h2标签背景色应该是白色,而不是从sidebar继承过来的红色,为此,要增加一个样式覆盖它

<body> 
    <div id="main"> 
        <h2>I'm a Header</h2> 
    </div> 
    <div id="sidebar"> 
        <h2>I'm a Sidebar Header</h2> 
        <div class="calendar"> 
            <h2>I'm a Calendar Header</h2> 
        </div> 
    </div> 
</body> 
<style> 
h2 { 
 font-size: 24px; 
 color: red; 
} 
#sidebar h2 { 
 font-size: 20px; 
 background: red; 
 color: white; 
} 
#sidebar .calendar h2 { 
 background: none; 
 color: red; 
} 
</style>
  • 选择器优先级

    无论你处理带 ID 的标签还是长选择器,重写一个选择器时,总是需要注意它的优先级。

  • 颜色重置

    要恢复到原来的 H2 颜色,我们必须再次指定样式,并且要覆盖当前的背景颜色。

  • 位置依赖

    现在我们的日历样式依赖于侧边栏的样式。如果将日历移到页首或者页尾,样式将会改变。

  • 多重继承

    现在这个 H2 的样式来源多达三个,这意味着只要改变主体或侧边栏的样式,都会影响到日历的呈现。

  • 深层嵌套

    日历控件里的日历条目可能还有其他的 H2,而它们也需要一个更具体的选择器,这样 一来,H2 的样式来源就增加到了四个。

如何优化?

上面介绍的三种设计方法可以拿出来分析一下了,让我们来快速看一下这些关键原则,并且了解它们是如何帮助 我们解决前面遇到的问题的。

OOCSS带来分离容器和内容的思想,我们要将日历看成一个组件,只关心日历样式实现,每处都只修改日历样式,而不是从 sidebar 选中到 日历,将日历与sidebar整体分开,分离结构和外观。

SMACSS将样式系统分为5个具体类别,在编写选择器时,需单独定义好layout(布局)、module(模块)、state(状态),theme(主题)需要哪个样式就给标签添哪个,更该模块样式通过子模块或者皮肤/主题修改,和OOCSS最显著的差异是使用皮肤和带is前缀的状态名

BEM为每个标记命名独一无二的CSS标识,这样会使每个 BEM 风格的 CSS 类名都可以 对应到某一组独属于该元素的 CSS 属性,而不会随着具体情境或选择器的使用而变化:

<body> 
    <div class="main"> 
        <h2 class="content__title">"I'm a Header"</h2> 
    </div> 
    <div class="sidebar"> 
        <h2 class="content__title--reversed"> 
            "I'm a Sidebar Header" 
        </h2> 
        <div class="calendar"> 
            <h2 class="calendar__title">"I'm a Calendar Header"</h2> 
        </div> 
    </div> 
</body> 
<style>
/* 组件文件夹 */ 
.content__title { 
 font-size: 24px; 
 color: red; 
} 
.content__title--reversed { 
 font-size: 20px; 
 background: red; 
 color: white; 
} 
.calendar__title { 
 font-size: 20px; 
 color: red; 
} 
/* 布局文件夹 */ 
.main { 
 float: left; 
 ... 
} 
.sidebar { 
 float: right; 
 ... 
} 
</style>

这就解决了刚才的由于依赖位置而造成 CSS 样式混乱的问题。

  • 选择器优先级

    把 ID 选择器改成 CSS 类名选择器是一个很好的开始,这样可以停止 CSS 优先级之间 的冲突问题,让每一个选择器的权重扁平化成“1”,我们就不再需要利用优先级较量出 “胜利者”来决定样式。

  • 颜色重置

    比降低权重更好的方法是对每一个元素使用唯一的选择器。这样你的模块样式就不再会 与侧边栏样式或者页面通用样式冲突了。

  • 位置依赖

    去掉布局文件中的样式代码之后,我们就不用再担心因为把日历组件移出侧边栏而造成 样式改变了。

  • 多重继承

    每个标题都有了自己唯一的 CSS 类名之后,我们就可以任意修改其中的某个样式而不 会影响其他标题了。如果你想改变多个选择器对应的样式,可以使用预处理器变量、混 入(mixin)或继承来帮你做。

  • 深层嵌套

    即使在日历的层级上,我们也仍然没有给 H2 标签应用任何样式。因此再给日历中的新 H2 添加样式时,就不需要重写通用样式、侧边栏样式或者日历的头部样式了。

其他编写CSS原则

单一职责原则

<div class="calendar"> 
    <h2 class="primary-header">This Is a Calendar Header</h2> 
</div> 
<div class="blog"> 
    <h2 class="primary-header">This Is a Blog Header</h2> 
</div> 

<style>
.primary-header { 
    color: red; 
    font-size: 2em; 
}
</style>

这里的 primary-hader 同时作用在两个block里,当blog的primary-header字体大小更大时,如果直接改primary-header,将会导致其余应用该样式的标签受到影响,只能通过提高优先级来覆盖它

.blog .primary-header{
	font-size: 2.4em;
}

这种方法虽然在短期内有效,但是也使我们再次面临本章开头所提到的那几个问题。这个 新的标题样式现在取决于元素位置,具有多重继承性,并且引发了“最高优先级”争夺赛。

所以针对这个问题,应该给每个标签都有单一的,有重点的任务:

<div class="calendar"> 
    <h2 class="calendar-header">This Is a Calendar Header</h2> 
</div> 
<div class="blog"> 
    <h2 class="blog-header">This Is a Blog Header</h2> 
</div> 

<style>
.calendar-header { 
    color: red; 
    font-size: 2em; 
} 
.blog-header { 
    color: red; 
    font-size: 2.4em; 
}
</style>

虽然这种方法确实会导致一些代码重复(红色字体定义了两次),但是它的可持续性带来 的好处大大超过代码重复的任何坏处。这里多出来的代码对网页大小的增加而言微不足道 (gzip 喜欢重复的内容),而且由于博客标题不一定一直保持红色,如果整个项目强制执行 单一责任原则,就能够确保在进一步改变时,我们可以毫不费力地完成,并且也不需要回 顾之前的代码

单一样式来源

单一样式来源的方法将单一责任理论应用到更深层次,不仅每个 CSS 类名被创建为单一用 途,而且每个标签的样式也只有单一的来源。在一个模块化设计中,任何组件的设计必须 由组件本身决定,而不应该被它的父类名限制。让我们看看实际应用中的情况

<div class="blog"> 
    <h2 class="blog-header">This Is a Blog Header</h2> 
    ... 
    <div class="calendar"> 
        <h2 class="calendar-header">This Is a Calendar Header</h2> 
    </div> 
</div> 

<style>
/* calendar.css */ 
.calendar-header { 
    color: red; 
    font-size: 2em; 
} 

/* blog.css */ 
.blog-header {
    color: red; 
    font-size: 2.4em; 
} 
.blog .calendar-header { 
	font-size: 1.6em; 
}
</style>

写这样的样式是为了当日历在博客文章里出现时,缩小日历头部文字的字体。从设计的角 度看,这无可厚非,最终日历组件会根据它在哪里而改变外观。我们称这种带条件的样式 为“上下文”,已被广泛应用到我的设计系统里。

这种方法的主要问题是,定义缩小字体的样式代码在博客组件的文件中,而不是单一地存在于 日历组件文件。在这种情况下,样式散落在多个组件文件里,导致很难预料某个组件放到 页面中会长什么模样。为了缓解这个问题,我建议把带上下文的样式移到日历模块代码中:

/* calendar.css */ 
.calendar-header { 
 color: red; 
 font-size: 2em; 
} 
.blog .calendar-header { 
 font-size: 1.6em; 
} 
/* blog.css */ 
.blog-header { 
 color: red; 
 font-size: 2.4em; 
}

将 blog 下的 calendar-header 放到 calendar.css 文件里,目的是不让css散落在各处

通过这种方法,当日历头部出现在博客文章里时,我们仍然能够缩小它的字体,而且如果 把所有带 calendar-header 上下文的样式放到日历文件中,我们可以看到日历头部所有可 能的变动都放在一起。这使得更新日历模块更容易(因为我们知道所有变动情况),也使 得我们能为每一个变动创建适当的测试覆盖

JavaScript

除了使用代码检查工具,在编写函数时,应尽量创造可复用的函数

内容太多了,就这样吧

参考

(图灵程序设计丛书) 前端架构设计 | PDF (scribd.com)

CSS 的模組化方法:OOCSS、SMACSS、BEM、CSS Modules、CSS in JS | Summer。桑莫。夏天 (cythilya.github.io)

评论