编写灵活、稳定、高质量的 HTML、CSS 和 Javascript 代码
有人说,编译器的规范叫做"语法规则"(grammar),这是程序员必须遵守的;而编译器忽略的部分,就叫"代码风格"(code style)。[1]
程序员固然可以自由选择代码风格,但是好的代码风格有助于写出质量更高、错误更少、更易于维护的程序。
"代码风格"的选择不应该基于个人爱好、熟悉程度、打字工作量等因素,而要考虑如何尽量使代码清晰易读、减少出错。你选择的,不是你喜欢的风格,而是一种能够清晰表达你的意图的风格。
如果你发现本规范中有任何不足,敬请指正或参与贡献。
“永远遵循同一套编码规范。”
- 严格执行一致认可的风格 [2]
- 如果有疑议,可以使用现有的、通用的模式
- 别想着过早地优化代码。得先保证它们可读又可理解
- 无论多少人参与及贡献,所有代码都应如同一个人编写的一样
“程序是给人读的,顺便让计算机执行。” —— Donald Knuth
“Programs are meant to be read by humans and only incidentally for computers to execute.” —— Donald Knuth
在项目的所有代码中,应该只有一个风格。在缩进的使用上,必须始终保持一致。使用空格来提高可读性。
请不厌其烦地使用 双引号,虽然有时使用单引号更方便。
<!-- HTML -->
<h1 class="hello">Hello world!</h1>
/* CSS */
.selector[name="hello"]:before { /* 这里的引号不要省略 */
content: "Hello World!";
background: url(image.jpg); /* 这里的引号可以省略 */
font-family: "Microsoft Yahei";
}
/* Javascript */
var hello = "Hello World!";
代码看起来应当像一系列可读的段落,而不是一大段揉在一起的连续文本。
具体情况需要自己把握,目的是为了更好地提升代码的可读性。
/* Good */
if (condition) {
for (condition1; condition2; condition3) {
var foo = 0;
var bar = 1;
// a comment
if (condition) {
alert(foo);
} else {
alert(bar);
}
}
}
/* Bad */
if (condition) {
for (condition1; condition2; condition3) {
var foo = 0;
var bar = 1;
// a comment
if (condition) {
alert(foo);
} else {
alert(bar);
}
}
}
良好的注释是非常重要的。请留出时间来描述组件(component)的工作方式、局限性和构建它们的方法。不要让你的团队其它成员来猜测一段不通用或不明显的代码的目的。
<!-- (如果是 HTML 就加上这个)
/**
* ===========================================================
* 文件标题
* ===========================================================
*
* 从这里开始写这个文件的简要说明。
*
* 当需要进行更细致的解释说明、提供文档文本时,较长的说明文本就很
* 有用了。这种长长的说明文本,可以包括示例 HTML、链接 等等其他你
* 认为重要或者有用的东西。
*
* ===========================================================
*
* @author (作者)
* @create (创建时间)
*
* @update (修改时间) (修改者)
* 1. (修改内容)
* @update (修改时间) (修改者)
* ...
*
* @todo: 这个“‘需做’陈述”描述了一个接下来要去做的小工作。这种文本,
* 如果超长了的话,应该在80个半角字符(如英文)或40个全角字符
* (如中文)后便换行,并且保持对齐。
*/
-->
将你的编辑器按照下面的配置进行设置,以避免常见的代码不一致和差异:
lf
(Unix 风格)参照文档并将这些配置信息添加到项目的.editorconfig
文件中。例如 Bootstrap 中的实例 。更多信息请参考 EditorConfig 。
# editorconfig.org
root = true
[*]
charset = utf-8
indent_size = 2
end_of_line = lf
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
在HTML5出现之前,使用DOCTYPE不仅需要在多个版本之间作出选择,而且每一个版本的代码都很长,很难记忆。所以,请始终使用简洁的 HTML5 Doctype。
<!DOCTYPE html>
根据 HTML5 规范:
强烈建议为 html 根元素指定 lang 属性,从而为文档设置正确的语言。这将有助于语音合成工具确定其所应该采用的发音,有助于翻译工具确定其翻译时所应遵守的规则等等。
语言属性可以指明地区,其中与中文相关的有四个:[5]
zh-CN
(中国)zh-TW
(台湾)zh-HK
(香港)zh-SG
(新加坡)如果需要指定使用繁体中文,应该使用 zh-Hant
,毕竟使用繁体的不止一个地区,台湾和香港都使用繁体。要指定简体中文,则使用 zh-Hans
。
声明语言有利于搜索引擎优化。将页面语言声明为中文有利于网站在中文搜索引擎中的排名。百度是典型的中文搜索引擎,在其索引中,中文网站的权重远高于其他语言(包括英文)的网站。
<html lang="zh-CN">
通过明确声明字符编码,能够确保浏览器快速并容易的判断页面内容的渲染方式。这样做的好处是,可以避免在 HTML 中使用字符实体标记(character entity),从而全部与文档编码一致(一般采用 UTF-8 编码)。
<meta charset="UTF-8">
IE 支持通过特定的 <meta>
标签来确定绘制页面所采用的 IE 版本。除非有特殊需求,最好设置为 edge mode,从而通知 IE 采用其所支持的最新的模式。
阅读这篇 stack overflow 上的文章 可以获得更多有用的信息。
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
视觉区域(viewport)指的是浏览器(包括桌面浏览器和移动浏览器)显示页面的区域。它不包含浏览器地址栏、按钮这样的东西,只是浏览区域。为了让页面适应拥有不同大小视觉区域的浏览器,需要在 <head>
元素中添加视觉区域 <meta>
元素。[5]
其中,width=device-width
用以将视觉区域的宽度会被设成与设备宽度相同的值,从而保证媒体查询的 min-width
和 max-width
特性拥有预期的效果。initial-scale=1.0
用以将页面的默认缩放级别设成100%。
<meta name="viewport" content="width=device-width, initial-scale=1.0">
根据 HTML5 规范(link、style、script),在引入 CSS 和 JavaScript 时一般不需要指定 type
属性,因为 text/css
和 text/javascript
分别是它们的默认值。
另外,也不需要为 <script>
标签添加 lang
属性。
<!-- Good -->
<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
<!-- Bad -->
<link type="text/css" rel="stylesheet" href="style.css">
<script type="text/javascript" src="script.js"></script>
太多人觉得HTML太简单,但它恰恰又是前端开发中最基础最重要的部分。
</li>
或</body>
)尽量遵循 HTML 标准和语义,但是不要以牺牲实用性为代价。任何时候都要尽量使用最少的标签并保持最小的复杂度。
<!-- Good -->
<body>
<ul>
<li>
<img src="images/image.jpg">
</li>
<li>
"Not Character Entity"
</li>
</ul>
</body>
<!-- Bad -->
<BODY>
<UL>
<li>
<img src="images/image.jpg" />
<li>
"Character Entity"
</UL>
</BODY>
良好的缩进大大增强可读性。
<doctype>
<html>
<head>
<body>
不需要缩进
<!-- Good -->
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<title>Page title</title>
</head>
<body>
<h1 class="hello-world">Hello, world!</h1>
<img src="images/image.jpg">
</body>
</html>
<!-- Bad -->
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<title>Page title</title>
</head>
<body>
<h1 class="hello-world">Hello, world!</h1>
<img src="images/image.jpg">
</body>
</html>
根据各个标签的用途而去使用它们。
使用标签要知道为什么去使用它们和是否正确。 例如,用 <h1>
标签构造标题, <p>
标签构造段落, <a>
标签构造锚点等。根据各个标签的用途而去使用是很重要的,它涉及到文档的可访问性、重用和代码效率等方面:
在前端web开发的上下文中,语义大多是与元素,属性,和属性值(包括像Microdata之类的扩展)的一致认同意义相关。这些认同意义通常在规范中被定义概念,它们可以帮助人类(程序员)或机器(搜索引擎)更好的理解网站中信息的不同方面。[4]
<!-- Good -->
<body>
<header>
<a href="recommendations">
All recommendations
</a>
</header>
<section>
<article>...</article>
</section>
<footer></footer>
<body>
<!-- Bad -->
<body>
<div>
<div onclick="goToRecommendations();">
All recommendations
</div>
</div>
<div>
<div>...</div>
</div>
<div></div>
<body>
使用标签时请尽量符合语义。
data-
前缀
<!-- Good -->
<html lang="zh-cn">
<body>
<section data-role="dialog">
<h1 class="hello-world">Hello, world!</h1>
<img
class="long-attr"
data-role="example"
src="images/image.jpg"
width="100%">
</section>
</body>
</html>
<!-- Bad -->
<html LANG="zh-cn">
<body onload=init() >
<section my-role=dialog>
<h1 class="hello-world">Hello, world!</h1>
<img class="long-attr" custom-role="example" src="images/image.jpg" style="width:100%">
</section>
</body>
</html>
HTML 属性应当按照以下给出的顺序依次排列,确保代码的易读性。
class
id
,name
data-*
src
,for
,type
,href
title
,alt
aria-*
,role
class 用于标识高度可复用组件,因此应该排在首位。id 用于标识具体组件,应当谨慎使用(例如,页面内的书签),因此排在第二位。
<a class="..." id="..." data-modal="toggle" href="#">...</a>
<input class="form-control" type="text">
<img src="..." alt="...">
布尔型属性可以在声明时不赋值。
XHTML 规范要求为其赋值,但是 HTML5 规范不需要(WhatWG section on boolean attributes):
元素的布尔型属性如果有值,就是 true,如果没有值,就是 false。
如果一定要为其赋值的话,请参考 WhatWG 规范:
如果属性存在,其值必须是 空字符串 或 区分大小写的规范名称,并且不要在收尾时添加空格。
简单来说,就是不用赋值。
<!-- Good -->
<input type="text" disabled>
<input type="checkbox" value="1" checked>
<option value="1" selected>1</option>
<!-- Bad -->
<input type="text" disabled="disabled">
<input type="checkbox" value="1" checked="checked">
<option value="1" selected="selected">1</option>
{
前保留一个空格,与选择器在同一行}
独占一行;
结尾:
后保留一个空格
/* Good */
.selector,
.selector-secondary,
.selector[type="text"] {
padding: 15px;
}
/* Bad */
#selector, .selector-secondary, .selector[type=text] {
padding:15px;
margin:0px 0px 15px; }
对于只包含一条声明的一组相关样式,为了易读性和便于快速编辑,建议将语句放在同一行。
/* Good */
.icon { background-position: 0 0; }
.icon-home { background-position: 0 -20px; }
.icon-account { background-position: 0 -40px; }
/* Bad */
.icon {
background-position: 0 0;
}
.icon-home {
background-position: 0 -20px;
}
.icon-account {
background-position: 0 -40px;
}
[class^="..."]
)。浏览器的性能会受到这些因素的影响扩展阅读:
/* Good */
.avatar { ... }
.tweet-header .username { ... }
.tweet .avatar { ... }
/* Bad */
span { ... }
.page-container #stream .stream-item .tweet .tweet-header .username { ... }
.avatar { ... }
相关的属性声明应当归为一组,并按照下面的顺序排列:
由于定位可以从正常的文档流中移除元素,并且还能覆盖盒模型相关的样式,因此排在首位。盒模型排在第二位,因为它决定了组件的尺寸和位置。
其他属性只是影响组件的内部(inside)或者是不影响前两组属性,因此排在后面。
完整的属性列表及其排列顺序请参考 Recess 。
.declaration-order {
/* Positioning */
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 100;
/* Box-model */
display: block;
float: right;
width: 100px;
height: 100px;
/* Visual */
background-color: #f5f5f5;
border: 1px solid #e5e5e5;
border-radius: 3px;
/* Typography */
font: normal 13px "Helvetica Neue", sans-serif;
line-height: 1.5;
color: #333;
text-align: center;
/* Misc */
opacity: 1;
}
rgb()
、rgba()
、hsl()
、hsla()
或rect()
)#fff
代替#ffffff
).5
代替0.5
;-.5px
代替-0.5px
)margin: 0;
代替margin: 0px;
)
/* Good */
.selector,
.selector-secondary,
.selector[type="text"] {
padding: 15px;
margin-bottom: 15px;
background-color: rgba(0,0,0,.5);
box-shadow:
0 1px 2px #ccc,
0 1px 0 #fff inset;
}
/* Bad */
#selector, .selector-secondary, .selector[type=text] {
padding:15px;
margin:0px 0px 15px;
background-color:rgba(0, 0, 0, 0.5);
box-shadow:0px 1px 2px #CCC,inset 0 1px 0 #FFFFFF }
当使用特定厂商的带有前缀的属性时,通过缩进的方式,让每个属性的值在垂直方向对齐,这样便于多行编辑。
/* Good */
.selector {
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.15);
box-shadow: 0 1px 2px rgba(0,0,0,.15);
}
/* Bad */
.selector {
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.15);
box-shadow: 0 1px 2px rgba(0,0,0,.15);
}
对于以逗号分隔并且非常长的属性值 -- 例如一堆渐变或者阴影的声明 -- 可以放在多行中,这有助于提高可读性,并易于生成有效的区分。
/* Good */
.selector {
box-shadow:
1px 1px 1px #000,
2px 2px 1px 1px #ccc inset;
background-image:
linear-gradient(#fff, #ccc),
linear-gradient(#f3c, #4ec);
}
/* Bad */
.selector {
box-shadow: 1px 1px 1px #000, 2px 2px 1px 1px #ccc inset;
background-image: linear-gradient(#fff, #ccc), linear-gradient(#f3c, #4ec);
}
在需要显示地设置所有值的情况下,应当尽量限制使用简写形式的属性声明。常见的滥用简写属性声明的情况如下:
padding
margin
font
background
border
border-radius
大部分情况下,我们不需要为简写形式的属性声明指定所有值。例如,HTML 的 heading 元素只需要设置上、下边距(margin)的值,因此,在必要的时候,只需覆盖这两个值就可以。过度使用简写形式的属性声明会导致代码混乱,并且会对属性值带来不必要的覆盖从而引起意外的副作用。
MDN(Mozilla Developer Network)上一篇非常好的关于 shorthand properties 的文章,对于不太熟悉简写属性声明及其行为的用户很有用。
/* Good */
.element {
margin-bottom: 10px;
background-color: red;
background-image: url("image.jpg");
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
/* Bad */
.element {
margin: 0 0 10px;
background: red;
background: url("image.jpg");
border-radius: 3px 3px 0 0;
}
-
连接在为 Sass 和 Less 变量命名是也可以参考上面列出的各项规范。
/* Good */
.btn { ... }
.btn-success { ... }
.tweet { ... }
.tweet-header { ... }
/* Bad */
.s { ... }
.t_01 { ... }
.red { ... }
.header { ... }
与<link>
标签相比,@import
指令要慢很多,不光增加了额外的请求次数,还会导致不可预料的问题。替代办法有以下几种:
<link>
元素请参考 Steve Souders 的文章 了解更多知识。
<!-- Good -->
<link rel="stylesheet" href="core.css">
/* Bad */
@import url("more.css");
.selector { ... }
.selector-avatar { ... }
@media (min-width: 480px) {
.selector { ...}
.selector-avatar { ... }
}
;
;
左边不要有多余的空格,
的右边保留一个空格根据上述风格来配置 .jshintrc
文件。
/* Good */
var foo = "Hello";
var bar = "World";
var obj = {
foo: 0
};
function showMessage(a, b) {
alert(a + " " + b);
}
obj.foo = 10;
/* Bad */
var foo = "Hello" // 没有分号
var bar = "World" ; var obj = { // 两条语句在同一行
foo:0
}
function showMessage(a,b) { // 逗号后面没有保留空格
alert(a+" "+b) // 操作符左右没有保留空格
}
===
代替相等 ==
eval()
、Function
、setTimeout
、setInterval()
中使用字符串创建 Javascript 语句with
语句goto
语句根据上述风格来配置 .jshintrc
文件。
/* Good */
var obj = { foo: 0 };
obj.foo = 10;
if (obj.foo === 10) {
setTimeout(function () {
alert("closure");
}, 50);
);
/* Bad */
var obj = new Object(); // 不要用构造函数创建
with (obj) { // 不要使用 with
foo = 10; // 这里创建了一个全局变量
}
if (obj.foo == 10) { // 尽量使用恒等式
setTimeout("alert('eval')", 50); // 不要用字符串的方式执行语句
}
Javascript 中的声明可以在脚本的任何地方,但是它们实际上都会提前到包含这段逻辑的上下文的顶部执行。
var
。这样方便在开发时修改,而在构建阶段再把它们合并var
声明函数
/* Good */
var foo;
var bar = true;
if (condition) {
var func = function () {
...
};
} else {
var func = function () {
...
};
}
/* Bad */
var foo, bar = true,
a = 1, b;
if (condition) {
function func () {
...
};
} else {
function func () {
...
};
}
Javascript 对大小写是敏感的。名字最好能与它的内容相对应,一般情况下要使用英文而不是拼音。
is
、has
等作为第一个单词)_
连接get
、set
等作为第一个单词)
/* Good */
var myName = "Nicholas";
var names = [0, 1, 2];
var isSet = false;
var MAX_COUNT = 10;
function getName() {
return myName;
}
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function () {
alert(this.name);
}
/* Bad */
var setName = "Nicholas";
var name = [0, 1, 2];
var set = false;
var max_count = 10;
function theName() {
return setName;
}
function person(name) {
this.name = name;
}
person.prototype.sayName = function () {
alert(this.name);
}
采用 Java 的风格。
{}
包含,不要省略{
的左边保留一个空格,并且要与前面语句在同一行}
(某些情况下会与右括号 )
或分号 ;
一起)独占一行}
的右边保留一个空格,并且不要换行
/* Good */
if (conidion) {
...
} else if {
...
} else {
...
}
/* Bad */
if (conidion) foo();
if (conidion)
foo();
if (conidion) { foo(); }
if (conidion)
{
foo();
}
if (conidion) {
...
}
else{
...
}
(
之间没有空格(
之间,都有一个空格
/* Good */
foo();
bar(a, b);
for (var i = 0; i < 9; i++) {
...
}
(function () {
...
})();
/* Bad */
foo ();
bar( a, b );
for(var i = 0; i < 9; i++) {
...
}
function() {
...
}();
与 Java、PHP 这些语言不同,Javascript 中的字符串使用双引号或单引号在功能上并无不同。但是我们需要关心的是,代码应当从头到尾只保持一种风格。
""
括起来\
来换行+
来换行
/* Good */
var foo = "Hello World!";
var longString = "This is a long string " +
"example.";
/* Bad */
var foo = 'Hello World!';
var longString = "This is a long string \
example.";
在 Javascript 中,整数 和 浮点数 其实都存储为相同的数据类型。
.
前后的数字
/* Good */
var float = 10.00 ;
var float = 0.01 ;
var num = 8 ;
/* Bad */
var float = 10. ;
var float = .01 ;
var num = 010 ; // 八进制写法
/* Good */
var values = [0, 1, 2];
var longValues = [
0,
[0, 1, 2],
{a: 1, b: 2}
];
/* Bad */
var values = [0,1,2];
var values = [ 0, 1, 2 ];
var longValues = [ 0, [0, 1, 2], {a: 1, b: 2} ];
对象直接量可以高效地完成与非直接量写法相同的任务。
:
的右侧保留一个空格,而左边不保留空格,
/* Good */
var obj = {
foo: 0,
bar: 1,
obj: {a: 1, b: 2}
}
/* Bad */
var obj = { foo: 0, bar: 0 }
var obj = {
"foo":0,
bar :1
}
var obj = {
foo: 0, bar: 0,
}
null
常会与 undefined
混淆。其中一个让人颇感困惑之处在于 null == undefined
结果是 true
。然而这两个值的用途却各不相同。
以下场景才应当使用 null
:
以下场景不应当使用 null
:
null
来检测一个未初始化的变量null
来检测是否传入了参数禁止使用特殊值 undefined
:
null
typeof
来检测 undefined
/* Good */
var person = null;
if (person !== null) {
doSomething();
}
function getPerson() {
if (condition) {
return new Person("Nicholas");
} else {
return null;
}
}
console.log(person === null);
console.log(typeof person === "undefined");
/* Bad */
var person;
if (person !== null) { // person 未初始化
doSomething();
}
function doSomething(arg1, arg2) {
if (arg2 !== null) { // 不要用来检测参数
doSomethingElse();
}
}
console.log(person === undefined); // 不要直接使用 undefined
尽管语法相似,Javascript 中任何表达式都可以合法地用于 case
从句,但在其他语言中则必须使用原始值和常量。
case
相对 switch
缩进一个层级case
后的语句再缩进一个层级:
左边不要有空格,右边不要有语句default
就补充一个注释
/* Good */
switch (conidion) {
case 0:
...
break;
case 1:
...
break;
// no default
}
/* Bad */
switch (conidion) {
case 0 : ... break;
case 1 :
...
break;
}
传统的 for
循环,是从 C 和 Java 继承而来。
;
不能省略。
;
右边保留一个空格var
声明建议提取出来break
和 continue
会改变循环的方向,尽量避免使用,但是不禁止
/* Good */
var i;
for (i = 0; i < 9; i++ ) {
if (i === 2) {
process();
}
}
/* Bad */
for (var i = 0;i <9) {
if (i !== 2) {
continue;
}
process();
i++;
}
for-in
循环不仅遍历对象的实例属性(instance property),同样还遍历从原型继承来的属性。
hasOwnProperty
一起检测属性,除非你需要检查原型
/* Good */
var prop;
for (prop in object) {
if (object.hasOwnProperty(prop)) {
console(prop);
}
}
/* Bad */
var prop;
var vals = [1, 2, 3];
for (prop in object) {
console(prop);
}
for (prop in vals) {
console(prop);
}
单行注释以 //
开头。
//
后保留一个空格
// a comment
var foo = 0;
var foo = 0; // keep an indent
/**
* A module representing a jacket.
*
* @module jacket
* @author devi
*/