CSS编码规范
1 前言
CSS 作为网页样式的描述语言,在百度一直有着广泛的应用。本文档的目标是使 CSS 代码风格保持一致,容易被理解和被维护。
虽然本文档是针对 CSS 设计的,但是在使用各种 CSS 的预编译器(如 less、sass、stylus 等)时,适用的部分也应尽量遵循本文档的约定。
2 代码风格
2.1 文件
[建议] CSS
文件使用无 BOM
的 UTF-8
编码。
解释:
UTF-8 编码具有更广泛的适应性。BOM 在使用程序或工具处理文件时可能造成不必要的干扰。
2.2 缩进
[强制] 使用 4
个空格做为一个缩进层级,不允许使用 2
个空格 或 tab
字符。
示例:
.selector {
margin: 0;
padding: 0;
}
2.3 空格
[强制] 选择器
与 {
之间必须包含空格。
示例:
.selector {
}
[强制] 属性名
与之后的 :
之间不允许包含空格, :
与 属性值
之间必须包含空格。
示例:
margin: 0;
[强制] 列表型属性值
书写在单行时,,
后必须跟一个空格。
示例:
font-family: Arial, sans-serif;
2.4 行长度
[强制] 每行不得超过 120
个字符,除非单行不可分割。
解释:
常见不可分割的场景为URL超长。
2.5 选择器
[强制] 当一个 rule 包含多个 selector 时,每个选择器声明必须独占一行。
示例:
/* good */
.post,
.page,
.comment {
line-height: 1.5;
}
/* bad */
.post, .page, .comment {
line-height: 1.5;
}
[强制] >
、+
、~
选择器的两边各保留一个空格。
示例:
/* good */
main > nav {
padding: 10px;
}
label + input {
margin-left: 5px;
}
input:checked ~ button {
background-color: #69C;
}
/* bad */
main>nav {
padding: 10px;
}
label+input {
margin-left: 5px;
}
input:checked~button {
background-color: #69C;
}
[强制] 属性选择器中的值必须用双引号包围。
解释:
不允许使用单引号,不允许不使用引号。
示例:
/* good */
article[character="juliet"] {
voice-family: "Vivien Leigh", victoria, female;
}
/* bad */
article[character='juliet'] {
voice-family: "Vivien Leigh", victoria, female;
}
2.6 属性
[强制] 属性定义必须另起一行。
示例:
/* good */
.selector {
margin: 0;
padding: 0;
}
/* bad */
.selector { margin: 0; padding: 0; }
[强制] 属性定义后必须以分号结尾。
示例:
/* good */
.selector {
margin: 0;
}
/* bad */
.selector {
margin: 0
}
3 通用
3.1 选择器
[强制] 如无必要,不得为 id
、class
选择器添加类型选择器进行限定。
解释:
在性能和维护性上,都有一定的影响。
示例:
/* good */
#error,
.danger-message {
font-color: #c00;
}
/* bad */
dialog#error,
p.danger-message {
font-color: #c00;
}
[建议] 选择器的嵌套层级应不大于 3
级,位置靠后的限定条件应尽可能精确。
示例:
/* good */
#username input {}
.comment .avatar {}
/* bad */
.page .header .login #username input {}
.comment div * {}
3.2 属性缩写
[建议] 在可以使用缩写的情况下,尽量使用属性缩写。
示例:
/* good */
.post {
font: 12px/1.5 arial, sans-serif;
}
/* bad */
.post {
font-family: arial, sans-serif;
font-size: 12px;
line-height: 1.5;
}
3.3 属性书写顺序
[建议] 同一 rule set 下的属性在书写时,应按功能进行分组,并以 Formatting Model(布局方式、位置) > Box Model(尺寸) > Typographic(文本相关) > Visual(视觉效果) 的顺序书写,以提高代码的可读性。
解释:
- Formatting Model 相关属性包括:
position
/top
/right
/bottom
/left
/float
/display
/overflow
等 - Box Model 相关属性包括:
border
/margin
/padding
/width
/height
等 - Typographic 相关属性包括:
font
/line-height
/text-align
/word-wrap
等 - Visual 相关属性包括:
background
/color
/transition
/list-style
等
另外,如果包含 content
属性,应放在最前面。
示例:
.sidebar {
/* formatting model: positioning schemes / offsets / z-indexes / display / ... */
position: absolute;
top: 50px;
left: 0;
overflow-x: hidden;
/* box model: sizes / margins / paddings / borders / ... */
width: 200px;
padding: 5px;
border: 1px solid #ddd;
/* typographic: font / aligns / text styles / ... */
font-size: 14px;
line-height: 20px;
/* visual: colors / shadows / gradients / ... */
background: #f5f5f5;
color: #333;
-webkit-transition: color 1s;
-moz-transition: color 1s;
transition: color 1s;
}
3.4 清除浮动
[建议] 当元素需要撑起高度以包含内部的浮动元素时,通过对伪类设置 clear
或触发 BFC
的方式进行 clearfix
。尽量不使用增加空标签的方式。 ?????????????
解释:
触发 BFC 的方式很多,常见的有:
- float 非 none
- position 非 static
- overflow 非 visible
如希望使用更小副作用的清除浮动方法,参见 A new micro clearfix hack 一文。
另需注意,对已经触发 BFC 的元素不需要再进行 clearfix。
3.5 !important
[建议] 尽量不使用 !important
声明。
[建议] 当需要强制指定样式且不允许任何场景覆盖时,通过标签内联和 !important
定义样式。
解释:
必须注意的是,仅在设计上 确实不允许任何其它场景覆盖样式
时,才使用内联的 !important
样式。通常在第三方环境的应用中使用这种方案。下面的 z-index
章节是其中一个特殊场景的典型样例。
3.6 z-index
[建议] 将 z-index
进行分层,对文档流外绝对定位元素的视觉层级关系进行管理。
解释:
同层的多个元素,如多个由用户输入触发的 Dialog,在该层级内使用相同的 z-index
或递增 z-index
。
建议每层包含100个 z-index
来容纳足够的元素,如果每层元素较多,可以调整这个数值。
[建议] 在可控环境下,期望显示在最上层的元素,z-index
指定为 999999
。
解释:
可控环境分成两种,一种是自身产品线环境;还有一种是可能会被其他产品线引用,但是不会被外部第三方的产品引用。
不建议取值为 2147483647
。以便于自身产品线被其他产品线引用时,当遇到层级覆盖冲突的情况,留出向上调整的空间。
[建议] 在第三方环境下,期望显示在最上层的元素,通过标签内联和 !important
,将 z-index
指定为 2147483647
。
解释:
第三方环境对于开发者来说完全不可控。在第三方环境下的元素,为了保证元素不被其页面其他样式定义覆盖,需要采用此做法。
4 值与单位
4.1 文本
[强制] 文本内容必须用双引号包围。
解释:
文本类型的内容可能在选择器、属性值等内容中。
示例:
/* good */
html[lang|="zh"] q:before {
font-family: "Microsoft YaHei", sans-serif;
content: "“";
}
html[lang|="zh"] q:after {
font-family: "Microsoft YaHei", sans-serif;
content: "”";
}
/* bad */
html[lang|=zh] q:before {
font-family: 'Microsoft YaHei', sans-serif;
content: '“';
}
html[lang|=zh] q:after {
font-family: "Microsoft YaHei", sans-serif;
content: "”";
}
4.2 数值
[强制] 当数值为 0 - 1 之间的小数时,省略整数部分的 0
。
示例:
/* good */
panel {
opacity: .8;
}
/* bad */
panel {
opacity: 0.8;
}
4.4 长度
[强制] 长度为 0
时须省略单位。 (也只有长度单位可省)
示例:
/* good */
body {
padding: 0 5px;
}
/* bad */
body {
padding: 0px 5px;
}
4.5 颜色
[强制] RGB颜色值必须使用十六进制记号形式 #rrggbb
。不允许使用 rgb()
。
解释:
带有alpha的颜色信息可以使用 rgba()
。使用 rgba()
时每个逗号后必须保留一个空格。
示例:
/* good */
.success {
box-shadow: 0 0 2px rgba(0, 128, 0, .3);
border-color: #008000;
}
/* bad */
.success {
box-shadow: 0 0 2px rgba(0,128,0,.3);
border-color: rgb(0, 128, 0);
}
[强制] 颜色值可以缩写时,必须使用缩写形式。
示例:
/* good */
.success {
background-color: #CCC;
}
/* bad */
.success {
background-color: #CCCCCC;
}
[强制] 颜色值不允许使用命名色值。
示例:
/* good */
.success {
color: #90ee90;
}
/* bad */
.success {
color: lightgreen;
}
[建议] 颜色值中的英文字符采用小写。如不用小写也需要保证同一项目内保持大小写一致。
示例:
/* good */
.success {
background-color: #aca;
color: #90ee90;
}
/* good */
.success {
background-color: #ACA;
color: #90EE90;
}
/* bad */
.success {
background-color: #ACA;
color: #90ee90;
}
4.6 2D 位置
[强制] 必须同时给出水平和垂直方向的位置。
解释:
2D 位置初始值为 0% 0%
,但在只有一个方向的值时,另一个方向的值会被解析为 center。为避免理解上的困扰,应同时给出两个方向的值。background-position属性值的定义
示例:
/* good */
body {
background-position: center top; /* 50% 0% */
}
/* bad */
body {
background-position: top; /* 50% 0% */
}
5 文本编排
5.1 字体族
[强制] font-family
按「西文字体在前、中文字体在后」、「效果佳 (质量高/更能满足需求) 的字体在前、效果一般的字体在后」的顺序编写,最后必须指定一个通用字体族( serif
/ sans-serif
)。
解释:
更详细说明可参考本文。
示例:
/* Display according to platform */
.article {
font-family: Arial, sans-serif;
}
/* Specific for most platforms */
h1 {
font-family: "Helvetica Neue", Arial, "Hiragino Sans GB", "WenQuanYi Micro Hei", "Microsoft YaHei", sans-serif;
}
[强制] font-family
不区分大小写,但在同一个项目中,同样的 Family Name
大小写必须统一。
示例:
/* good */
body {
font-family: Arial, sans-serif;
}
h1 {
font-family: Arial, "Microsoft YaHei", sans-serif;
}
/* bad */
body {
font-family: arial, sans-serif;
}
h1 {
font-family: Arial, "Microsoft YaHei", sans-serif;
}
5.4 字重
[强制] font-weight
属性必须使用数值方式描述。
解释:
CSS 的字重分 100 – 900 共九档,但目前受字体本身质量和浏览器的限制,实际上支持 400
和 700
两档,分别等价于关键词 normal
和 bold
。
浏览器本身使用一系列启发式规则来进行匹配,在 <700
时一般匹配字体的 Regular 字重,>=700
时匹配 Bold 字重。
但已有浏览器开始支持 =600
时匹配 Semibold 字重 (见此表),故使用数值描述增加了灵活性,也更简短。
示例:
/* good */
h1 {
font-weight: 700;
}
/* bad */
h1 {
font-weight: bold;
}
5.5 行高
[建议] line-height
在定义文本段落时,应使用数值。
解释:
将 line-height
设置为数值,浏览器会基于当前元素设置的 font-size
进行再次计算。在不同字号的文本段落组合中,能达到较为舒适的行间间隔效果,避免在每个设置了 font-size
都需要设置 line-height
。
当 line-height
用于控制垂直居中时,还是应该设置成与容器高度一致。
示例:
.container {
line-height: 1.5;
}
6 变换与动画
[强制] 使用 transition
时应指定 transition-property
。
示例:
/* good */
.box {
transition: color 1s, border-color 1s;
}
/* bad */
.box {
transition: all 1s;
}
7 响应式
[强制] Media Query
不得单独编排,必须与相关的规则一起定义。
示例:
/* Good */
/* header styles */
@media (...) {
/* header styles */
}
/* main styles */
@media (...) {
/* main styles */
}
/* footer styles */
@media (...) {
/* footer styles */
}
/* Bad */
/* header styles */
/* main styles */
/* footer styles */
@media (...) {
/* header styles */
/* main styles */
/* footer styles */
}
[强制] Media Query
如果有多个逗号分隔的条件时,应将每个条件放在单独一行中。
示例:
@media
(-webkit-min-device-pixel-ratio: 2), /* Webkit-based browsers */
(min--moz-device-pixel-ratio: 2), /* Older Firefox browsers (prior to Firefox 16) */
(min-resolution: 2dppx), /* The standard way */
(min-resolution: 192dpi) { /* dppx fallback */
/* Retina-specific stuff here */
}
[建议] 尽可能给出在高分辨率设备 (Retina) 下效果更佳的样式。 ???????
8 兼容性
8.1 属性前缀
[强制] 带私有前缀的属性由长到短排列,按冒号位置对齐。
解释:
标准属性放在最后,按冒号对齐方便阅读,也便于在编辑器内进行多行编辑。
示例:
.box {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
8.2 Hack
[建议] 需要添加 hack
时应尽可能考虑是否可以采用其他方式解决。
解释:
如果能通过合理的 HTML 结构或使用其他的 CSS 定义达到理想的样式,则不应该使用 hack 手段解决问题。通常 hack 会导致维护成本的增加。
[建议] 尽量使用 选择器 hack
处理兼容性,而非 属性 hack
。
解释:
尽量使用符合 CSS 语法的 selector hack,可以避免一些第三方库无法识别 hack 语法的问题。
示例:
/* IE 7 */
*:first-child + html #header {
margin-top: 3px;
padding: 5px;
}
/* IE 6 */
* html #header {
margin-top: 5px;
padding: 4px;
}
[建议] 尽量使用简单的 属性 hack
。
示例:
.box {
_display: inline; /* fix double margin */
float: left;
margin-left: 20px;
}
.container {
overflow: hidden;
*zoom: 1; /* triggering hasLayout */
}
8.3 Expression
[强制] 禁止使用 Expression
。
html
HTML编码规范
1 前言
HTML 作为描述网页结构的超文本标记语言,在百度一直有着广泛的应用。本文档的目标是使 HTML 代码风格保持一致,容易被理解和被维护。
2 代码风格
2.1 缩进与换行
[强制] 使用 4
个空格做为一个缩进层级,不允许使用 2
个空格 或 tab
字符。
示例:
<ul>
<li>first</li>
<li>second</li>
</ul>
2.2 命名
[强制] class
必须单词全字母小写,单词间以 -
分隔。
[强制] class
必须代表相应模块或部件的内容或功能,不得以样式信息进行命名。
示例:
<!-- good -->
<div class="sidebar"></div>
<!-- bad -->
<div class="left"></div>
[强制] 元素 id
必须保证页面唯一。
解释:
同一个页面中,不同的元素包含相同的 id
,不符合 id
的属性含义。并且使用 document.getElementById
时可能导致难以追查的问题。
[建议] id
建议单词全字母小写,单词间以 _
分隔。同项目必须保持风格一致。
[建议] id
、class
命名,在避免冲突并描述清楚的前提下尽可能短。
示例:
<!-- good -->
<div id="nav"></div>
<!-- bad -->
<div id="navigation"></div>
<!-- good -->
<p class="comment"></p>
<!-- bad -->
<p class="com"></p>
<!-- good -->
<span class="author"></span>
<!-- bad -->
<span class="red"></span>
[强制] 同一页面,应避免使用相同的 name
与 id
。
解释:
IE 浏览器会混淆元素的 id
和 name
属性, document.getElementById
可能获得不期望的元素。所以在对元素的 id
与 name
属性的命名需要非常小心。
一个比较好的实践是,为 id
和 name
使用不同的命名法。
示例:
<input name="foo">
<div id="foo"></div>
<script>
// IE6 将显示 INPUT
alert(document.getElementById('foo').tagName);
</script>
2.3 标签
[强制] 标签名必须使用小写字母。
示例:
<!-- good -->
<p>Hello StyleGuide!</p>
<!-- bad -->
<P>Hello StyleGuide!</P>
[强制] 对于无需自闭合的标签,不允许自闭合。
解释:
常见无需自闭合标签有 input
、br
、img
、hr
等。
示例:
<!-- good -->
<input type="text" name="title">
<!-- bad -->
<input type="text" name="title" />
[强制] 对 HTML5
中规定允许省略的闭合标签,不允许省略闭合标签。
解释:
对代码体积要求非常严苛的场景,可以例外。比如:第三方页面使用的投放系统。
示例:
<!-- good -->
<ul>
<li>first</li>
<li>second</li>
</ul>
<!-- bad -->
<ul>
<li>first
<li>second
</ul>
[强制] 标签使用必须符合标签嵌套规则。
解释:
比如 div
不得置于 p
中,tbody
必须置于 table
中。
详细的标签嵌套规则参见HTML DTD中的 Elements
定义部分。
[建议] HTML 标签的使用应该遵循标签的语义。
解释:
下面是常见标签语义
- p - 段落
- h1,h2,h3,h4,h5,h6 - 层级标题
- strong,em - 强调
- ins - 插入
- del - 删除
- abbr - 缩写
- code - 代码标识
- cite - 引述来源作品的标题
- q - 引用
- blockquote - 一段或长篇引用
- ul - 无序列表
- ol - 有序列表
- dl,dt,dd - 定义列表
示例:
<!-- good -->
<p>Esprima serves as an important <strong>building block</strong> for some JavaScript language tools.</p>
<!-- bad -->
<div>Esprima serves as an important <span class="strong">building block</span> for some JavaScript language tools.</div>
[建议] 标签的使用应尽量简洁,减少不必要的标签。 ???????????
示例:
<!-- good -->
<img class="avatar" src="image.png">
<!-- bad -->
<span class="avatar">
<img src="image.png">
</span>
2.4 属性
[强制] 属性名必须使用小写字母。
示例:
<!-- good -->
<table cellspacing="0">...</table>
<!-- bad -->
<table cellSpacing="0">...</table>
[强制] 属性值必须用双引号包围。
解释:
不允许使用单引号,不允许不使用引号。
示例:
<!-- good -->
<script src="esl.js"></script>
<!-- bad -->
<script src='esl.js'></script>
<script src=esl.js></script>
[建议] 布尔类型的属性,建议不添加属性值。
示例:
<input type="text" disabled>
<input type="checkbox" value="1" checked>
[建议] 自定义属性建议以 xxx-
为前缀,推荐使用 data-
。
解释:
使用前缀有助于区分自定义属性和标准定义的属性。
示例:
<ol data-ui-type="Select"></ol>
3 通用
[建议] 启用 IE Edge 模式。
示例:
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
3.2 编码
[强制] 页面必须使用精简形式,明确指定字符编码。指定字符编码的 meta
必须是 head
的第一个直接子元素。
解释:
见 HTML5 Charset能用吗 一文。
示例:
<html>
<head>
<meta charset="UTF-8">
......
</head>
<body>
......
</body>
</html>
3.3 CSS 和 JavaScript 引入
[强制] 引入 CSS
时必须指明 rel="stylesheet"
。
示例:
<link rel="stylesheet" href="page.css">
4 head
4.1 title
[强制] 页面必须包含 title
标签声明标题。
[强制] title
必须作为 head
的直接子元素,并紧随 charset
声明之后。
解释:
title
中如果包含 ASCII 之外的字符,浏览器需要知道字符编码类型才能进行解码,否则可能导致乱码。
示例:
<head>
<meta charset="UTF-8">
<title>页面标题</title>
</head>
4.2 favicon
[强制] 保证 favicon
可访问。
解释:
在未指定 favicon 时,大多数浏览器会请求 Web Server 根目录下的 favicon.ico
。为了保证 favicon 可访问,避免 404,必须遵循以下两种方法之一:
- 在 Web Server 根目录放置
favicon.ico
文件。 - 使用
link
指定 favicon。
示例:
<link rel="shortcut icon" href="path/to/favicon.ico">
4.3 viewport
[建议] 若页面欲对移动设备友好,需指定页面的 viewport
。
解释:
viewport meta tag 可以设置可视区域的宽度和初始缩放大小,避免在移动设备上出现页面展示不正常。
比如,在页面宽度小于 980px
时,若需 iOS 设备友好,应当设置 viewport 的 width
值来适应你的页面宽度。同时因为不同移动设备分辨率不同,在设置时,应当使用 device-width
和 device-height
变量。
另外,为了使 viewport 正常工作,在页面内容样式布局设计上也要做相应调整,如避免绝对定位等。关于 viewport 的更多介绍,可以参见 Safari Web Content Guide的介绍
5 图片
[强制] 禁止 img
的 src
取值为空。延迟加载的图片也要增加默认的 src
。
解释:
src
取值为空,会导致部分浏览器重新加载一次当前页面,参考:https://developer.yahoo.com/performance/rules.html#emptysrc
[建议] 为重要图片添加 alt
属性。
解释:
可以提高图片加载失败时的用户体验。
[建议] 添加 width
和 height
属性,以避免页面抖动。
[建议] 有下载需求的图片采用 img
标签实现,无下载需求的图片采用 CSS 背景图实现。
解释:
- 产品 logo、用户头像、用户产生的图片等有潜在下载需求的图片,以
img
形式实现,能方便用户下载。 - 无下载需求的图片,比如:icon、背景、代码使用的图片等,尽可能采用 CSS 背景图实现。
6 表单
6.1 控件标题
[强制] 有文本标题的控件必须使用 label
标签将其与其标题相关联。
解释:
有两种方式:
- 将控件置于
label
内。 -
label
的for
属性指向控件的id
。
推荐使用第一种,减少不必要的 id
。如果 DOM 结构不允许直接嵌套,则应使用第二种。
示例:
<label><input type="checkbox" name="confirm" value="on"> 我已确认上述条款</label>
<label for="username">用户名:</label> <input type="textbox" name="username" id="username">
6.2 按钮
[强制] 使用 button
元素时必须指明 type
属性值。
解释:
button
元素的默认 type
为 submit
,如果被置于 form
元素中,点击后将导致表单提交。为显示区分其作用方便理解,必须给出 type
属性。
示例:
<button type="submit">提交</button>
<button type="button">取消</button>
[建议] 尽量不要使用按钮类元素的 name
属性。
解释:
由于浏览器兼容性问题,使用按钮的 name
属性会带来许多难以发现的问题。具体情况可参考此文。
6.3 可访问性 (A11Y)
[建议] 负责主要功能的按钮在 DOM 中的顺序应靠前。??????????
解释:
负责主要功能的按钮应相对靠前,以提高可访问性。如果在 CSS 中指定了 float: right
则可能导致视觉上主按钮在前,而 DOM 中主按钮靠后的情况。
示例:
<!-- good -->
<style>
.buttons .button-group {
float: right;
}
</style>
<div class="buttons">
<div class="button-group">
<button type="submit">提交</button>
<button type="button">取消</button>
</div>
</div>
<!-- bad -->
<style>
.buttons button {
float: right;
}
</style>
<div class="buttons">
<button type="button">取消</button>
<button type="submit">提交</button>
</div>
[建议] 在针对移动设备开发的页面时,根据内容类型指定输入框的 type
属性。
解释:
根据内容类型指定输入框类型,能获得能友好的输入体验。
示例:
<input type="date">
7 多媒体
[建议] 当在现代浏览器中使用 audio
以及 video
标签来播放音频、视频时,应当注意格式。
解释:
音频应尽可能覆盖到如下格式:
- MP3
- WAV (不确定)
- Ogg (ios不支持)
视频应尽可能覆盖到如下格式:
- MP4
- WebM
- Ogg
[建议] 在支持 HTML5
的浏览器中优先使用 audio
和 video
标签来定义音视频元素。
[建议] 使用退化到插件的方式来对多浏览器进行支持。
示例:
<audio controls>
<source src="audio.mp3" type="audio/mpeg">
<source src="audio.ogg" type="audio/ogg">
<object width="100" height="50" data="audio.mp3">
<embed width="100" height="50" src="audio.swf">
</object>
</audio>
<video width="100" height="50" controls>
<source src="video.mp4" type="video/mp4">
<source src="video.ogg" type="video/ogg">
<object width="100" height="50" data="video.mp4">
<embed width="100" height="50" src="video.swf">
</object>
</video>
[建议] 只在必要的时候开启音视频的自动播放。
[建议] 在 object
标签内部提供指示浏览器不支持该标签的说明。
示例:
<object width="100" height="50" data="something.swf">DO NOT SUPPORT THIS TAG</object>
js
JavaScript编码规范
1 前言
JavaScript 在百度一直有着广泛的应用,特别是在浏览器端的行为管理。本文档的目标是使 JavaScript 代码风格保持一致,容易被理解和被维护。
虽然本文档是针对 JavaScript 设计的,但是在使用各种 JavaScript 的预编译语言时(如 TypeScript 等)时,适用的部分也应尽量遵循本文档的约定。
2 代码风格
2.1 文件
[建议] JavaScript 文件使用无 BOM
的 UTF-8
编码。
解释:
UTF-8 编码具有更广泛的适应性。BOM 在使用程序或工具处理文件时可能造成不必要的干扰。
[建议] 在文件结尾处,保留一个空行。
2.2 结构
2.2.1 缩进
[强制] 使用 4
个空格做为一个缩进层级,不允许使用 2
个空格 或 tab
字符。
2.2.2 空格
[强制] 二元运算符两侧必须有一个空格,一元运算符与操作对象之间不允许有空格。
示例:
var a = !arr.length;
a++;
a = b + c;
[强制] 用作代码块起始的左花括号 {
前必须有一个空格。
示例:
// good
if (condition) {
}
while (condition) {
}
function funcName() {
}
// bad
if (condition){
}
while (condition){
}
function funcName(){
}
[强制] if / else / for / while / function / switch / do / try / catch / finally
关键字后,必须有一个空格。
示例:
// good
if (condition) {
}
while (condition) {
}
(function () {
})();
// bad
if(condition) {
}
while(condition) {
}
(function() {
})();
[强制] 在对象创建时,属性中的 :
之后必须有空格,:
之前不允许有空格。
示例:
// good
var obj = {
a: 1,
b: 2,
c: 3
};
// bad
var obj = {
a : 1,
b:2,
c :3
};
[强制] 函数声明、具名函数表达式、函数调用中,函数名和 (
之间不允许有空格。
示例:
// good
function funcName() {
}
var funcName = function funcName() {
};
funcName();
// bad
function funcName () {
}
var funcName = function funcName () {
};
funcName ();
[强制] ,
和 ;
前不允许有空格。如果不位于行尾,,
和 ;
后必须跟一个空格。
示例:
// good
callFunc(a, b);
// bad
callFunc(a , b) ;
[强制] 在函数调用、函数声明、括号表达式、属性访问、if / for / while / switch / catch
等语句中,()
和 []
内紧贴括号部分不允许有空格。
示例:
// good
callFunc(param1, param2, param3);
save(this.list[this.indexes[i]]);
needIncream && (variable += increament);
if (num > list.length) {
}
while (len--) {
}
// bad
callFunc( param1, param2, param3 );
save( this.list[ this.indexes[ i ] ] );
needIncreament && ( variable += increament );
if ( num > list.length ) {
}
while ( len-- ) {
}
[强制] 单行声明的数组与对象,如果包含元素,{}
和 []
内紧贴括号部分不允许包含空格。
解释:
声明包含元素的数组与对象,只有当内部元素的形式较为简单时,才允许写在一行。元素复杂的情况,还是应该换行书写。
示例:
// good
var arr1 = [];
var arr2 = [1, 2, 3];
var obj1 = {};
var obj2 = {name: 'obj'};
var obj3 = {
name: 'obj',
age: 20,
sex: 1
};
// bad
var arr1 = [ ];
var arr2 = [ 1, 2, 3 ];
var obj1 = { };
var obj2 = { name: 'obj' };
var obj3 = {name: 'obj', age: 20, sex: 1};
[强制] 行尾不得有多余的空格。
2.2.3 换行
[强制] 每个独立语句结束后必须换行。
[强制] 每行不得超过 120
个字符。
解释:
超长的不可分割的代码允许例外,比如复杂的正则表达式。长字符串不在例外之列。
[强制] 运算符处换行时,运算符必须在新行的行首。
示例:
// good
if (user.isAuthenticated()
&& user.isInRole('admin')
&& user.hasAuthority('add-admin')
|| user.hasAuthority('delete-admin')
) {
// Code
}
var result = number1 + number2 + number3
+ number4 + number5;
// bad
if (user.isAuthenticated() &&
user.isInRole('admin') &&
user.hasAuthority('add-admin') ||
user.hasAuthority('delete-admin')) {
// Code
}
var result = number1 + number2 + number3 +
number4 + number5;
[建议] 不同行为或逻辑的语句集,使用空行隔开,更易阅读。
示例:
// 仅为按逻辑换行的示例,不代表setStyle的最优实现
function setStyle(element, property, value) {
if (element == null) {
return;
}
element.style[property] = value;
}
[建议] 在语句的行长度超过 120
时,根据逻辑条件合理缩进。
示例:
// 较复杂的逻辑条件组合,将每个条件独立一行,逻辑运算符放置在行首进行分隔,或将部分逻辑按逻辑组合进行分隔。
// 建议最终将右括号 ) 与左大括号 { 放在独立一行,保证与 `if` 内语句块能容易视觉辨识。
if (user.isAuthenticated()
&& user.isInRole('admin')
&& user.hasAuthority('add-admin')
|| user.hasAuthority('delete-admin')
) {
// Code
}
// 按一定长度截断字符串,并使用 + 运算符进行连接。
// 分隔字符串尽量按语义进行,如不要在一个完整的名词中间断开。
// 特别的,对于 HTML 片段的拼接,通过缩进,保持和 HTML 相同的结构。
var html = '' // 此处用一个空字符串,以便整个 HTML 片段都在新行严格对齐
+ '<article>'
+ '<h1>Title here</h1>'
+ '<p>This is a paragraph</p>'
+ '<footer>Complete</footer>'
+ '</article>';
// 也可使用数组来进行拼接,相对 `+` 更容易调整缩进。
var html = [
'<article>',
'<h1>Title here</h1>',
'<p>This is a paragraph</p>',
'<footer>Complete</footer>',
'</article>'
];
html = html.join('');
// 当参数过多时,将每个参数独立写在一行上,并将结束的右括号 ) 独立一行。
// 所有参数必须增加一个缩进。
foo(
aVeryVeryLongArgument,
anotherVeryLongArgument,
callback
);
// 也可以按逻辑对参数进行组合。
// 最经典的是 baidu.format 函数,调用时将参数分为“模板”和“数据”两块
baidu.format(
dateFormatTemplate,
year, month, date, hour, minute, second
);
// 当函数调用时,如果有一个或以上参数跨越多行,应当每一个参数独立一行。
// 这通常出现在匿名函数或者对象初始化等作为参数时,如 `setTimeout` 函数等。
setTimeout(
function () {
alert('hello');
},
200
);
order.data.read(
'id=' + me.model.id,
function (data) {
me.attchToModel(data.result);
callback();
},
300
);
// 链式调用较长时采用缩进进行调整。
$('#items')
.find('.selected')
.highlight()
.end();
#### 2.2.4 语句
##### [强制] 不得省略语句结束的分号。
##### [强制] 在 `if / else / for / do / while` 语句中,即使只有一行,也不得省略块 `{...}`。
示例:
```javascript
// good
if (condition) {
callFunc();
}
// bad
if (condition) callFunc();
if (condition)
callFunc();
[强制] 函数定义结束不允许添加分号。
示例:
// good
function funcName() {
}
// bad
function funcName() {
};
// 如果是函数表达式,分号是不允许省略的。
var funcName = function () {
};
[强制] IIFE
必须在函数表达式外添加 (
,非 IIFE
不得在函数表达式外添加 (
。
解释:
IIFE = Immediately-Invoked Function Expression.
额外的 ( 能够让代码在阅读的一开始就能判断函数是否立即被调用,进而明白接下来代码的用途。而不是一直拖到底部才恍然大悟。
示例:
// good
var task = (function () {
// Code
return result;
})();
var func = function () {
};
// bad
var task = function () {
// Code
return result;
}();
var func = (function () {
});
2.3 命名
[强制] 变量
使用 Camel命名法
。
示例:
var loadingModules = {};
[强制] 常量
使用 全部字母大写,单词间下划线分隔
的命名方式。
示例:
var HTML_ENTITY = {};
[强制] 函数
使用 Camel命名法
。
示例:
function stringFormat(source) {
}
[强制] 函数的 参数
使用 Camel命名法
。
示例:
function hear(theBells) {
}
[强制] 类
使用 Pascal命名法
。
示例:
function TextNode(options) {
}
[强制] 类的 方法
/ 属性
使用 Camel命名法
。
示例:
function TextNode(value, engine) {
this.value = value;
this.engine = engine;
}
TextNode.prototype.clone = function () {
return this;
};
[强制] 枚举变量
使用 Pascal命名法
,枚举的属性
使用 全部字母大写,单词间下划线分隔
的命名方式。
示例:
var TargetState = {
READING: 1,
READED: 2,
APPLIED: 3,
READY: 4
};
[强制] 命名空间
使用 Camel命名法
。
示例:
equipments.heavyWeapons = {};
[强制] 由多个单词组成的缩写词,在命名中,根据当前命名法和出现的位置,所有字母的大小写与首字母的大小写保持一致。??????????????????????
示例:
function XMLParser() {
}
function insertHTML(element, html) {
}
var httpRequest = new HTTPRequest();
[强制] 类名
使用 名词
。
示例:
function Engine(options) {
}
[建议] 函数名
使用 动宾短语
。
示例:
function getStyle(element) {
}
[建议] boolean
类型的变量使用 is
或 has
开头。
示例:
var isReady = false;
var hasMoreCommands = false;
3 语言特性
3.1 变量
[强制] 变量、函数在使用前必须先定义。
解释:
不通过 var 定义变量将导致变量污染全局环境。
示例:
// good
var name = 'MyName';
// bad
name = 'MyName';
原则上不建议使用全局变量,对于已有的全局变量或第三方框架引入的全局变量,需要根据检查工具的语法标识。
示例:
/* globals jQuery */
var element = jQuery('#element-id');
[强制] 每个 var
只能声明一个变量。
解释:
一个 var
声明多个变量,容易导致较长的行长度,并且在修改时容易造成逗号和分号的混淆。
示例:
// good
var hangModules = [];
var missModules = [];
var visited = {};
// bad
var hangModules = [],
missModules = [],
visited = {};
[强制] 变量必须 即用即声明
,不得在函数或其它形式的代码块起始位置统一声明所有变量。
解释:
变量声明与使用的距离越远,出现的跨度越大,代码的阅读与维护成本越高。虽然JavaScript的变量是函数作用域,还是应该根据编程中的意图,缩小变量出现的距离空间。
示例:
// good
function kv2List(source) {
var list = [];
for (var key in source) {
if (source.hasOwnProperty(key)) {
var item = {
k: key,
v: source[key]
};
list.push(item);
}
}
return list;
}
// bad
function kv2List(source) {
var list = [];
var key;
var item;
for (key in source) {
if (source.hasOwnProperty(key)) {
item = {
k: key,
v: source[key]
};
list.push(item);
}
}
return list;
}
3.2 条件
[强制] 在 Equality Expression 中使用类型严格的 ===
。仅当判断 null
或 undefined
时,允许使用 == null
。
解释:
使用 ===
可以避免等于判断中隐式的类型转换。
示例:
// good
if (age === 30) {
// ......
}
// bad
if (age == 30) {
// ......
}
[建议] 尽可能使用简洁的表达式。
示例:
// 字符串为空
// good
if (!name) {
// ......
}
// bad
if (name === '') {
// ......
}
// 字符串非空
// good
if (name) {
// ......
}
// bad
if (name !== '') {
// ......
}
// 数组非空
// good
if (collection.length) {
// ......
}
// bad
if (collection.length > 0) {
// ......
}
// 布尔不成立
// good
if (!notTrue) {
// ......
}
// bad
if (notTrue === false) {
// ......
}
[建议] 按执行频率排列分支的顺序。
解释:
按执行频率排列分支的顺序好处是:
- 阅读的人容易找到最常见的情况,增加可读性。
- 提高执行效率。
[建议] 对于相同变量或表达式的多值条件,用 switch
代替 if
。
示例:
// good
switch (typeof variable) {
case 'object':
// ......
break;
case 'number':
case 'boolean':
case 'string':
// ......
break;
}
// bad
var type = typeof variable;
if (type === 'object') {
// ......
}
else if (type === 'number' || type === 'boolean' || type === 'string') {
// ......
}
3.3 循环
[建议] 对循环内多次使用的不变值,在循环外用变量缓存。
示例:
// good
var width = wrap.offsetWidth + 'px';
for (var i = 0, len = elements.length; i < len; i++) {
var element = elements[i];
element.style.width = width;
// ......
}
// bad
for (var i = 0, len = elements.length; i < len; i++) {
var element = elements[i];
element.style.width = wrap.offsetWidth + 'px';
// ......
}
[建议] 对有序集合进行遍历时,缓存 length
。
解释:
虽然现代浏览器都对数组长度进行了缓存,但对于一些宿主对象和老旧浏览器的数组对象,在每次 length
访问时会动态计算元素个数,此时缓存 length
能有效提高程序性能。
示例:
for (var i = 0, len = elements.length; i < len; i++) {
var element = elements[i];
// ......
}
3.4 类型
3.4.1 类型检测
[建议] 类型检测优先使用 typeof
。对象类型检测使用 instanceof
。null
或 undefined
的检测使用 == null
。
示例:
// string
typeof variable === 'string'
// number
typeof variable === 'number'
// boolean
typeof variable === 'boolean'
// Function
typeof variable === 'function'
// Object
typeof variable === 'object'
// RegExp
variable instanceof RegExp
// Array
variable instanceof Array
// null
variable === null
// null or undefined
variable == null
// undefined
typeof variable === 'undefined'
3.4.2 类型转换
[建议] 转换成 string
时,使用 + ''
。
示例:
// good
num + '';
// bad
new String(num);
num.toString();
String(num);
[建议] 转换成 number
时,通常使用 +
。
示例:
// good
+str;
// bad
Number(str);
[建议] string
转换成 number
,要转换的字符串结尾包含非数字并期望忽略时,使用 parseInt
。
示例:
var width = '200px';
parseInt(width, 10);
[强制] 使用 parseInt
时,必须指定进制。
示例:
// good
parseInt(str, 10);
// bad
parseInt(str);
[建议] 转换成 boolean
时,使用 !!
。
示例:
var num = 3.14;
!!num;
[建议] number
去除小数点,使用 Math.floor
/ Math.round
/ Math.ceil
,不使用 parseInt
。
示例:
// good
var num = 3.14;
Math.ceil(num);
// bad
var num = 3.14;
parseInt(num, 10);
3.5 字符串
[强制] 字符串开头和结束使用单引号 '
。
解释:
- 输入单引号不需要按住
shift
,方便输入。 - 实际使用中,字符串经常用来拼接 HTML。为方便 HTML 中包含双引号而不需要转义写法。
示例:
var str = '我是一个字符串';
var html = '<div class="cls">拼接HTML可以省去双引号转义</div>';
3.7 数组
[强制] 遍历数组不使用 for in
。
解释:
数组对象可能存在数字以外的属性, 这种情况下 for in
不会得到正确结果。
示例:
var arr = ['a', 'b', 'c'];
// 这里仅作演示, 实际中应使用 Object 类型
arr.other = 'other things';
// 正确的遍历方式
for (var i = 0, len = arr.length; i < len; i++) {
console.log(i);
}
// 错误的遍历方式
for (var i in arr) {
console.log(i);
}
[建议] 不因为性能的原因自己实现数组排序功能,尽量使用数组的 sort
方法。
解释:
自己实现的常规排序算法,在性能上并不优于数组默认的 sort
方法。以下两种场景可以自己实现排序:
- 需要稳定的排序算法,达到严格一致的排序结果。
- 数据特点鲜明,适合使用桶排。
[建议] 清空数组使用 .length = 0
。
3.9 面向对象
3.10 动态特性
3.10.1 eval
[强制] 避免使用直接 eval
函数。
解释:
直接 eval
,指的是以函数方式调用 eval
的调用方法。直接 eval
调用执行代码的作用域为本地作用域,应当避免。
如果有特殊情况需要使用直接 eval
,需在代码中用详细的注释说明为何必须使用直接 eval
,不能使用其它动态执行代码的方式,同时需要其他资深工程师进行 Code Review。
[建议] 尽量避免使用 eval
函数。
3.10.2 动态执行代码
[建议] 使用 new Function
执行动态代码。
解释:
通过 new Function
生成的函数作用域是全局使用域,不会影响当当前的本地作用域。如果有动态代码执行的需求,建议使用 new Function
。
示例:
var handler = new Function('x', 'y', 'return x + y;');
var result = handler($('#x').val(), $('#y').val());
3.10.3 with
[建议] 尽量不要使用 with
。
解释:
使用 with
可能会增加代码的复杂度,不利于阅读和管理;也会对性能有影响。大多数使用 with
的场景都能使用其他方式较好的替代。所以,尽量不要使用 with
。
3.10.4 delete
[建议] 减少 delete
的使用。
解释:
如果没有特别的需求,减少或避免使用 delete
。delete
的使用会破坏部分 JavaScript 引擎的性能优化。
[建议] 处理 delete
可能产生的异常。
解释:
对于有被遍历需求,且值 null
被认为具有业务逻辑意义的值的对象,移除某个属性必须使用 delete
操作。
在严格模式或 IE 下使用 delete
时,不能被删除的属性会抛出异常,因此在不确定属性是否可以删除的情况下,建议添加 try-catch
块。
示例:
try {
delete o.x;
}
catch (deleteError) {
o.x = null;
}
3.10.5 对象属性
[建议] 避免修改外部传入的对象。
解释:
JavaScript 因其脚本语言的动态特性,当一个对象未被 seal 或 freeze 时,可以任意添加、删除、修改属性值。
但是随意地对 非自身控制的对象 进行修改,很容易造成代码在不可预知的情况下出现问题。因此,设计良好的组件、函数应该避免对外部传入的对象的修改。
下面代码的 selectNode 方法修改了由外部传入的 datasource 对象。如果 datasource 用在其它场合(如另一个 Tree 实例)下,会造成状态的混乱。
function Tree(datasource) {
this.datasource = datasource;
}
Tree.prototype.selectNode = function (id) {
// 从datasource中找出节点对象
var node = this.findNode(id);
if (node) {
node.selected = true;
this.flushView();
}
};
对于此类场景,需要使用额外的对象来维护,使用由自身控制,不与外部产生任何交互的 selectedNodeIndex 对象来维护节点的选中状态,不对 datasource 作任何修改。
function Tree(datasource) {
this.datasource = datasource;
this.selectedNodeIndex = {};
}
Tree.prototype.selectNode = function (id) {
// 从datasource中找出节点对象
var node = this.findNode(id);
if (node) {
this.selectedNodeIndex[id] = true;
this.flushView();
}
};
除此之外,也可以通过 deepClone 等手段将自身维护的对象与外部传入的分离,保证不会相互影响。
[建议] 具备强类型的设计。
解释:
- 如果一个属性被设计为
boolean
类型,则不要使用1
或0
作为其值。对于标识性的属性,如对代码体积有严格要求,可以从一开始就设计为number
类型且将0
作为否定值。 - 从 DOM 中取出的值通常为
string
类型,如果有对象或函数的接收类型为number
类型,提前作好转换,而不是期望对象、函数可以处理多类型的值。
4 浏览器环境
4.1 模块化
4.1.1 AMD
[强制] 使用 AMD
作为模块定义。
解释:
AMD 作为由社区认可的模块定义形式,提供多种重载提供灵活的使用方式,并且绝大多数优秀的 Library 都支持 AMD,适合作为规范。
目前,比较成熟的 AMD Loader 有:
[强制] 模块 id
必须符合标准。
解释:
模块 id 必须符合以下约束条件:
- 类型为 string,并且是由
/
分割的一系列 terms 来组成。例如:this/is/a/module
。 - term 应该符合 [a-zA-Z0-9_-]+ 规则。
- 不应该有 .js 后缀。
- 跟文件的路径保持一致。
4.1.2 define
[建议] 定义模块时不要指明 id
和 dependencies
。
解释:
在 AMD 的设计思想里,模块名称是和所在路径相关的,匿名的模块更利于封包和迁移。模块依赖应在模块定义内部通过 local require
引用。
所以,推荐使用 define(factory)
的形式进行模块定义。
示例:
define(
function (require) {
}
);
[建议] 使用 return
来返回模块定义。
解释:
使用 return 可以减少 factory 接收的参数(不需要接收 exports 和 module),在没有 AMD Loader 的场景下也更容易进行简单的处理来伪造一个 Loader。
示例:
define(
function (require) {
var exports = {};
// ...
return exports;
}
);
4.1.3 require
[强制] 全局运行环境中,require
必须以 async require
形式调用。
解释:
模块的加载过程是异步的,同步调用并无法保证得到正确的结果。
示例:
// good
require(['foo'], function (foo) {
});
// bad
var foo = require('foo');
[强制] 模块定义中只允许使用 local require
,不允许使用 global require
。
解释:
- 在模块定义中使用
global require
,对封装性是一种破坏。 - 在 AMD 里,
global require
是可以被重命名的。并且 Loader 甚至没有全局的require
变量,而是用 Loader 名称做为global require
。模块定义不应该依赖使用的 Loader。
[强制] Package 在实现时,内部模块的 require
必须使用 relative id
。
解释:
对于任何可能通过 发布-引入 的形式复用的第三方库、框架、包,开发者所定义的名称不代表使用者使用的名称。因此不要基于任何名称的假设。在实现源码中,require
自身的其它模块时使用 relative id
。
示例:
define(
function (require) {
var util = require('./util');
}
);
[建议] 不会被调用的依赖模块,在 factory
开始处统一 require
。
解释:
有些模块是依赖的模块,但不会在模块实现中被直接调用,最为典型的是 css
/ js
/ tpl
等 Plugin 所引入的外部内容。此类内容建议放在模块定义最开始处统一引用。
示例:
define(
function (require) {
require('css!foo.css');
require('tpl!bar.tpl.html');
// ...
}
);
4.2 DOM
4.2.1 元素获取
[建议] 对于单个元素,尽可能使用 document.getElementById
获取,避免使用document.all
。
[建议] 对于多个元素的集合,尽可能使用 context.getElementsByTagName
获取。其中 context
可以为 document
或其他元素。指定 tagName
参数为 *
可以获得所有子元素。
[建议] 遍历元素集合时,尽量缓存集合长度。如需多次操作同一集合,则应将集合转为数组。
解释:
原生获取元素集合的结果并不直接引用 DOM 元素,而是对索引进行读取,所以 DOM 结构的改变会实时反映到结果中。
示例:
<div></div>
<span></span>
<script>
var elements = document.getElementsByTagName('*');
// 显示为 DIV
alert(elements[0].tagName);
var div = elements[0];
var p = document.createElement('p');
docpment.body.insertBefore(p, div);
// 显示为 P
alert(elements[0].tagName);
</script>
[建议] 获取元素的直接子元素时使用 children
。避免使用childNodes
,除非预期是需要包含文本、注释和属性类型的节点。
4.2.2 样式获取
[建议] 获取元素实际样式信息时,应使用 getComputedStyle
或 currentStyle
。
解释:
通过 style 只能获得内联定义或通过 JavaScript 直接设置的样式。通过 CSS class 设置的元素样式无法直接通过 style 获取。
4.2.3 样式设置
[建议] 尽可能通过为元素添加预定义的 className 来改变元素样式,避免直接操作 style 设置。
[强制] 通过 style 对象设置元素样式时,对于带单位非 0 值的属性,不允许省略单位。
解释:
除了 IE,标准浏览器会忽略不规范的属性值,导致兼容性问题。
4.2.4 DOM 操作
[建议] 操作 DOM
时,尽量减少页面 reflow
。
解释:
页面 reflow 是非常耗时的行为,非常容易导致性能瓶颈。下面一些场景会触发浏览器的reflow:
- DOM元素的添加、修改(内容)、删除。
- 应用新的样式或者修改任何影响元素布局的属性。
- Resize浏览器窗口、滚动页面。
- 读取元素的某些属性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE)) 。
[建议] 尽量减少 DOM
操作。
解释:
DOM 操作也是非常耗时的一种操作,减少 DOM 操作有助于提高性能。举一个简单的例子,构建一个列表。我们可以用两种方式:
- 在循环体中 createElement 并 append 到父元素中。
- 在循环体中拼接 HTML 字符串,循环结束后写父元素的 innerHTML。
第一种方法看起来比较标准,但是每次循环都会对 DOM 进行操作,性能极低。在这里推荐使用第二种方法。
4.2.5 DOM 事件
[建议] 优先使用 addEventListener / attachEvent
绑定事件,避免直接在 HTML 属性中或 DOM 的 expando
属性绑定事件处理。
解释:
expando 属性绑定事件容易导致互相覆盖。
[建议] 使用 addEventListener
时第三个参数使用 false
。
解释:
标准浏览器中的 addEventListener 可以通过第三个参数指定两种时间触发模型:冒泡和捕获。而 IE 的 attachEvent 仅支持冒泡的事件触发。所以为了保持一致性,通常 addEventListener 的第三个参数都为 false。
网友评论