引言

文中引用的参考若打不开,可借助于虫部落.

之前记了一段时间代码规范,写代码时也尽量去遵循了许多.现在再次拿出来翻一翻,养成个更好的习惯,不会因为看起来乱糟糟(实际上也容易有许多低级或者说语法错误)的代码而降低效率.这里我推荐的是百度ecomfe团队共同遵循的一个spec,今天这篇文章主要以html, css, less, js为主,有关less的规范可以适当延伸到其他css预处理器(其实我还是喜欢stylus,因为less有许多bug比如a标签的嵌套,二期 stylus 对于我来说更简洁...).其他的规范:JS-ESNext, E-JSON, 模块和加载器, 包结构, 项目目录, 图标库标准接下来有关代码规范的第二篇文章再来说说除了不太熟悉的图表库标准,之外的其他6个规范.
规范听起来好像特别固定,但是这东西其实挺灵活的,只不过我觉得百度ecomfe推出来的规范建立在许多大神的多年代码经验上,强制部分不会有问题都应该遵守(而且大部分规范其实都建立在填坑的基础上,都是有科学依据的),建议部分大家可以尽量遵守,也可以不太在乎.当然如果有多年码代码的经验,自己搞一套自己用得来的规范在公司一起让大家用也行,如果只有自己一个人用的话那就不好了...如果私下用自己的规范在公司一起用另一套,那一不小心就弄混了.
总之写代码不遵守一定的规范的话,会容易导致许多问题,慢慢地会积累的越来越多.

正文

在进入这三个spec之前,有一些通用的规范:

  • 使用4个空格做缩进层级,不允许使用两个空格或者tab字符(像ST还有其他editor都有相关设置可以使得你敲下tab也没关系,保存文件时可以帮你把tab转换成空格),下面是我ST的preferences.sublime:

{
    "caret_style": "phase",
    "color_scheme": "Packages/User/SublimeLinter/Mac Classic (SL).tmTheme",
    "default_encoding": "UTF-8",
    "default_line_ending": "unix",
    "detect_indentation": false,
    "draw_white_space": "all",
    "font_face": "Courier New",
    "font_size": 14.0,
    "highlight_line": true,
    "highlight_modified_tabs": true,
    "ignored_packages":
    [
        "",
        "Markdown"
    ],
    "line_numbers": true,
    "open_files_in_new_window": false,
    "rulers":
    [
        80,
        100
    ],
    "tab_size": 4,
    "translate_tabs_to_spaces": true,
    "trim_trailing_white_space_on_save": true
}

其中default_encoding, default_line_ending, detect_indentation, tab_size, translate_tabs_to_spaces对规范的养成有较大作用, draw_white_space, hignlight_line, highlight_modified_tabs, open_files_in_new_window有助于视觉上的辨识和其他的一些方便之处,ST其实除了上述基本设置其实还有许多插件还有快捷键很方便,插件到处有网上那个什么最受欢迎的20个挑着用,以后遇到的不方便说不定搜搜就有插件可以搞定,不急.快捷键的话最近发现Command + Shift + S, Command + P, Command + R...好用.Command + Shift + S和页面的关键信息(比如title等等)有利于快速定位文件,双击Find Results的单项结果可以快速打开, Command + R方便查看js文件内的各个函数, Command + P有利于快速定位(一定目录名下的)文件.这里有ST快捷键的官方doc,这里有ST设置的官方doc,都可以借鉴一下.

  • 为了方便review code,每行字符数不要超过120,就是大概每行行尾字符不超过这两条竖线位置,如下图.当然比较长的 url 或者正则表达式是例外,长字符串不在例外之列,可以用 + 当做行首来换行,连接长字符串.
    图片描述

  • 文件尽量都无 BOM 的采用 UTF-8 编码,因为它具有更广泛的适应性,BOM 在使用程序或工具处理文件时可能造成不必要的干扰.

  • + - * / 等等二元运算符两侧必须保留一个空格.

  • 单行注释尽量采用 //Comment... 形式

html spec

在这部分spec之前, 建议再看看html, css, js公共的spec.

命名

1 所有class小写, 以 - 分隔单词, 工程化开发要求大部分情况下使用class,id用得特别少,命名规则和class一样.两者命名在避免冲突并描述清楚的前提下尽可能简短,例如用nav替代navigation.
2 class语义化,传达出模块或部件的内容或者功能,而不是样式信息.

//good 
<div class="sidebar"></div>

//bad
<div class="left"></div>

3 为了避免ie下的bug,同一页面不同元素避免使用相同的name和id.

<input name="foo" />
<div id="foo"></div>
//will be 'INPUT' in ie6
document.getElementById('foo');

标签

1 无需自闭合的元素比如br, img, input, hr等等, 不要给他加上自闭合.以前我记得是一本书上说加上自闭合更好,可能是这本书年代有点久远,不够正确.所以说年代久远的书稍微看看重点,其中的细节或许有不当之处.这里有个stackover上的回答:are-self-closing-tags-valid-in-html5,很不错.
2 标签的使用要符合嵌套规则,比如div等等块级元素不要置于p里面,tbody必须放在table里面,这里有篇W3Cfuns博客介绍了一些tag嵌套规则.这里有一份HTML DTD文件,其中的ELEMENT部分有嵌套规则;我用chrome打开dtd看着不爽,你们找到可以比较好地查看dtd文件的app可以在评论里说一声,谢谢(不过好像本来就显示成这样...我猜的).

属性

1 必须使用双引号括起来,不能用单引号
2 disabled,checkd等等布尔类型属性,建议不添加属性值.这让我想起了.prop()方法和.()attr方法的差别,大家可以搜搜,或者用自己买的vpn吧.
3 自定义属性建议使用data-作前缀,有助于区分自定义属性和标准属性.

通用

1 使用大写的DOCTYPE来启用标准模式:<!DOCTYPE html>.
2 启用 IE Edge并优先使用最新版IE和chrome内核, 一般来讲这算是pc短最起码的小小的兼容技巧吧,还不说其他的了,别忘了,pc端meta标签中一般都有它.

<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">

3 页面需要指定字符编码的meta必须是head的第一个子元素.因为如果HTTP Headers中没有指定charset,指定charset的meta放在head的第一个子元素位置可以让浏览器今早获取字符编码信息;title必须在charset meta后面,一般是紧随其后,可以优先获取页面标题.因为如果包含ASCII之外的字符,浏览器需要知道字符编码类型才能进行解码,否则可能导致乱码.
4 保证favicon可访问
未指定favicon的话大多数浏览器会请求Web Server根目录下的favicon.icon,为了保证favicon可以访问,可以有以下两种办法:

  • 在Web Server根目录下放置favicon.ico文件

  • 使用link指定favicon

<link rel="shortcut icon" href="path/to/favicon">

5 viewport
如今互联网移动化趋势已成,对移动设备友好已成为每个网页的基本要求,还没有考虑到移动设备间的兼容性问题.

<meta name="viewport" content="user-scalable=no, width=device-width">

<html>
    <head>
        <meta charset="UTF-8">
        <title>页面标题</title>
        ...
    </head>
    <body>
        ...
    </body>
</html>

4 js应当放在页面末尾,或者采用异步加载.
出于性能方面的考虑,如果将script放在页面中间将阻断页面的渲染.
5 图片
禁止img的src取值为空,延迟加载的图片也要增加默认的src,因为src取值为空的话会导致部分浏览器重新加载一次当前页面;避免为img添加不必要的title,会影响看图体验;为重要图片添加alt属性,这样可以提高图片加载失败的用户体验;添加width和height属性,以避免页面抖动;有下载需求的图片采用img标签,无下载需求的图片采用css背景图实现.产品logo,用户头像,用户产生的图片等等有钱在下载需求的图片以img形式实现,如果勇CSS background实现那么无法右键下载.无下载需求的图片,比如icon,背景,代码使用的图片等等尽可能使用css背景图实现.stackoverflow对CSS background-image和img标签做了较为详尽的对比.
6 表单

  • 控件
    有文本标题的控件必须是用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">
  • 按钮
    使用 button 必须指明 type 属性值

button 元素的默认 type 是 submit,吐过被置于 form 元素中,点击后将导致表单提交,必须给出 type 属性.

<button type="submit">submit</button>
<button type="button">cancle</button>

尽量不要使用 button 的 name 属性,因为浏览器兼容性会带来许多难以发现的问题,这里有一篇参考文章;另外负责主要功能的按钮应该靠前,提高可访问性.移动端页面开发时,应该根据内容类型指定 input 元素的 type 属性,这样可以获得友好的输入体验.比如 text, password, date...
7 多媒体
在支持 HTML5 的浏览器仲有先使用 audio 和 video 标签,同时使用退化到插件的方式来进行多浏览器的兼容.在 object 标签内部提供指示浏览器不支持该标签的说明.
8 模板
模板代码的缩进优先保证好 HTML 代码的正常缩进.

css spec

空格

1 选择器与 { 之间必须包含空格.

.selector {
    ...
}

2 属性名与之后的 : 之间不允许包含空格, : 与属性值之间必须包含空格.

margin: 0 auto;

3 列表行属性值数组写在单行时, , 后必须跟一个空格.

font-family: Arial, sans-serif;

选择器

1 当一个 rule 包含多个 selector 时,每个选择器声明必须独占一行.

.post,
.comment {
    line-height: 1.5;
}

2 >, +, ~选择器两边都保留一个空格.
3 属性选择器中的值必须用双引号包围,不允许使用单引号或者不使用引号,类似于HTML 标签中属性值的引号规则.

section[class="intro"] {
    font: 12px;
}

属性

1 属性值定义必须另起一行.

//bad
.selector { margin: 0 auto; padding: 0; }

2 属性定义后必须以分号结尾,不能漏掉.

通用

选择器

1 如果没有必要,不要在 id,class 之前加上元素类型进行限定,这会对性能和维护方面带来一定影响.

//bad
dialog#error,
p.danger-message {
    font: #c00;
}

2 嵌套层级不大于3级,位置靠后的限定条件尽可能准确.

属性缩写

1 尽可能多的使用属性缩写,比如 background(取代单独的 background-image,background-color 属性等等),font(取代单独的 font-size,font-style, font-family 属性等等)...既可以减少代码量,也不会有其他影响, 无属性值列表顺序要求.

//good
.comment {
    //1.5 -- line-height
    font: 12px/1.5 arial, sans-serif;

2 使用 border/margin/padding 等等进行缩写的话,应注意如果有继承的话,可以不考虑缩写,只设置单个属性(比如 border-color)的值,不带来冗余.

同一选择器下的属性书写顺序

这个规范我在 <css权威指南> 上见到过...属性书写时应该按功能进行分组:
布局,位置(formatting model) > 尺寸(box model) > 文本(typographic) > 视觉(visual)
formatting model相关属性包括:
z-index/position/top/right/bottom/left/float/display/overflow...
box model 相关属性包括:
由外到里,margin/border/padding/width/height...
typographic 相关属性包括:
font/line-height/text-align/word-wrap(text styles)...
visual相关属性包括:
background/color/transition/gradient/list-style...
另外,如果包含与伪元素相关的 content 属性应该放在最前面.

清除浮动

当元素需要撑起高度以包含内部浮动元素时,通过对伪类设置 clear 或者触发BFC的方式进行 clearfix.尽量不使用增加空标签的方式.

数值,长度,url

当数值是0 - 1之间的小数时,省略整数部分的0;长度为0时需要省略单位,好像在 <css 权威指南> 当中看到过;url()函数中的路径不加引号,若为绝对路径,则可以省去协议名.

颜色,2D 位置

rgb 颜色值不许使用十六进制,不允许使用 rgb(),rgba()例外;颜色值可以缩写时必须缩写,不允许使用命名色值比如 lightgreen,red...建议颜色值采用小写,不用按住 shift或者 caps lock,方便;
2D 位置需要同时给出水平和垂直方向的位置,初始值是0% 0%.尽管只有一个方向时,另一个方向的值会被解析为 center,但是为了避免记忆和理解麻烦,同时给出最好.

字体
  • 字体族
    font-family 属性中的字体族名称应使用英文.常见名称如下:

字体            操作系统    Family Name
宋体(中易宋体)     Win        SimSun
黑体(中易黑体)     Win        SimHei
微软雅黑          Win        Microsoft Yahei
微软正黑          Win        Microsoft JhengHei
华文黑体          Mac/iOS    STHei
冬青黑体          Mac/iOS    Hiragino SansGB
文泉驿正黑        Linux      WenQuanYi Zen Hei
文泉驿微米黑       Linux     WenQuanYi Micro Hei

西文字体在前中文字体在后;效果佳/质量高/更能满足需求的字体在前,效果一般的字体在后的顺序,最后必须指定一个通用字体族(serif/sans-serif),知乎上有一篇有关字体的讨论很不错.

//display according to platform 
.article {
    font-family: Arial, sans-serif;
}
//specific for most platforms
h1 {
    font-family: "Helvetica Neue", Arial, "Hragino Sans GB", "WenQuanYi Micro Hei", "Microsoft YaHei", sans-serif;
}

font-family 不区分大小写,但是同样的 Family Name 大小写需要保持一致.

  • 字号
    需要在 Windows 平台显示的中文内容,其字号应该不小于12px.由于 window 的字体渲染机制,小于12px的文字显示效果极差,难以辨认.

  • 需要在 Windows 平台显示的中文内容,不要使用 normal 以外的 fon-style,其他平台也慎用.由于 因为中文字体没有 italic 风格的实现,所有浏览器下都会 fallback 到 obilique (自动拟合为斜体),小字号下(特别是 windows 下会在小字号下使用点阵字体)显示效果差,造成阅读困难.

  • 字重
    font-weight 属性必须使用数值,因为 css 自重分100-900九档,但是目前因为字体本身质量和浏览器的限制,实际上支持400和700两档,分别等价于关键词 normal 和 bold.浏览器本身使用一系列规则来进行匹配,在<700时一般匹配字体的 Regular 字重,>=700时匹配 Bold 字重,但已有浏览器开始支持 =600 时匹配 Semibold 字重,故使用数字描述增加了灵活性,详细的font属性整体性介绍可以查看 w3school.

  • line-height
    将其设置为数值,浏览器会基于当前元素设置的 font-size 进行再次计算,不同 fz 的文本段落时都会有较为舒适的行间距.避免在每个设置了 fz 时需要设置 lh;lh 还可以用于控制垂直居中.

  • 变换与动画
    使用 transition 时应指定 transition-property

//good 
.box {
    transition: color 1s, border-color 1s;
}
//bad
.box {
    transition: all 1s;
}

尽可能在浏览器上能高效实现的属性上添加过渡和动画,选择这四种变换:

transform: translate(npx, npx);
transform: scale(n);
transform: rotate(ndeg);
opacity: 0...1;

e.g. 可以使用 translate 来代替 left 作为动画属性:

//good
.box {
    transition: transform 1s;
}
.box:hover {
    //move right for 20px
    transform: translate(20px);
}
//bad
.box {
    left: 0;
    transition: left 1s;
}
.box:hover {
    //move right for 20px
    left: 20px;
}
兼容性
  • 带私有前缀的属性由长到短排列,按冒号位置对齐.
    标准属性放在最后,按冒号对齐方便阅读,也方便多行快捷编辑.

less spec

代码组织

  1. @import

  2. 变量声明

  3. 样式声明

//good 
@import "../common/base.less"

@primary-color: #333;

.container {
    margin: 0 auto;
    width: 1080px;
    color: @primary-color;
}

运算,Mixin

    • 两侧的操作数必须有相同的单位,如果其中一个是变量,另一个数值必须书写单位.

//good
@a: 200px;
@b: (@a + 100px) * 2;

Mixin 和后面的括号之间不得包含空格.

.box {
    .size(30px, 20px);
    .clearfix ();
}

定义Mixin混入时,如果 mixin 名称不是一个需要使用的 className,必须加上括号,否则即使不被调用也会输出到 css 中,在 stylus 中就不用考虑这种情况.

//good 
.big-text() {
    font-size: 2em;
}

h3 {
    .big-text();
}

hack

尽量避免使用 css hack,需要时也要使用简单的 hack 名如_zoom,*margin...
谨慎使用 IE 滤镜,IE 滤镜中图片的 url 是以页面路径为基准计算目录,而不是以 css 文件路径为基准.

继承

如果在声明块内书写:extend 语句,那么必须写在开头

//good
.sub {
    &:extend(.mod all);
    color: #f00;
}

js spec

文件,结构

1 在文件结尾处,保留一个空行
2 switch 下的 case 和 default 必须增加一个缩进层级.

空格

1 与二元运算符不同,一元运算符操作对象之间不允许有空格.

//good
var a = !arr.length;
a++;
var b = 0;
c = a + b;

2 用作代码块开头的左花括号 { 前必须有一个空格
3 if/else/for/while/function/switch/do/try/catch/finally 关键字后必须有一个空格.
4 在对象创建时, 属性中的 : 之后必须有空格, : 之前不允许有空格.
5 函数声明,具名函数表达式,函数调用中,函数名和 ( 之间不允许有空格.

//good
function funcName() {
}

var a = function funcName() {
};

funcName();

6 , 和 ; 前不允许有空格,如果不位于行尾,他俩之后必须紧跟一个空格.
7 在函数调用,函数声明,括号表达式,属性访问,if/for/while/switch/catch,()和[]内紧贴括号部分不允许有空格.
8 单行声明的数组与对象,如果包含元素,{}和[]内紧贴括号部分不允许有空格.
声明包含元素的数组与对象,只有当内部元素的形式较为简单时,才允许写在一行,元素复杂的情况下还是应该换行书写.

换行

1 运算符处换行, 运算符必须在新行行首(相比于行尾的位置显眼舒服地多).
2 在函数声明,函数表达式,函数调用,对象创建,数据创建,for 语句等等场景中,不允许在,或者;前面换行.
3 不同行为或者逻辑的语句集,使用空行隔开更易阅读.
4 语句长度超过120时,根据逻辑条件合理缩进.
较复杂的逻辑条件组合,将每个条件独立一行,逻辑运算放置在行首进行分隔,或者将部分逻辑组合进行分隔.建议将右括号 ( 与左大括号 } 放在独立一行,保证与if内语句块能容易视觉辨识.

if (user.isAuth()
    && user.isInRole('admin')
    && user.hasAuth('add-admin')
    && user.hasAuth('delete-admin')
){
    ...
}

var html = '' //此处用一个空字符串,以便整个html片段都在新行严格对齐.
    + '<article>'
    +     '<h1>Title here</h1>'
    +     '<p>This is a paragraph</p>'
    +     '<footer>Complete</footer>'
    + '</article>';
//也可以使用数组进行拼接,由于少了html字符串开头的空字符串''以及每个`+`后的空格,所以相对`+`更容易调整缩进.
var html = [
    '<article>',
        '<h1>Title here</h1>',
        '<p>This is a paragraph</p>',
        '<footer>Complete</footer>',
    '</article>'
];
html = html.join('');
//可以按逻辑对参数进行组合,比如baidu.format 函数,调用时将参数分为"模板"和"数据
两块.baidu.format(
    dataFormatTemplate,
    year, month, date, hour, minute, second
);
//当函数调用时,如果有一个以上参数跨越多行时,应当每一个参数独立一行.
//这通常出现在匿名函数或者对象初始化等等作为参数时,如'setTimeout'函数等.
setTimeout(
    function () {
        alert('hello');
    },
    200
);
//数组和对象初始化的混用,严格按照每个对象的 `{` 和结束 `}`在独立一行的风格书写.
var arr1 = [
    {
        ...    
    },
    {
        ...
    }
];

对于 if...else..., try...catch...finally 等等语句中,推荐使用在 } 后面添加一个换行的风格,使代码层次更加清晰,阅读性更好.

if (condition) {
    ...some statements
}
else {
    ...some statements
}

try {
    //some statements
}
catch (ex) {
    //some statements
}
finally {
    //always do me
}

To be honestly, I always thought:

if (condition) {
    ...
} else (another condition){
    ...
}

is better and I remember it was also recommended in one book but now...I changed the idea.

语句
  • 函数定义结束不允许添加分号

//bad
function funcName() {
};
//good
//如果是函数表达式,分号不允许省略
var funcName = function () {
};
  • IIFE(Immediatyle-Invoked Function Expression)必须在表达式外添加 (,非 IIFE 不得在函数表达式外添加 (.

命名

  • 常量和枚举的属性全部大写,单词间下划线分隔.

var HTML_ENTITY = {};

var TargetState = {
    READING: 1,
    READED: 2,
    APPLIED: 3,
    READY: 4
};
  • 变量,函数,函数参数,类的方法/属性使用 Camel 命名法

var loadingModules = {};

function stringFormat(source) {
    ...
}

function write(fontUsed, wordsNum) {
    ...
}

function TextNode(value, engine) {
    this.value = value;
    this.engine = engine;
}
TextNode.prototype.clone = function () {
    return this;
}
  • 类,枚举变量使用 Pascal 命名法

function TextNode(value, engine) {
    this.value = value;
    this.engine = engine;
}

var TargetState = {
    READING: 1,
    READED: 2,
    APPLIED: 3,
    READY: 4
};
  • 类名使用名词,函数名使用动宾短语, boolean 类型的变量使用 is 或者 has 开头.Promise 对象使用动宾短语的进行时表达.

function Engine(options) {
    ...
}
function getStyle(element) {
    ...
}
var isReady = false;
var hasMoreCommands = false;

var loadingData = ajax.get('url');
loadingData.then(callback);

注释

普通注释
  • 尽量使用单行注释, 避免使用/.../这样的多行注释,有多行注释时,使用多个单行注释.

  • //后跟一个空格,缩进与下一行被注释说明的代码一致

文档化注释
  • 为了便于代码阅读和自文档化,以下内容必须包含以/*.../形式的块注释中:
    1.文件 2.namespace 3.类 4.函数或者方法 5.类属性 6.事件 7.全局变量 8.常量 9.AMD 模块

  • 文档注释前必须空一行

  • 文档注释说明 what, 而不是 how.

类型定义
  • 以 { 开始,以 } 结束.常用的以{string}, {number}, {boolean}, {Object}, {Function}, {RegExp}, {Array}, {Date}...

  • 基本类型{string},{number},{boolean}首字母必须小写.

文件注释
  • 文件顶部必须包含文件注释, 用@file 标识文件说明

  • 用 @author 标识开发者信息,多人开发时按责任进行排序,@author 是后续解决 bug, 代码维护的依据,新增需求需要新增代码时,自己的名字应该放在创建者前面.

语法特性

变量
  • 变量必须即用即声明,不得在函数或者其他形式的代码会起始位置统一声明所有变量.
    因为,变量声明与使用的距离越远,出现的跨度越大,代码的阅读与维护成本越高.索然 js 的变量是函数作用域,还是应该尽量缩小变量出现的距离空间.

//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;
}
条件
  • 在等式判断中使用类型严格的 === ,可以避免隐式的类型转换.仅当判断 null 或者 undefined 时,允许使用 == null.

  • 尽可能使用简洁的表达式

//字符串非空
//good
if (!name) {
    ...
}
//bad
if (name !== '') {
    ...
}

//数组非空
//gooood
if (collection.length) {
    ...
}
//bad
if (collection.length > 0) {
    ...
}

//null or undefined
//good
if (noValue == null) {
    ...
}
//bad
if (noValue === null || typeof noValue )
    ...
}
  • 按执行频率排列分支
    好处:1. 容易找到最常见的情况,增加可读性;2. 提高执行效率

  • 对于相同变量或者表达式的多值条件,用 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') {
    ...
}
循环
  • 不要在循环体中包含函数表达式,事先将函数提取到循环体外.
    循环体中的函数表达式,运行过程中会生成循环次数个函数对象.所以说,两个字,性能.

//good
function clicker() {
    ...
}

for (var i = 0, lent = elements.length; i < leng; i++) {
    var element = elements[i];
    addListener(element, 'click', clicker);
}

//bad 
for (var i = 0, len =elements.length; i < len; i++) {
    var element = elements[i];
    addListener(element, 'click', function () {});
}
  • 对循环内多次使用的不变量,在循环外用变量缓存.

  • 对有序集合进行遍历时,缓存 length
    虽然现代浏览器都对数组长度进行了缓存,但对于一些宿主对象和老旧浏览器的数组对象,在每次 length 访问时会动态计算元素个数,此时缓存 length 能有效提高程序性能.

  • 对有序集合进行顺序无关的遍历时,使用逆序遍历.
    可以节省变量,代码比较优化.

var len = elements.length;
while (len--) {
    var element = elements[len];
    ...
}
类型
类型检测
  • 优先使用 typeof,对象类型检测使用instanceOf. null or undefined 的检测使用 ==null

  • string 转换成 number,要转换的字符串结尾包含非数字并期望忽略时,使用 parseInt.

var width = '200px';
parseInt(width, 10);
  • 使用 parseInt时,必须制定进制

  • number 去除小数点,,使用 Math.floor/Math.round/Math.ceil,不使用 parseInt.

字符串
  • 字符串开头和结束使用单引号'
    因为:

  1. 输入单引号不需要按住 shift,方便输入.

  2. 字符串经常用来拼接 HTML,为了方便 HTML 中包含双引号而不需要转义写法.
    我之前一直用单引号,但是中途还以为自己用错了,又用回双引号.直到现在又走回正轨.

  • 使用数组或者 + 拼接字符串.

  1. 使用 + 拼接字符串,如果拼接的全部是StringLiteral,压缩工具可以对其进行自动合并的优化.所以,静态字符串建议使用 + 拼接.

  2. 在现代浏览器下, 使用 + 拼接字符串,性能较数组的方式要高.

  3. 如果需要兼顾老旧浏览器,应尽量使用数组拼接字符串.

//使用数组拼接字符串
var str = [
    //推荐换行开始并缩进开始第一个字符串,队医代码,方便阅读
    '<ul>',
        '<li>第一项</li>',
        '<li>第二项</li>',
    '</ul>'
].join('');

//使用 `+` 拼接字符串
var str2 = ''//建议第一个为空字符串,第二个换行开始并缩进开始,对齐代码,方便阅读.
    + '<ul>'
    +     '<li>第一项</li>'
    +     '<li>第二项</li>'
    + '</ul>';
对象
  • 使用对象字面量 {} 创建新 Object.

//good 
var obj = {};

//bad 
var obj = new Object();
  • 对象创建时,如果一个对象的所有属性均可以不添加引号,那么所有属性不添加引号;如果任何一个属性需要添加单引号,则所有属性都添加.

  • 不允许修改和扩展任何原生对象和宿主对象的原型.

//bad 
String.prototype.trim = function () {
    ...          
};
  • 属性访问时,尽量使用.
    属性名符合 Camel命名法,用 . 来访问更清晰简洁.如果是来自后端的 JSON 数据,那么可以通过[expr]形式来访问.

  • for in 遍历对象时,使用 hasOwnProperty 过滤掉原型中的属性.

var newInfo = {};
for (var key in info) {
    if (info.hasOwnProperty(key)) {
        newInfo[key] = info[key];
    }
}
数组
  • 使用数组字面量[]创建新数组,除非想要创建的是指定长度的数组.

  • 遍历数组不使用 for in.
    数组可能存在数字以外的属性,这种情况下 for in 不会得到正确的结果.

  • 清空数组使用 .length = 0.

函数
函数长度

50行以内,如果将过多的逻辑单元混在一个大函数中,容易导致难以维护.一个清晰易懂的函数应该完成单一的逻辑单元.复杂的操作应做进一步抽取,通过函数的调用来体现流程.

函数参数

控制在6个以内.

空函数
  • 不使用 new Function() 的形式
    var emptyFunction = function () {};

  • 对于性能又高要求的场合,建议存在一个空函数的常量,供多处使用共享.

var EMPTY_FUNCTION = function () {};
function MyClass () {
    …
}

MyClass.prototype.abstractMethod = EMPTY_FUNCTION;
MyClass.prototype.hooks.before = EMPTY_FUNCTION;
MyClass.prototype.hooks.after = EMPTY_FUNCTION;
面向对象
  • 类的继承方案,实现时需要修正constructor.
    通过使用其他 library 的继承方案都会进行 constructor 修正.如果是自己实现的类继承方案,需要修正 constructor.

/**
 * 构建类之间的继承关系
 * 
 * @param {Function} subClass 子类函数
 * @param {Function} superClass 父类函数
 */
function inherits(subClass, superClass) {
    var F = new Function();
    F.prototype = superClass.prototype;
    subClass.prototype = new F();
    subClass.prototype.constructor = subClass;
}
  • 声明类时,保证 constructor 的正确性.

function Animal(name) {
    this.name = name;
}

//直接 prototype 等于对象时,需要修正 constructor.
Animal.prototype = {
    constructor: Animal,

    jump: function () {
        alert(‘animal ’ + this.name + ‘ jump’);
    }
};

//这种方式扩展 prototype则无需理会 constructor
Animal.prototype.jump = function () {
    alert(‘animal ‘ + this.name + ‘ jump’);
};
  • 属性在构造函数中声明,方法在原型中声明.
    原型对象的成员被所有实例共享,能节约内存占用.所以编码时我们应该遵守这样的原则:原型对象包含程序不会修改的成员,如方法函数或者配置项.

  • 自定义事件的事件名必须全小写.
    在 js 广泛应用的浏览器环境,绝大多数 DOM 事件名称都是全小写的.

一个事件对象的好处有:

  1. 顺序无关,避免事件监听者需要记忆参数顺序.

  2. 每个事件信息都可以根据需要提供或者不提供,更自由。

  3. 扩展方便,未来添加事件信息时,无需考虑会破坏监听器参数形式而无法向后兼容。

  • 设计自定义事件时,应考虑禁止默认行为。
    常见禁止默认行为的方式有两种:

  1. 事件监听函数中 return false.

  2. s事件对象中包含禁止默认行为的方法,如 preventDefault.

动态特性
eval

尽量避免使用 eval 函数

使用new Function 执行动态代码

通过 new Function 生成的函数作用域是全局使用域,不会影响当前的本地作用域。如果有动态代码执行的需求,建议使用 new Function。

var handler = new Function('x', 'y', 'return x + y');
var result = handler($('#x').val(), $('#y').val());
with

尽量不要使用 with,因为可能会增加代码复杂度,不利于阅读和管理;也会对性能有影响.

浏览器环境

模块化
AMD
  • 使用 AMD 作为模块定义
    AMD 提供多种重载提供灵活的使用方式,并且绝大多数优秀的 Library 都支持 AMD.目前,比较成熟的 AMD Loader 有:require 和 esl.

  • 模块 id 必须符合标准:

  1. 类型为 string,并且是由 / 分割的一系列 terms 来组成.比如this/is/a/module

  2. term 应该符合 [a-zA-Z0-9_-]+规则.

  3. 不应该有.js后缀.
    4.跟文件的路径保持一致.

define
  • 定义模块时不要指明 id 和 dependencies.
    在 AMD 设计思想里,模块名称是和所在路径相关的,匿名的模块更利于封包和迁移.模块依赖应再模块定义内部通过 local require 引用.

DOM
元素获取
  • 对于单个元素,尽可能使用document.getElementById 获取,避免使用document.all

  • 对于多个元素的集合,尽可能使用context.getElementsByTagName获取.context可以是 document 或其他元素.指定 tagName参数是*可以获得所有子元素.

  • 遍历元素集合时,尽量缓存集合长度.如需多次操作同一集合,应将集合转为数组.
    原生获取集合的结果并不直接引用 DOM 元素,而是对索引进行读取,所以 DOM 结构的改变会实时反映到结果中.

<div></div>
<span></span>

<script>
var elements = document.getElementsByTagName('*');

//display DIV
alert(elements[0].tagName);

var div = elements[0];
var p = document.createElement('p');
document.body.insertBefore(p, div);

//display P 
alert(elements[0].tagName)l
</script>
样式获取
  • 应该使用 getComputedStyle 或者 currentStyle.
    解释:

通过 style 只能获得内联定义或通过 js 直接设置的样式.通过 CSS class 设置的元素样式无法直接通过 style 获取.

样式设置
  • 尽可能通过为元素添加预定义的 className 来改变元素样式,避免直接操作 style设置.

  • 通过 style对象设置元素样式时,对于带单位非0值的属性,不允许省略单位.
    除了 IE,标准浏览器会忽略不规范的属性值,导致兼容性问题.

DOM 操作
  • 操作 DOM 时,尽量减少页面 reflow.
    页面 reflow 非常耗时,容易带来性能瓶颈.下面的场景会触发 reflow:

  1. DOM 元素的添加,修改,删除.

  2. 应用新的样式或者修改任何影响元素布局的属性

  3. Resize浏览器窗口,滚动页面

  4. 读取元素的某些属性(offsetLeft, offsetTop, offsetHeight, offsetWidth, scrollTop/Left/Width/Height, clientTop/Left/Width/Height, getComputedStyle(), currentStyle(in IE))

  • 尽量减少 DOM 操作
    因为也非常耗时.e.g.构建一个列表,可以用两种方式:

  1. 在循环体中 createElement并 append 到父元素中

  2. 在循环体中拼接 HTML 字符串,循环结束后写父元素的 innerHTML.
    第一种方法看起来比较标准,但是每次循环都会对 DOM 进行操作.性能极低.

DOM 事件
  • 优先使用 addEventListener/attachEvent 绑定事件,避免直接在 HTML 属性中或者 DOM 的 expando 属性绑定事件处理.
    expando属性绑定事件容易导致相互覆盖.

  • 使用addEventListener时第三个参数使用 false.
    标准浏览器中的 addEventListener可以通过第三个参数指定两种时间触发模型:冒泡和捕获.而 ie 的 attachEvent 仅支持冒泡的事件触发,所以为了保持一致性,通常 addEventListener 的第三个参数都为 false.

代码规范基础篇到此为止

南赐
125 声望4 粉丝

大多数情况下会在segmentfault上面活跃,日后主攻小程序,storybook + vue 组件开发,前端测试,持续集成,node.js,eggjs等等。期待与大家深入交流技术~


« 上一篇
SVG初体验