前端代码规范
该文于 2013 年创作,经年修改,录以记之
统一的规范有助于团队合作开发,但规范又臭又长,又不利于阅读与遵守,所以本规范尽量压缩。另外,本规范只是草案,还需要在实践中不断完善。
一、全局规范
tab or space
考虑到各代码编辑器的差异,前端统一采用 4 个空格 作为缩进标准。
设置 4 个空格后,向前删除空格时就会很痛苦,可在编辑器中,设置按下退格键时,一次删掉前面的四个空格。
Sublime Text 是这样设置的:
"tab_size": 4,
"translate_tabs_to_spaces": true,
VIM 是这样设置的:
set autoindent softtabstop=4 shiftwidth=4 expandtab
其他编辑器请自行摸索。
UNIX \n or Windows \r\n
统一使用 UNIX 的换行符
文件编码
统一使用不带 BOM 字节的 UTF-8 编码
路径和文件名
- 不要在路径和文件名中使用空格
- view/css/js/img 文件,统一采用陀峰风格,请不要用 - 或 _ 连接各个单词,更不要不区分单词,一律小写
二、HTML 书写规范
秉承 HTML5 规范与精神,结合 XHTML,制定本规范。
基本页面
采用如下模板:
<!doctype html>
<html lang='zh-CN'>
<head>
<meta charset='utf-8'>
<title>page title</title>
</head>
<body>
</body>
</html>
-
doctype 采用 html5 建议的 doctype,废弃 xhtml 的 doctype
-
charset 采用 html5 建议的 charset,不要使用:
<meta http-equiv="charset" content="utf-8">
-
charset 使用 utf-8
标签闭合
xhtml 规定,标签必须闭合,对没有内容的标签,也要写一个 /,例如:
<input type='text' name='username'/>
<br/>
html5 拨乱反正,回归 html 本质,所以,我们这样写:
<input type='text' name='username'>
<br>
疯狂的 html5 甚至允许这样:
<table>
<tr>
<th>header1
<th>header2
<tr>
<td>cell11
<td>cell12
<tr>
<td>cell21
<td>cell22
<p>content out of table, no table close tag before
<p>second paragraph
我们不要这么疯狂,这样会弄乱我们的代码,所以,对于没有内容的标签,我们不必用 / 关闭,对于有内容的标签,我们仍然遵从 xhtml 的要求
属性和属性值
html5 允许对属性值不加任何引号,但我们不要这么做,我们还是要加引号的,引号可以用单引号,也可以用双引号,推荐使用单引号,因为输入双引号时,要同时按 Shift 键,例如:
<meta charset='utf-8'>
<input type='text' name='username'>
<div class='page cl'>
没有值的属性
罗嗦的 xhtml 规定,对没有值的属性这样处理:
<input type='radio' checked='checked'/>
但接受 html5 鼓励的我们,应该这样写:
<input type='radio' checked>
常见标签简洁写法
省略 script/link 标签的 type 属性,这不是必需的,例如:
罗嗦的写法:
<script src='...' type='text/javascript'></script>
<link href='...' type='text/css' rel='stylesheet'>
简洁的写法:
<script src='...'></script>
<link href='...' rel='stylesheet'>
标签顺序
head 标签内的标签顺序,推荐:
<meta>
<title>
<link>
script 标签的位置
<script>
应放置在 </body>
前,而不是 <head>
内,放在 <head>
内的脚本会阻塞页面,导致页面性能下降
类名和 ID
- 类名使用
-
分割单词,不要使用_
或陀峰命名法 - ID 使用陀峰命名法,不要使用
-
或_
例如:
不当的类名和 ID
<div class='userBox' id='user_box'>
<div class='user_box' id='user-box'>
恰当的类名和 ID
<div class='user-box' id='userBox'>
三、CSS 书写规范
类名和 ID
-
永远不要使用 ID 进行样式化,只使用类名,例如:
// 不当的样式 #userBox { ... } // 良好的样式,使用类名 .user-box { ... }
类名用于样式化,ID 用于脚本访问
格式风格
不当的格式风格和推荐的格式风格对比:
// 不当的,{ 应放在类名后,而不是重启一行
.user-box
{
...
}
// 应该这样:
.user-box {
...
}
// 不当的样式
.user-box{
...
}
// 应该这样,{ 与类名之间应该有空格
.user-box {
...
}
// 不当的,样式不应写在一行:
.user-box { font-size: 14px; line-height: 2; }
// 应该这样:
.user-box {
font-size: 14px;
line-height: 2;
}
// 不当的样式
font-size:14px;
// 应该这样,键和值应以空格隔开
font-size: 14px;
// 不当的,最后一行样式也应以 ; 结束
.user-box {
font-size: 14px;
line-height: 2
}
// 应该这样:
.user-box {
font-size: 14px;
line-height: 2;
}
// 不当的样式
.user-list a, .news-list a {
...
}
// 应该这样,每个 selector 占一行
.user-list a,
.news-list a {
...
}
层级不可过多
// 不当,层级过多
.user-box .user-list .user-item .user-avatar {
...
}
// 应该减少层级,这样能提高 CSS 性能
.user-box .user-avatar {
...
}
// 采用命名空间的情况下,最好一级
.user-avatar {
...
}
// 这种情况下,即 .class tag,可以两级
.user-avatar a {
...
}
// 最长不要超过三级
使用命名空间
多使用 namespace-*
形式的类名,每个业务场景定义一个前缀作为命名空间,如用 site- 作为全站的命名空间,用 user- 作为用户中心的命名空间,用 news- 作为咨询的命名空间:
.site-top
.user-nav
.news-left
针对 tag 的样式
-
永远不要针对 div/span/p 的样式,这些标签太通用:
// 不当的 .user-box div .news-box span .article-list p
-
可以对 em strong 等含有丰富语义的标签应用样式,但不要嵌套
// 恰当的 .user-list em { ... } // 不当的,进行了多级嵌套 .user-list li em { ... } // 最不当的,单独的 tag 样式是魔鬼 // 合理的 css reset 除外,全站应有且只有一个统一的 css reset em { ... }
-
无论如何,少用 tag 样式
注释
-
less
中允许使用//
注释,而css
语法中不允许,只能使用/**/
四、JS 书写规范
; 号的使用
-
虽然很多正规的教程,都要求每条 js 语句都应以
;
结束,但这里,我们选择所有的语句省略 -
;
浪费了键盘输入,窃以为是 js 中的糟粕,不用纠结,大胆省略吧 -
有时,省略
;
会导致代码出错,可加一个前置;
,例如var str = '' ; [1, 2, 3].forEach(function(n) { console.log(n) })
代码风格
// 所有的二元运算符前后应有空格
var str = 'hi, ' + name
// 函数定义与 { 应处于同一行,并以空格分割
function lineTo(from, to) {
...
}
// 应尽量避免写 while 与 do .. while 循环,用 for 替代
// 可以尝试用 ECMAScript 5 新规范新增的 Array 方法,彻底替代循环结构,这些方法有:
// Array.prototype.forEach
// Array.prototype.map
// Array.prototype.every
// Array.prototype.some
// Array.prototype.filter
// Array.prototype.reduce
// Array.prototype.reduceRight
// 这些方法的使用,符合函数式理念,一旦习惯,就会爱上这种方式
// for 与 (,for 内的 ; 之间,) 与 { 都应有空格
for (var i = 1; i <= 9; i++) {
...
}
// if 与 for 同理,else 应与结束的 } 处于同一行
if (sex == 'girl') {
...
} else if (sex == 'boy') {
...
} else {
...
}
// 始终在 if/else/for 中使用 {},那怕只有一条语句
if (condition) {
// only one statement
return true
}
// switch 的 case 应该这样缩进,default 应始终在最后
switch (sex) {
case 'boy':
....
break;
case 'girl':
....
break;
default:
...
}
// 尽量避免使用 switch 语句,可以采用这样的结构
var map = {
boy: ...,
girl: ...
}
var val = map[sex]
// 甚至可以用 map 执行函数
var actionMap = {
doThis: function() {
...
},
doThat: function() {
...
}
}
// 函数调用与 ( 之间不应有空格,以便和 for if 语句加以区分
lineTo(5, 6)
// 永远不要这样写
var arr = new Array()
var obj = new Object()
// 应这样写
var arr = []
var obj = {}
// 一行一个变量,应该这样
var v1 = 1
var v2 = 2
// 避免这样
var v1 = 1, v2 = 2
// 变量名和函数名采用陀峰样式,不要使用 _,如
var myVar = 22
// 尽量使用 ES6 的语法,前提是环境支持(或 webpack + babel 编译支持,或原生环境支持)
尽量保持一个出口
很多人这样写代码:
function someFunc(...) {
if (aCondition) {
...
return aValue
}
if (bCondition) {
...
return bValue
}
...
return cValue
}
这样的代码,说实话,在我眼里,很拙劣,无论你有千般理由!什么性能啦、逻辑清晰啦,都是你对代码没有掌控力的借口!
不要纠结于局部的、指令级的性能,你不是在写汇编!
多处 return,你是爽了,但这种代码,很容易出现维护性问题。
我宁愿多用嵌套 if ... else,始终保持一个出口,函数采用三段式(第一段前置全函数变量声明、第二段逻辑处理、第三段返回)来写,例如:
function someFunc(...) {
var result
if (...) {
if (...) {
...
} else {
...
}
} else {
...
}
return result
}
如果嵌套过多,可以将之提取出来,放到一个新函数里,并且,给这个新函数,取一个含义明确的名字,这样代码就能实现“自注释”,例如:
function someFunc(...) {
var result
if (...) {
result = newFuncWithWellDescription(...)
} else {
...
}
return result
}
function newFuncWithWellDescription(...) {
...
}
并且,应在 else 里处理例外情况(错误处理),在 if 里处理正常流程。
不要怕函数多了影响性能,记住,你不是在写汇编,局部的、指令级的性能优化是没有意义的,系统整体的性能优化,才是重点
变量就地声明,声明的同时进行赋值
有些人这样写:
function someFunc(items) {
var i, len = items.length
...
for (i = 0; i < len; i++) {
...
}
...
}
这样写没有任何好处,i 只是一个循环变量,没有必要像 C 语言那样作前置声明,应该直接这样:
function someFunc(items) {
...
for (var i = 0, len = items.length; i < len; i++) {
...
}
...
}
变量就地声明并赋值,赋值后保持不变,不仅仅符合函数式编程思想,也是一种良好的编程习惯
嵌套函数
嵌套函数应该这样写:
function doSomething(...) {
...
var doSomethingInner = function(...) {
...
}
...
}
而不应该这样:
function doSomething(...) {
...
function doSomethingInner(...) {
...
}
...
}
要牢记,在 JS 这类函数式语言中,函数也是值
在 seajs 中,所有的函数都声明在 define(function() { ... }) 内,也即,所有的函数都是嵌套函数,但这里,我们忽略 seajs 这一级,将 seajs 内的第一级函数,当成顶级函数,例如:
define(function(require) {
...
function someFunc(...) {
...
}
...
})
而不要这样:
define(function(require) {
...
var someFunc = function(...) {
...
}
...
})
佛说,不要着相
五、函数式编程
js 本质上是函数式编程语言,受当时面向对象思潮的影响,也沾染上一些面向对象的糟粕。让我们去芜存菁,回归函数式本质。
关于函数式,可通过搜索引擎进行学习,这里只谈函数式的一个很重要的特性:不变性。
不变性
简单理解,一个变量,一旦定义并赋值(定义时同时赋值,是一个良好的编程习惯),其值应保持不变。
看以下代码:
function onSuccess(data) {
data = parseJson(data)
if (data.s == 200) {
...
}
}
上述代码存在的问题是,data 的值在第一行发生了变化!即使在命令式编程语言中,这种代码也是很拙劣的!
一般,稍微有经验的程序员,无论是否受到过函数式编程思想的影响,都不会写出这样的代码。多声明一个变量会死呀?应该这样:
function onSuccess(data) {
var json = parseJson(data)
if (json.s == 200) {
...
}
}
再看以下代码:
function doubleIt(items) {
for (var i = 0, len = items.length; i < len; i++) {
items[i] = items[i] * 2
}
return items
}
很多命令式编程语言的程序员,觉得上述代码没什么,甚至会告诉你,这样节省内存
上述代码不符合函数式精神,items 中的元素,仍然发生了变化,这样写:
function doubleIt(items) {
var newItems = []
for (var i = 0, len = items.length; i < len; i++) {
newItems[i] = items[i] * 2
}
return newItems
}
上述代码,只有循环变量 i 在变,并且局限在局部。更彻底地,我们使用 Array.prototype.map:
function doubleIt(items) {
return items.map(function(item) {
return item * 2
})
}
回忆高中时,数学中关于函数的定义:函数是一个集合到另一个集合!这样的代码,才算是,真正回归了,编程语言的数学本质
不要担心函数调用的性能,现代的 JS 引擎,会对这样的调用进行优化
JS 性能点不在这里,而在 DOM 操作。一个 DOM 操作所耗时间,约是 JS 对象操作的 1000 倍以上,所以应该尽量减少 DOM 操作
六、补充材料
解读ECMAScript[1]——执行环境、作用域及闭包
解读ECMAScript[2]——函数、构造器及原型
闭包漫谈(从抽象代数及函数式编程角度)
CSS 相对绝对定位系列(一)
SeaJS 官方文档
LESS 中文文档
使用SeaJS实现模块化JavaScript开发
七、拥抱 ES6+
从现在开始,重视并书写 ES6+,这里有个很好的教程 http://es6.ruanyifeng.com/