前端开发规范
1. 手册说明
1.1 概述
为了提升团队凝聚力,统一团队代码风格,优化团队协作效率,需要推出专门的《前端开发规范手册》来约束同一项目不同程序员的代码风格。
本规范旨在为前端程序的开发者提供规范化最新的指导,可用于程序员个人编译环境以及研发团队集成环境等场合的代码规范化检查。
不管有多少人共同参与同一项目,一定要确保每一行代码都像是同一个人编写的
1.2 手册约定
约束等级 | 约束效力 | 强制性 |
---|---|---|
【强制】 | 违反该项将被认为代码存在严重缺陷 | 前端程序团队必须遵守 |
【推荐】 | 违反该项将被认为代码存在轻微缺陷 | 根据具体产品特性的不同,选择性地遵守 |
【参考】 | 违反该项可被认为代码存在优化空间 | 从产品持续优化及人员技能提升的角度,参考使用 |
2. 基本原则
2.1 结构、样式、行为分离
尽量确保文档和模板只包含 HTML
结构,样式都放到样式表里,行为都放到脚本里。
2.2 文件规范
-
html
,css
,js
,images
文件均归档至约定的目录中。 -
html
文件命名: 英文命名, 后缀.html
或.htm.
同时将统一页面文件放于同目录中, 以方便后端添加功能时查找对应页面。 -
css
文件命名: 英文命名, 后缀.css
共用base.css
, 首页index.css
, 其他页面依实际模块需求命名。 -
Js
文件命名: 英文命名, 后缀.js
共用common.js
, 其他依实际模块需求命名。
2.3 命名规范
2.3.1 命名严谨性
代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。 说明:正确的英文拼写和语法可以让开发者易于理解,避免歧义。注意,即使纯拼音命名方式也要避免采用,杜绝完全不规范的缩写,避免望文不知意。
- CODELF (unbug.github.io) 变量命名网站 (现在暂时不可用,后续可能修复)
2.3.2 项目命名
使用 kebab-case 命名,小写形式,中横线分隔
例子 :cloud-teach-client
2.3.3 目录命名
全部采用小写或者 PascalCase(驼峰命名法) 方式命名,有负数目录结构时,要采用复数命名 , 缩写不需要采用负数
例子: styles 、components 、images 、layouts 、utils 、 api 、router
2.3.4 组件文件命名
在 Vue 中 .vue文件命名使用 PascalCase 杜绝完全不规范的缩写,避免望文不知意。
2.4 缩进
- [强制] 统一
Tab
缩进(需设置1个Tab
为2个空格数),不要使用Tab
或者Tab
、空格混搭。
2.5 文件编码
使用不带 BOM
的 UTF-8 编码。
- 在 HTML中指定编码
<meta charset="utf-8">
; - 无需使用
@charset
指定样式表的编码,它默认为UTF-8
(参考 @charset);
2.6 标签和属性一律使用小写字母
<!-- 推荐 -->
<img src="google.png" alt="Google">
<!-- 不推荐 -->
<A HREF="/">Home</A>
/* 推荐 */
color: #e5e5e5;
/* 不推荐 */
color: #E5E5E5;
2.7 省略外链资源 URL 协议部分
省略外链资源(图片及其它媒体资源)URL 中的 http
/ https
协议,使 URL 成为相对地址,避免Mixed Content 问题,减小文件字节数。
其它协议(ftp
等)的 URL 不省略。
<!-- 推荐 -->
<script src="//www.w3cschool.cn/statics/js/autotrack.js"></script>
<!-- 不推荐 -->
<script src="http://www.w3cschool.cn/statics/js/autotrack.js"></script>
/* 推荐 */
.example {
background: url(//www.google.com/images/example);
}
/* 不推荐 */
.example {
background: url(http://www.google.com/images/example);
}
2.8 统一注释
通过配置编辑器,可以提供快捷键来输出一致认可的注释模式。
2.8.1 HTML 注释
-
模块注释
<!-- 文章列表列表模块 --> <div class="article-list"> ... </div>
-
区块注释
<!-- @name: Drop Down Menu @description: Style of top bar drop down menu. @author: deyu(deyu@gmail.com) -->
2.8.2 CSS注释
组件块和子组件块以及声明块之间使用一空行分隔,子组件块之间三空行分隔;
/* ==========================================================================
组件块
============================================================================ */
/* 子组件块
============================================================================ */
.selector {
padding: 15px;
margin-bottom: 15px;
}
/* 子组件块
============================================================================ */
.selector-secondary {
display: block; /* 注释*/
}
.selector-three {
display: span;
}
2.8.3 JavaScript注释
- 单行注释
必须独占一行。//
后跟一个空格,缩进与下一行被注释说明的代码一致。
- 多行注释
避免使用 /*...*/
这样的多行注释。有多行注释内容时,使用多个单行注释。
- 函数/方法注释
- 函数/方法注释必须包含函数说明,有参数和返回值时必须使用注释标识;
- 参数和返回值注释必须包含类型信息和说明;
- 当函数是内部函数,外部不可访问时,可以使用 @inner 标识;
/**
* 函数描述
*
* @param {string} p1 参数1的说明
* @param {string} p2 参数2的说明,比较长
* 那就换行了.
* @param {number=} p3 参数3的说明(可选)
* @return {Object} 返回值描述
*/
function foo(p1, p2, p3) {
let p3 = p3 || 10;
return {
p1: p1,
p2: p2,
p3: p3
};
}
- 文件注释
文件注释用于告诉不熟悉这段代码的读者这个文件中包含哪些东西。 应该提供文件的大体内容, 它的作者, 依赖关系和兼容性信息。如下:
/**
* @fileoverview Description of file, its uses and information
* about its dependencies.
* @author user@meizu.com (Firstname Lastname)
* Copyright 2015 Meizu Inc. All Rights Reserved.
*/
2.9 编辑器配置
将你的编辑器按照下面的配置进行设置,以避免常见的代码不一致和差异:
- 制表符
tab
设置为2
个空格数。 - 保存文件时,删除尾部的空白符。
- 设置文件编码为 UTF-8。
- 在文件结尾添加一个空白行。
参照文档并将这些配置信息添加到项目的 .editorconfig
文件中。
3. HTML 规范
尽量遵循 HTML 标准和语义,但是不要以牺牲实用性为代价。任何时候都要尽量使用最少的标签并保持最小的复杂度。
3.1 通用规定
3.1.1 标签
-
[强制] 标签名必须使用小写字母。
<!-- good --> <p>Hello StyleGuide!</p> <!-- bad --> <P>Hello StyleGuide!</P>
-
[强制] 对于无需自闭合的标签,不允许自闭合 ( 例如:
img
input
br
hr
等 );<!-- good --> <input type="text" name="title"> <!-- bad --> <input type="text" name="title" />
-
[强制] 可选的闭合标签(closing tag),需闭合 ( 例如:
</li>
或</body>
);<!-- good --> <ul> <li>first</li> <li>second</li> </ul> <!-- bad --> <ul> <li>first <li>second </ul>
-
[推荐] 尽量减少标签数量。
<!-- 不推荐 --> <span class="avatar"> <img src="..."> </span> <!-- 推荐 --> <img class="avatar" src="...">
3.1.2 Class 与 ID
- [强制]
class
必须代表相应模块或部件的内容或功能,不得以样式信息进行命名。 - [强制] class 与 id 单词全字母小写,多个单词组成时,采用中划线
-
分隔。 - 使用唯一的 id 作为 Javascript hook, 同时避免创建无样式信息的 class。
<!-- 不推荐 -->
<div class="j-hook left contentWrapper"></div>
<!-- 推荐 -->
<div id="j-hook" class="sidebar content-wrapper"></div>
-
[强制] 禁止为了
hook 脚本
,创建无样式信息的class
。不允许
class
只用于让 JavaScript 选择某些元素,class
应该具有明确的语义和样式。否则容易导致 CSS class 泛滥。使用
id
、属性选择作为 hook 是更好的方式。
3.1.3 属性
-
[强制] 属性名必须使用小写字母。
<!-- 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>
-
[强制] HTML 属性应该按照特定的顺序出现以保证易读性。
- id
- class
- name
- data-xxx
- src, for, type, href
- title, alt
-
aria-xxx, role
<a id="..." class="..." data-modal="toggle" href="###"></a> <input class="form-control" type="text"> <img src="..." alt="...">
- **[建议] 布尔类型的属性,建议不添加属性值。**
```html
<input type="text" disabled>
<input type="checkbox" value="1" checked>
-
[建议] 自定义属性建议以
xxx-
为前缀,推荐使用data-
。使用前缀有助于区分自定义属性和标准定义的属性。
<ol data-ui-type="Select"></ol>
3.1.4 引号
属性的定义,统一使用双引号。
<!-- 不推荐 -->
<span id='j-hook' class=text>Google</span>
<!-- 推荐 -->
<span id="j-hook" class="text">Google</span>
3.1.5 嵌套
a 不允许嵌套 div
这种约束属于语义嵌套约束,与之区别的约束还有严格嵌套约束,比如a 不允许嵌套 a
。
严格嵌套约束在所有的浏览器下都不被允许;而语义嵌套约束,浏览器大多会容错处理,生成的文档树可能相互不太一样。
详细的标签嵌套规则参见 HTML标签嵌套规则。
3.1.5.1 语义嵌套约束
<li>
用于 <ul>
或 <ol>
下;
<dd>
, <dt>
用于 <dl>
下;
<thead>
, <tbody>
, <tfoot>
, <tr>
, <td>
用于 <table>
下;
3.1.5.2 严格嵌套约束
- 行内(inline-Level) 元素,仅可以包含文本或其它行内( inline-Level )元素;
<a>
里不可以嵌套交互式元素<a>
、<button>
、<select>
等;<p>
里不可以嵌套块级元素<div>
、<h1>~<h6>
、<p>
、<ul>/<ol>/<li>
、<dl>/<dt>/<dd>
、<form>
等。
3.1.6 布尔值属性
HTML5 规范中 disabled
、checked
、selected
等属性不用设置值。
<input type="text" disabled>
<input type="checkbox" value="1" checked>
<select>
<option value="1" selected>1</option>
</select>
3.1.7 CSS 和 JavaScript 引入
-
[强制] 引入
CSS
时必须指明rel="stylesheet"
。<link rel="stylesheet" href="page.css">
-
[建议] 引入
CSS
和JavaScript
时无须指明type
属性。 -
[建议] 在
head
中引入页面需要的所有CSS
资源。在页面渲染的过程中,新的CSS可能导致元素的样式重新计算和绘制,页面闪烁。
-
[建议]
JavaScript
应当放在页面末尾,或采用异步加载。将
script
放在页面中间将阻断页面的渲染。出于性能方面的考虑,如非必要,请遵守此条建议。
3.2 语义化
没有 CSS
的 HTML
是一个语义系统而不是 UI 系统。
通常情况下,每个标签都是有语义的,各自有对应的功能和含义。
此外语义化的 HTML
结构,有助于机器(搜索引擎)理解,另一方面多人协作时,能迅速了解开发者意图。
3.2.1 常见标签语义
标签 | 语义 |
---|---|
<p> |
段落 |
<h1> <h2> <h3> ... |
标题 |
<ul> |
无序列表 |
<ol> |
有序列表 |
<blockquote> |
大段引用 |
<cite> |
一般引用 |
<b> |
为样式加粗而加粗 |
<strong> |
为强调内容而加粗 |
<i> |
为样式倾斜而倾斜 |
<em> |
为强调内容而倾斜 |
code |
代码标识 |
abbr |
缩写 |
3.2.2 示例
将你构建的页面当作一本书,将标签的语义对应的其功能和含义;
- 书的名称:
<h1>
- 书的每个章节标题:
<h2>
- 章节内的文章标题:
<h3>
- 小标题/副标题:
<h4> <h5> <h6>
- 章节的段落:
<p>
3.3 HEAD
3.3.1 文档类型
-
为每个 HTML 页面的第一行添加标准模式(standard mode)的声明, 这样能够确保在每个浏览器中拥有一致的表现。
<!DOCTYPE html>
3.3.2 语言属性
[建议] 在 html
标签上设置正确的 lang
属性。
为什么使用 lang="zh-cmn-Hans" 而不是我们通常写的 lang="zh-CN" 呢? 请参考知乎上的讨论: 网页头部的声明应该是用 lang="zh" 还是 lang="zh-cn"?
<!-- 中文 -->
<html lang="zh-Hans">
<!-- 简体中文 -->
<html lang="zh-cmn-Hans">
<!-- 繁体中文 -->
<html lang="zh-cmn-Hant">
<!-- English -->
<html lang="en">
3.3.3 字符编码
- [建议]
HTML
文件使用无BOM
的UTF-8
编码。 - [强制] 指定字符编码的
meta
必须是head
的第一个直接子元素;
<html>
<head>
<meta charset="utf-8">
......
</head>
<body>
......
</body>
</html>
3.3.4 IE 兼容模式
优先使用最新版本的IE 和 Chrome 内核
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
3.3.5 SEO 优化
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<!-- SEO -->
<title>Style Guide</title>
<meta name="keywords" content="your keywords">
<meta name="description" content="your description">
<meta name="author" content="author,email address">
</head>
3.3.6 title
-
[强制] 页面必须包含
title
标签声明标题。 -
[强制]
title
必须作为head
的直接子元素,并紧随charset
声明之后。<head> <meta charset="UTF-8"> <title>页面标题</title> </head>
3.3.7 viewport
viewport
: 一般指的是浏览器窗口内容区的大小,不包含工具条、选项卡等内容;width
: 浏览器宽度,输出设备中的页面可见区域宽度;device-width
: 设备分辨率宽度,输出设备的屏幕可见宽度;initial-scale
: 初始缩放比例;maximum-scale
: 最大缩放比例;
为移动端设备优化,设置可见区域的宽度和初始缩放比例。
<meta name="viewport" content="width=device-width, initial-scale=1.0">
3.3.8 iOS 图标
- apple-touch-icon 图片自动处理成圆角和高光等效果;
- apple-touch-icon-precomposed 禁止系统自动添加效果,直接显示设计原图;
<!-- iPhone 和 iTouch,默认 57x57 像素,必须有 -->
<link rel="apple-touch-icon-precomposed" href="/apple-touch-icon-57x57-precomposed.png">
<!-- iPad,72x72 像素,可以没有,但推荐有 -->
<link rel="apple-touch-icon-precomposed" href="/apple-touch-icon-72x72-precomposed.png" sizes="72x72">
<!-- Retina iPhone 和 Retina iTouch,114x114 像素,可以没有,但推荐有 -->
<link rel="apple-touch-icon-precomposed" href="/apple-touch-icon-114x114-precomposed.png" sizes="114x114">
<!-- Retina iPad,144x144 像素,可以没有,但推荐有 -->
<link rel="apple-touch-icon-precomposed" href="/apple-touch-icon-144x144-precomposed.png" sizes="144x144">
关于Apple设备私有的apple-touch-icon属性详解
3.3.9 favicon
在未指定 favicon 时,大多数浏览器会请求 Web Server 根目录下的 favicon.ico 。为了保证 favicon 可访问,避免404,必须遵循以下两种方法之一:
- 在 Web Server 根目录放置 favicon.ico 文件;
- 使用 link 指定 favicon;
<link rel="shortcut icon" href="path/to/favicon.ico">
3.3.10 HEAD 模板
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Style Guide</title>
<meta name="description" content="不超过150个字符">
<meta name="keywords" content="">
<meta name="author" content="name, email@gmail.com">
<!-- 为移动设备添加 viewport -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- iOS 图标 -->
<link rel="apple-touch-icon-precomposed" href="/apple-touch-icon-57x57-precomposed.png">
<link rel="alternate" type="application/rss+xml" title="RSS" href="/rss.xml" />
<link rel="shortcut icon" href="path/to/favicon.ico">
</head>
3.4 图片
-
[强制] 禁止
img
的src
取值为空。延迟加载的图片也要增加默认的src
。src
取值为空,会导致部分浏览器重新加载一次当前页面。 -
[建议] 避免为
img
添加不必要的title
属性。多余的
title
影响看图体验,并且增加了页面尺寸。 -
[建议] 为重要图片添加
alt
属性。可以提高图片加载失败时的用户体验。
-
[建议] 添加
width
和height
属性,以避免页面抖动。 -
[建议] 有下载需求的图片采用
img
标签实现,无下载需求的图片采用 CSS 背景图实现。- 产品 logo、用户头像、用户产生的图片等有潜在下载需求的图片,以
img
形式实现,能方便用户下载。 - 无下载需求的图片,比如:icon、背景、代码使用的图片等,尽可能采用 CSS 背景图实现。
- 产品 logo、用户头像、用户产生的图片等有潜在下载需求的图片,以
3.5 表单
3.5.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">
- 将控件置于
3.5.2 按钮
-
[强制] 使用
button
元素时必须指明type
属性值。button
元素的默认type
为submit
,如果被置于form
元素中,点击后将导致表单提交。为显示区分其作用方便理解,必须给出type
属性。<button type="submit">提交</button> <button type="button">取消</button>
3.6 多媒体
-
[建议] 当在现代浏览器中使用
audio
以及video
标签来播放音频、视频时,应当注意格式。音频应尽可能覆盖到如下格式:
- MP3
- WAV
- Ogg
视频应尽可能覆盖到如下格式:
- 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>
4. CSS 规范
4.1 通用约定
4.1.1 代码组织
- 以组件为单位组织代码段;
- 制定一致的注释规范;
组件块和子组件块
以及声明块
之间使用一空行分隔,子组件块
之间三空行分隔;- 如果使用了多个 CSS 文件,将其按照组件而非页面的形式分拆,因为页面会被重组,而组件只会被移动;
良好的注释是非常重要的。请留出时间来描述组件(component)的工作方式、局限性和构建它们的方法。不要让你的团队其它成员 来猜测一段不通用或不明显的代码的目的。
提示:通过配置编辑器,可以提供快捷键来输出一致认可的注释模式。
/* ==========================================================================
组件块
============================================================================ */
/* 子组件块
============================================================================ */
.selector {
padding: 15px;
margin-bottom: 15px;
}
/* 子组件块
============================================================================ */
.selector-secondary {
display: block; /* 注释*/
}
.selector-three {
display: span;
}
4.1.2 Class 和 ID
-
使用语义化、通用的命名方式;
-
使用连字符 - 作为 ID、Class 名称界定符,不要驼峰命名法和下划线;
-
避免选择器嵌套层级过多,尽量少于 3 级;
-
避免选择器和 Class、ID 叠加使用;
出于性能考量,在没有必要的情况下避免元素选择器叠加 Class、ID 使用。
元素选择器和 ID、Class 混合使用也违反关注分离原则。如果HTML标签修改了,就要再去修改 CSS 代码,不利于后期维护。
/* 不推荐 */
.red {}
.box_green {}
.page .header .login #username input {}
ul#example {}
/* 推荐 */
#nav {}
.box-video {}
#username input {}
#example {}
4.1.3 命名规则常用单词
头:header
内容:content/container
尾:footer
导航:nav
侧栏:sidebar
栏目:column
页面外围控制整体布局宽度:wrapper
页面主体:main
登录条:loginbar
标志:logo
广告:banner
热点:hot
新闻:news
下载:download
子导航:subnav
菜单:menu
子菜单:submenu
搜索:search
友情链接:friendlink
页脚:footer
版权:copyright
滚动:scroll
内容:content
标签页:tab
文章列表:list
提示信息:msg
小技巧:tips
栏目标题:title
加入:joinus
指南:guide
服务:service
注册:regsiter
状态:status
投票:vote
合作伙伴:partner
4.1.4 声明块格式
-
选择器分组时,保持独立的选择器占用一行;
/* 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 */ .selector { margin: 0; padding: 0; } /* bad */ .selector { margin: 0; padding: 0; }
-
[强制] 每行不得超过
120
个字符,除非单行不可分割。常见不可分割的场景为URL超长。
-
[建议] 对于超长的样式,在样式值的
空格
处或,
后换行,建议按逻辑分组。/* 不同属性值按逻辑分组 */ background: transparent url(aVeryVeryVeryLongUrlIsPlacedHere) no-repeat 0 0; /* 可重复多次的属性,每次重复一行 */ background-image: url(aVeryVeryVeryLongUrlIsPlacedHere) url(anotherVeryVeryVeryLongUrlIsPlacedHere); /* 类似函数的属性值可以根据函数调用的缩进进行 */ background-image: -webkit-gradient( linear, left bottom, left top, color-stop(0.04, rgb(88,94,124)), color-stop(0.52, rgb(115,123,162)) );
-
声明块的左括号
{
前添加一个空格; -
声明块的右括号
}
应单独成行; -
声明语句中的
:
后应添加一个空格; -
声明语句应以分号
;
结尾; -
一般以逗号分隔的属性值,每个逗号后应添加一个空格;
-
rgb()
、rgba()
、hsl()
、hsla()
或rect()
括号内的值,逗号分隔,但逗号后不添加一个空格; -
对于属性值或颜色参数,省略小于 1 的小数前面的 0 (例如,
.5
代替0.5
;-.5px
代替-0.5px
); -
十六进制值应该全部小写和尽量简写,例如,
#fff
代替#ffffff
; -
避免为 0 值指定单位,例如,用
margin: 0;
代替margin: 0px;
;
/* 不推荐 */
.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
}
/* 推荐 */
.selector,
.selector-secondary,
.selector[type="text"] {
padding: 15px;
margin-bottom: 15px;
background-color: rgba(0,0,0,.5);
box-shadow: 0 1px 2px #ccc, inset 0 1px 0 #fff;
}
4.1.5 属性缩写
-
[建议] 在可以使用缩写的情况下,尽量使用属性缩写。
/* good */ .post { font: 12px/1.5 arial, sans-serif; } /* bad */ .post { font-family: arial, sans-serif; font-size: 12px; line-height: 1.5; }
-
[建议] 使用
border
/margin
/padding
等缩写时,应注意隐含值对实际数值的影响,确实需要设置多个方向的值时才使用缩写。border
/margin
/padding
等缩写会同时设置多个属性的值,容易覆盖不需要覆盖的设定。如某些方向需要继承其他声明的值,则应该分开设置。/* centering <article class="page"> horizontally and highlight featured ones */ article { margin: 5px; border: 1px solid #999; } /* good */ .page { margin-right: auto; margin-left: auto; } .featured { border-color: #69c; } /* bad */ .page { margin: 5px auto; /* introducing redundancy */ } .featured { border: 1px solid #69c; /* introducing redundancy */ }
4.1.6 属性书写顺序
[建议] 同一 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
等
.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;
}
4.1.7 引号使用
url()
、属性选择符、属性值使用双引号。
/* 不推荐 */
@import url(//www.google.com/css/maia.css);
html {
font-family: 'open sans', arial, sans-serif;
}
/* 推荐 */
@import url("//www.google.com/css/maia.css");
html {
font-family: "open sans", arial, sans-serif;
}
.selector[type="text"] {
}
4.1.8 文本编排
4.1.8.1 字体族
-
[强制]
font-family
属性中的字体族名称应使用字体的英文Family Name
,其中如有空格,须放置在引号中。字体 操作系统 Family Name 宋体 (中易宋体) Windows SimSun 黑体 (中易黑体) Windows SimHei 微软雅黑 Windows Microsoft YaHei 微软正黑 Windows Microsoft JhengHei 华文黑体 Mac/iOS STHeiti h1 { font-family: "Microsoft YaHei"; }
-
[强制]
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; }
4.1.8.2 字号
[强制] 需要在 Windows 平台显示的中文内容,其字号应不小于 12px
。
由于 Windows 的字体渲染机制,小于 12px
的文字显示效果极差、难以辨认。
4.1.9 媒体查询(Media query)的位置
-
**将媒体查询放在尽可能相关规则的附近。不要将他们打包放在一个单一样式文件中或者放在文档底部。如果你把他们分开了,将来只会被大家遗忘。 **
.element { ... } .element-avatar { ... } .element-selected { ... } @media (max-width: 768px) { .element { ...} .element-avatar { ... } .element-selected { ... } }
-
[强制]
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 */ }
4.1.10 不要使用@import
与 <link>
相比,@import
要慢很多,不光增加额外的请求数,还会导致不可预料的问题。
替代办法:
- 使用多个 元素;
- 通过 Sass 或 Less 类似的 CSS 预处理器将多个 CSS 文件编译为一个文件;
- 其他 CSS 文件合并工具;
参考: 不要使用@import
4.1.11 链接的样式顺序
a:link -> a:visited -> a:hover -> a:active(LVHA)
4.1.12 !important
-
[建议] 尽量不使用
!important
声明。 -
[建议] 当需要强制指定样式且不允许任何场景覆盖时,通过标签内联和
!important
定义样式。必须注意的是,仅在设计上
确实不允许任何其它场景覆盖样式
时,才使用内联的!important
样式。通常在第三方环境的应用中使用这种方案。
4.1.13 无需添加浏览器厂商前缀
使用 Autoprefixer 自动添加浏览器厂商前缀,编写 CSS 时不需要添加浏览器前缀,直接使用标准的 CSS 编写。
Autoprefixer 通过 Can I use,按兼容的要求,对相应的 CSS 代码添加浏览器厂商前缀。
4.2 BEM命名规范
CSS 的命名规范又叫做BEM规范,为的是结束混乱的命名方式,达到一个语义化的CSS命名方式。
BEM是三个单词的缩写:Block(块)代表更高级别的抽象或组件,Element(元素) Block的后代,以及Modifier(修饰) 不同状态的修饰符。
4.2.1 命名方法
.block__element--modifier {
display: flex;
}
.block--modifier {
display: flex;
}
.block__element {
display: flex;
}
<p class="header">
<p class="header__body">
<button class="header__button--primary"></button>
<button class="header__button--default"></button>
</p>
</p>
4.2.2 Block
block 代表一个更高级别的抽象或者是一个组件,它仅仅作为一个边界。它主要的功能有下面三点:
- 负责描述功能的,不应该包含状态。
/* good */
.header {
}
/* bad */
.header--select {
}
- 不影响自身布局,不包含具体的样式,也就是block里面不应该加样式
/* good */
.header {
}
/* bad */
.header {
margin-top: 50px;
}
- 不能使用元素选择器和ID选择器
/* good */
.header {
}
/* bad */
.header a {
margin-top: 50px;
}
4.2.3 Element
- 是用一个双下划线隔开
/* good */
.header__body {
margin-top: 50px;
}
/* bad */
.header .body {
margin-top: 50px;
}
- 表示的是目的,而不是状态,如下例子:目的是在header下面定义三个区域 body、logo、title,但是并没有指定任何状态。
.header__body {
margin-top: 50px;
}
.header__logo {
margin-top: 50px;
}
.header__title {
margin-top: 50px;
}
- 不能脱离Block父级单独使用
/* good */
<p class="header">
<p class="header__body">
<button class="header__button--primary"></button>
<button class="header__button--default"></button>
</p>
</p>
/* bad */
<p>
<p class="header__body">
<button class="header__button--primary"></button>
<button class="header__button--default"></button>
</p>
</p>
4.2.4 Modifier
- 表示的是一个状态,是用双横杠分开的。
.header__button--default {
background: none;
}
- Boolean
.header__button--select {
background: none;
}
- 枚举
.header__button--primary {
background: #329FD9;
}
- 不能单独使用
/* correct */
<p class="header">
<p class="header__body">
<button class="header__button--primary"></button>
<button class="header__button--default"></button>
</p>
</p>
/* wrong */
<p>
<p>
<button class="header__button--primary"></button>
<button class="header__button--default"></button>
</p>
</p>
4.3 Less 规范
4.3.1 代码组织
代码按以下顺序组织:
@import
- 变量声明
- 样式声明
@import "mixins/size.less";
@default-text-color: #333;
.page {
width: 960px;
margin: 0 auto;
}
4.3.2 @import
语句
@import
语句引用的文需要写在一对引号内,.less
后缀不得省略。引号使用 '
和 "
均可,但在同一项目内需统一。
/* 不推荐 */
@import "mixins/size";
@import 'mixins/grid.less';
/* 推荐 */
@import "mixins/size.less";
@import "mixins/grid.less";
4.3.3 混入(Mixin)
- 在定义
mixin
时,如果mixin
名称不是一个需要使用的 className,必须加上括号,否则即使不被调用也会输出到 CSS 中。 - 如果混入的是本身不输出内容的 mixin,需要在 mixin 后添加括号(即使不传参数),以区分这是否是一个 className。
/* 不推荐 */
.big-text {
font-size: 2em;
}
h3 {
.big-text;
.clearfix;
}
/* 推荐 */
.big-text() {
font-size: 2em;
}
h3 {
.big-text(); /* 1 */
.clearfix(); /* 2 */
}
4.3.4 避免嵌套层级过多
- 将嵌套深度限制在3级。对于超过3级的嵌套,给予重新评估。这可以避免出现过于详实的CSS选择器。
- 避免大量的嵌套规则。当可读性受到影响时,将之打断。推荐避免出现多于20行的嵌套规则出现。
4.3.5 字符串插值
变量可以用类似ruby和php的方式嵌入到字符串中,像@{name}这样的结构: @base-url: "http://assets.fnord.com";
background-image: url("@{base-url}/images/bg.png");
4.4 性能优化
4.4.1 慎重选择高消费的样式
高消耗属性在绘制前需要浏览器进行大量计算:
- box-shadows
- border-radius
- transparency
- transforms
- CSS filters(性能杀手)
4.4.2 避免过分重排
当发生重排的时候,浏览器需要重新计算布局位置与大小,更多详情。
常见的重排元素:
- width
- height
- padding
- margin
- display
- border-width
- position
- top
- left
- right
- bottom
- font-size
- float
- text-align
- overflow-y
- font-weight
- overflow
- font-family
- line-height
- vertical-align
- clear
- white-space
- min-height
4.4.3 正确使用Display的属性
Display 属性会影响页面的渲染,请合理使用。
- display: inline后不应该再使用 width、height、margin、padding 以及 float;
- display: inline-block 后不应该再使用 float;
- display: block 后不应该再使用 vertical-align;
- display: table-* 后不应该再使用 margin 或者 float;
4.4.4 不滥用 Float
Float在渲染时计算量比较大,尽量减少使用。
4.4.5 动画性能优化
动画的实现原理,是利用了人眼的“视觉暂留”现象,在短时间内连续播放数幅静止的画面,使肉眼因视觉残象产生错觉,而误以为画面在“动”。
动画的基本概念:
- 帧:在动画过程中,每一幅静止画面即为一“帧”;
- 帧率:即每秒钟播放的静止画面的数量,单位是fps(Frame per second);
- 帧时长:即每一幅静止画面的停留时间,单位一般是ms(毫秒);
- 跳帧(掉帧/丢帧):在帧率固定的动画中,某一帧的时长远高于平均帧时长,导致其后续数帧被挤压而丢失的现象。
一般浏览器的渲染刷新频率是 60 fps,所以在网页当中,帧率如果达到 50-60 fps 的动画将会相当流畅,让人感到舒适。
- 如果使用基于 javaScript 的动画,尽量使用 requestAnimationFrame. 避免使用 setTimeout, setInterval.
- 避免通过类似 jQuery animate()-style 改变每帧的样式,使用 CSS 声明动画会得到更好的浏览器优化。
- 使用 translate 取代 absolute 定位就会得到更好的 fps,动画会更顺滑。
4.4.6 多利用硬件能力,如通过3D变形开启GPU加速
一般在 Chrome 中,3D或透视变换(perspective transform)CSS属性和对 opacity 进行 CSS 动画会创建新的图层,在硬件加速渲染通道的优化下,GPU 完成 3D 变形等操作后,将图层进行复合操作(Compesite Layers),从而避免触发浏览器大面积重绘和重排。
注:3D 变形会消耗更多的内存和功耗。
使用 translate3d 右移 500px 的动画流畅度要明显优于直接使用 left:
.ball-1 {
transition: -webkit-transform .5s ease;
-webkit-transform: translate3d(0, 0, 0);
}
.ball-1.slidein{
-webkit-transform: translate3d(500px, 0, 0);
}
.ball-2 {
transition: left .5s ease; left:0;
}
.ball-2.slidein {
left:500px;
}
4.4.7 提升 CSS 选择器性能
CSS 选择器对性能的影响源于浏览器匹配选择器和文档元素时所消耗的时间,所以优化选择器的原则是应尽量避免使用消耗更多匹配时间的选择器。而在这之前我们需要了解 CSS 选择器匹配的机制, 如子选择器规则:
#header > a {font-weight:blod;}
我们中的大多数人都是从左到右的阅读习惯,会习惯性的设定浏览器也是从左到右的方式进行匹配规则,推测这条规则的开销并不高。
我们会假设浏览器以这样的方式工作:寻找 id 为 header 的元素,然后将样式规则应用到直系子元素中的 a 元素上。我们知道文档中只有一个 id 为 header 的元素,并且它只有几个 a 元素的子节点,所以这个 CSS 选择器应该相当高效。
事实上,却恰恰相反,CSS 选择器是从右到左进行规则匹配。了解这个机制后,例子中看似高效的选择器在实际中的匹配开销是很高的,浏览器必须遍历页面中所有的 a 元素并且确定其父元素的 id 是否为 header 。
如果把例子的子选择器改为后代选择器则会开销更多,在遍历页面中所有 a 元素后还需向其上级遍历直到根节点。
#header a {font-weight:blod;}
理解了CSS选择器从右到左匹配的机制后,明白只要当前选择符的左边还有其他选择符,样式系统就会继续向左移动,直到找到和规则匹配的选择符,或者因为不匹配而退出。我们把最右边选择符称之为关键选择器。——更多详情
- 避免使用通用选择器
/* 不推荐 */
.content * {color: red;}
浏览器匹配文档中所有的元素后分别向上逐级匹配 class 为 content 的元素,直到文档的根节点。因此其匹配开销是非常大的,所以应避免使用关键选择器是通配选择器的情况。
- 避免使用标签或 class 选择器限制 id 选择器
/* 不推荐 */
button#backButton {…}
/* 推荐 */
#newMenuIcon {…}
- 避免使用标签限制 class 选择器
/* 不推荐 */
treecell.indented {…}
/* 推荐 */
.treecell-indented {…}
/* 更推荐 */
.hierarchy-deep {…}
- 避免使用多层标签选择器。使用 class 选择器替换,减少css查找
/* 不推荐 */
treeitem[mailfolder="true"] > treerow > treecell {…}
/* 推荐 */
.treecell-mailfolder {…}
- 避免使用子选择器
/* 不推荐 */
treehead treerow treecell {…}
/* 推荐 */
treehead > treerow > treecell {…}
/* 更推荐 */
.treecell-header {…}
- 使用继承
/* 不推荐 */
#bookmarkMenuItem > .menu-left { list-style-image: url(blah) }
/* 推荐 */
#bookmarkMenuItem { list-style-image: url(blah)
5. JavaScript 规范
5.1 通用约定
5.1.1 缩进
-
[强制]使用
Tab
键来缩进,需在编辑器设置一个缩进为2
个字符。 -
[建议] 使用多行模板字符串时遵循缩进原则。当空行与空白字符敏感时,不使用多行模板字符串。
2
空格为一个缩进,换行后添加一层缩进。将起始和结束的 `` ` 符号单独放一行,有助于生成 HTML 时的标签对齐。为避免破坏缩进的统一,当空行与空白字符敏感时,建议使用
多个模板字符串
或普通字符串
进行连接运算,也可使用数组join
生成字符串。// good function foo() { let html = ` <div> <p></p> <p></p> </div> `; } // Good function greeting(name) { return 'Hello, \n' + `${name.firstName} ${name.lastName}`; } // Bad function greeting(name) { return `Hello, ${name.firstName} ${name.lastName}`; }
5.1.2 空格
-
二元运算符两侧必须有一个空格,一元运算符与操作对象之间不允许有空格。
let 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 let obj = { a: 1, b: 2, c: 3 }; // bad let obj = { a : 1, b:2, c :3 };
-
函数声明、具名函数表达式、函数调用中,函数名和
(
之间不允许有空格。// good function funcName() { } let funcName = function funcName() { }; funcName(); // bad function funcName () { } let 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 let arr1 = []; let arr2 = [1, 2, 3]; let obj1 = {}; let obj2 = {name: 'obj'}; let obj3 = { name: 'obj', age: 20, sex: 1 }; // bad let arr1 = [ ]; let arr2 = [ 1, 2, 3 ]; let obj1 = { }; let obj2 = { name: 'obj' }; let obj3 = {name: 'obj', age: 20, sex: 1};
-
行尾不得有多余的空格。
-
[强制] 使用
generator
时,*
前面不允许有空格,*
后面必须有一个空格。// good function* caller() { yield 'a'; yield* callee(); yield 'd'; } // bad function * caller() { yield 'a'; yield *callee(); yield 'd'; }
5.1.3 换行
-
[强制] 每个独立语句结束后必须换行。
-
[强制] 每行不得超过
120
个字符。超长的不可分割的代码允许例外,比如复杂的正则表达式。长字符串不在例外之列。
-
[强制] 运算符处换行时,运算符必须在新行的行首。
// good if (user.isAuthenticated() && user.isInRole('admin') && user.hasAuthority('add-admin') || user.hasAuthority('delete-admin') ) { // Code } let result = number1 + number2 + number3 + number4 + number5; // bad if (user.isAuthenticated() && user.isInRole('admin') && user.hasAuthority('add-admin') || user.hasAuthority('delete-admin')) { // Code } let result = number1 + number2 + number3 + number4 + number5;
-
[强制] 在函数声明、函数表达式、函数调用、对象创建、数组创建、
for
语句等场景中,不允许在,
或;
前换行。// good let obj = { a: 1, b: 2, c: 3 }; foo( aVeryVeryLongArgument, anotherVeryLongArgument, callback ); // bad let obj = { a: 1 , b: 2 , c: 3 }; foo( aVeryVeryLongArgument , anotherVeryLongArgument , callback );
-
[建议] 不同行为或逻辑的语句集,使用空行隔开,更易阅读。
// 仅为按逻辑换行的示例,不代表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 相同的结构。 let html = '' // 此处用一个空字符串,以便整个 HTML 片段都在新行严格对齐 + '<article>' + '<h1>Title here</h1>' + '<p>This is a paragraph</p>' + '<footer>Complete</footer>' + '</article>'; // 也可使用数组来进行拼接,相对 `+` 更容易调整缩进。 let html = [ '<article>', '<h1>Title here</h1>', '<p>This is a paragraph</p>', '<footer>Complete</footer>', '</article>' ]; html = html.join(''); // 当参数过多时,将每个参数独立写在一行上,并将结束的右括号 ) 独立一行。 // 所有参数必须增加一个缩进。 foo( aVeryVeryLongArgument, anotherVeryLongArgument, callback ); // 也可以按逻辑对参数进行组合。 // 如 format 函数,调用时将参数分为“模板”和“数据”两块 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(); // 三元运算符由3部分组成,因此其换行应当根据每个部分的长度不同,形成不同的情况。 let result = thisIsAVeryVeryLongCondition ? resultA : resultB; let result = condition ? thisIsAVeryVeryLongResult : resultB; // 数组和对象初始化的混用,严格按照每个对象的 `{` 和结束 `}` 在独立一行的风格书写。 let array = [ { // ... }, { // ... } ];
-
[建议] 对于
if...else...
、try...catch...finally
等语句,推荐使用在}
号后添加一个换行 的风格,使代码层次结构更清晰,阅读性更好。if (condition) { // some statements; } else { // some statements; } try { // some statements; } catch (ex) { // some statements; }
5.1.4 语句
-
[强制] 不得省略语句结束的分号。
-
[强制] 在
if / else / for / do / while
语句中,即使只有一行,也不得省略块{...}
。// good if (condition) { callFunc(); } // bad if (condition) callFunc(); if (condition) callFunc();
-
[强制] 函数定义结束不允许添加分号。
// good function funcName() { } // bad function funcName() { }; // 如果是函数表达式,分号是不允许省略的。 let funcName = function () { };
-
[强制]
IIFE
(Immediately-Invoked Function Expression, 立即执行函数) 必须在函数表达式外添加(
,非IIFE
不得在函数表达式外添加(
。额外的
(
能够让代码在阅读的一开始就能判断函数是否立即被调用,进而明白接下来代码的用途。而不是一直拖到底部才恍然大悟。// good let task = (function () { // Code return result; })(); let func = function () { }; // bad let task = function () { // Code return result; }(); let func = (function () { });
-
[强制] 类声明结束不允许添加分号。
-
[强制] 类成员定义中,方法定义后不允许添加分号,成员属性定义后必须添加分号。
-
// good class Foo { foo = 3; bar() { } } // bad class Foo { foo = 3 bar() { } }
-
[强制]
export
语句后,不允许出现表示空语句的分号。// good export function foo() { } export default function bar() { } // bad export function foo() { }; export default function bar() { };
-
[强制] 属性装饰器后,可以不加分号的场景,不允许加分号。
只有一种场景是必须加分号的:当属性
key
是computed property key
时,其装饰器必须加分号,否则修饰key
的[]
会做为之前表达式的property accessor
。上面描述的场景,装饰器后需要加分号。其余场景下的属性装饰器后不允许加分号。
// good class Foo { @log('INFO') bar() { } @log('INFO'); ['bar' + 2]() { } } // bad class Foo { @log('INFO'); bar() { } @log('INFO') ['bar' + 2]() { } }
-
[强制] 箭头函数的参数只有一个,并且不包含解构时,参数部分的括号必须省略。
// good list.map(item => item * 2); // good let fetchName = async id => { let user = await request(`users/${id}`); return user.fullName; }; // bad list.map((item) => item * 2); // bad let fetchName = async (id) => { let user = await request(`users/${id}`); return user.fullName; };
-
[建议] 箭头函数的函数体只有一个单行表达式语句,且作为返回值时,省略
{}
和return
。如果单个表达式过长,可以使用
()
进行包裹。// good list.map(item => item * 2); let foo = () => ( condition ? returnValueA() : returnValueB() ); // bad list.map(item => { return item * 2; });
-
[建议] 箭头函数的函数体只有一个
Object Literal (对象字面量)
,且作为返回值时,使用()
包裹。// good list.map(item => ({name: item[0], email: item[1]}));
-
[强制] 解构多个变量时,如果超过行长度限制,每个解构的变量必须单独一行。
太多的变量解构会让一行的代码非常长,极有可能超过单行长度控制,使代码可读性下降。
// good let { name: personName, email: personEmail, sex: personSex, age: personAge } = person; // bad let {name: personName, email: personEmail, sex: personSex, age: personAge } = person;
5.1.5 注释
5.1.5.1 原则
- As short as possible(如无必要,勿增注释):尽量提高代码本身的清晰性、可读性。
- As long as necessary(如有必要,尽量详尽):合理的注释、空行排版等,可以让代码更易阅读、更具美感。
5.1.5.2 单行注释
[强制]必须独占一行。//
后跟一个空格,缩进与下一行被注释说明的代码一致。
5.1.5.3 多行注释
[建议]避免使用 /*...*/
这样的多行注释。有多行注释内容时,使用多个单行注释。
5.1.5.4 类型定义
-
[强制] 类型定义都是以
{
开始,以}
结束。常用类型如:{string}, {number}, {boolean}, {Object}, {Function}, {RegExp}, {Array}, {Date}。
类型不仅局限于内置的类型,也可以是自定义的类型。比如定义了一个类 Developer,就可以使用它来定义一个参数和返回值的类型。
-
[强制] 对于基本类型 {string}, {number}, {boolean},首字母必须小写。
类型定义 语法示例 解释 String 字符串 Number 数字 Boolean 布尔值 Object 对象 Function 函数/方法 RegExp 正则表达式 Array 数组 Date 日期 单一类型集合 string 类型的数组 多类型 可能是 number 类型, 也可能是 boolean 类型 允许为null 可能是 number, 也可能是 null 不允许为null Object 类型, 但不是 null Function类型 函数, 形参类型 Function带返回值 函数, 形参, 返回值类型 Promise Promise.<resolveType, rejectType> Promise,成功返回的数据类型,失败返回的错误类型 参数可选 @param {string=} name 可选参数, =为类型后缀 可变参数 @param {...number} args 变长参数, ...为类型前缀 任意类型 任意类型 可选任意类型 @param {*=} name 可选参数,类型不限 可变任意类型 @param {...*} args 变长参数,类型不限
5.1.5.5 文件注释
-
[强制] 文件顶部必须包含文件注释,用
@file
标识文件说明。 -
[建议] 文件注释中可以用
@author
标识开发者信息。开发者信息能够体现开发人员对文件的贡献,并且能够让遇到问题或希望了解相关信息的人找到维护人。通常情况文件在被创建时标识的是创建者。随着项目的进展,越来越多的人加入,参与这个文件的开发,新的作者应该被加入
@author
标识。@author
标识具有多人时,原则是按照责任
进行排序。通常的说就是如果有问题,就是找第一个人应该比找第二个人有效。比如文件的创建者由于各种原因,模块移交给了其他人或其他团队,后来因为新增需求,其他人在新增代码时,添加@author
标识应该把自己的名字添加在创建人的前面。@author
中的名字不允许被删除。任何劳动成果都应该被尊重。业务项目中,一个文件可能被多人频繁修改,并且每个人的维护时间都可能不会很长,不建议为文件增加
@author
标识。通过版本控制系统追踪变更,按业务逻辑单元确定模块的维护责任人,通过文档与wiki跟踪和查询,是更好的责任管理方式。对于业务逻辑无关的技术型基础项目,特别是开源的公共项目,应使用
@author
标识。/** * @file Describe the file * @author author-name(mail-name@domain.com) * author-name2(mail-name2@domain.com) */
5.1.5.6 命名空间注释
[建议] 命名空间使用 @namespace
标识。
/**
* @namespace
*/
let util = {};
5.1.5.7 类注释
-
[建议] 使用
@class
标记类或构造函数。对于使用对象
constructor
属性来定义的构造函数,可以使用@constructor
来标记。/** * 描述 * * @class */ function Developer() { // constructor body }
-
[建议] 使用
@extends
标记类的继承信息。/** * 描述 * * @class * @extends Developer */ function Fronteer() { Developer.call(this); // constructor body } util.inherits(Fronteer, Developer);
-
[强制] 使用包装方式扩展类成员时, 必须通过
@lends
进行重新指向。没有
@lends
标记将无法为该类生成包含扩展类成员的文档。/** * 类描述 * * @class * @extends Developer */ function Fronteer() { Developer.call(this); // constructor body } util.extend( Fronteer.prototype, /** @lends Fronteer.prototype */{ getLevel: function () { // TODO } } );
-
[强制] 类的属性或方法等成员信息不是
public
的,应使用@protected
或@private
标识可访问性。生成的文档中将有可访问性的标记,避免用户直接使用非
public
的属性或方法。/** * 类描述 * * @class * @extends Developer */ let Fronteer = function () { Developer.call(this); /** * 属性描述 * * @type {string} * @private */ this.level = 'T12'; // constructor body }; util.inherits(Fronteer, Developer); /** * 方法描述 * * @private * @return {string} 返回值描述 */ Fronteer.prototype.getLevel = function () { };
5.1.5.8 函数/方法注释
-
函数/方法注释必须包含函数说明,有参数和返回值时必须使用注释标识。
当
return
关键字仅作退出函数/方法使用时,无须对返回值作注释标识。 -
[强制] 参数和返回值注释必须包含类型信息,且不允许省略参数的说明。
-
[建议]当函数是内部函数,外部不可访问时,可以使用 @inner 标识。
/**
* 函数描述
*
* @param {string} p1 参数1的说明
* @param {string} p2 参数2的说明,比较长
* 那就换行了.
* @param {number=} p3 参数3的说明(可选)
* @return {Object} 返回值描述
*/
function foo(p1, p2, p3) {
let p3 = p3 || 10;
return {
p1: p1,
p2: p2,
p3: p3
};
}
-
[强制] 对 Object 中各项的描述, 必须使用
@param
标识。/** * 函数描述 * * @param {Object} option 参数描述 * @param {string} option.url option项描述 * @param {string=} option.method option项描述,可选参数 */ function foo(option) { // TODO }
-
[建议] 重写父类方法时, 应当添加
@override
标识。如果重写的形参个数、类型、顺序和返回值类型均未发生变化,可省略@param
、@return
,仅用@override
标识,否则仍应作完整注释。简而言之,当子类重写的方法能直接套用父类的方法注释时可省略对参数与返回值的注释。
5.1.5.9 事件注释
-
[强制] 必须使用
@event
标识事件,事件参数的标识与方法描述的参数标识相同。/** * 值变更时触发 * * @event Select#change * @param {Object} e e描述 * @param {string} e.before before描述 * @param {string} e.after after描述 */ this.fire( 'change', { before: 'foo', after: 'bar' } );
-
[强制] 在会广播事件的函数前使用
@fires
标识广播的事件,在广播事件代码前使用@event
标识事件。 -
[建议] 对于事件对象的注释,使用
@param
标识,生成文档时可读性更好。
5.1.5.10 常量注释
[强制] 常量必须使用 @const
标记,并包含说明和类型信息。
/**
* 常量说明
*
* @const
* @type {string}
*/
const REQUEST_URL = 'myurl.do';
5.1.5.11 细节注释
对于内部实现、不容易理解的逻辑说明、摘要信息等,我们可能需要编写细节注释。
-
[建议] 细节注释遵循单行注释的格式。说明必须换行时,每行是一个单行注释的起始
function foo(p1, p2, opt_p3) { // 这里对具体内部逻辑进行说明 // 说明太长需要换行 for (...) { .... } }
-
[强制] 有时我们会使用一些特殊标记进行说明。特殊标记必须使用单行注释的形式。下面列举了一些常用标记:
- TODO: 有功能待实现。此时需要对将要实现的功能进行简单说明。
- FIXME: 该处代码运行没问题,但可能由于时间赶或者其他原因,需要修正。此时需要对如何修正进行简单说明。
- HACK: 为修正某些问题而写的不太好或者使用了某些诡异手段的代码。此时需要对思路或诡异手段进行描述。
- XXX: 该处存在陷阱。此时需要对陷阱进行描述。
5.1.6 命名
- [强制]
变量
, 使用Camel 命名法
。
let loadingModules = {};
- [强制]
私有属性、私有变量和私有方法
以下划线_
开头。
let _privateMethod = {};
- [强制]
常量
使用全部字母大写,单词间下划线分隔
的命名方式。
let 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
命名法,枚举的属性
使用全部字母大写,单词间下划线分隔
的命名方式。
let TargetState = {
READING: 1,
READED: 2,
APPLIED: 3,
READY: 4
};
-
[强制]
命名空间
使用Camel命名法
。equipments.heavyWeapons = {};
-
[强制]由多个单词组成的
缩写词
,在命名中,根据当前命名法和出现的位置,所有字母的大小写与首字母的大小写保持一致。
function XMLParser() {}
function insertHTML(element, html) {}
let httpRequest = new HTTPRequest();
5.1.7 命名语法
- 类名,使用名词。
function Engine(options) {}
- 函数名,使用动宾短语。
function getStyle(element) {}
- boolean 类型的变量使用 is 或 has 开头。
let isReady = false;
let hasMoreCommands = false;
- Promise 对象用动宾短语的进行时表达。
let loadingData = ajax.get('url');
loadingData.then(callback);
5.1.8 接口命名规范
- 可读性强,见名知义;
- 尽量写全。不用缩写,除非是下面列表中约定的;
常用词 | 说明 |
---|---|
options | 表示选项,与 jQuery 社区保持一致,不要用 config, opts 等 |
active | 表示当前,不要用 current 等 |
index | 表示索引,不要用 idx 等 |
trigger | 触点元素 |
triggerType | 触发类型、方式 |
context | 表示传入的 this 对象 |
object | 推荐写全,不推荐简写为 o, obj 等 |
element | 推荐写全,不推荐简写为 el, elem 等 |
length | 不要写成 len, l |
prev | previous 的缩写 |
next | next 下一个 |
constructor | 不能写成 ctor |
easing | 示动画平滑函数 |
min | minimize 的缩写 |
max | maximize 的缩写 |
DOM | 不要写成 dom, Dom |
.hbs | 使用 hbs 后缀表示模版 |
btn | button 的缩写 |
link | 超链接 |
title | 主要文本 |
img | 图片路径(img标签src属性) |
dataset | html5 data-xxx 数据接口 |
theme | 主题 |
className | 类名 |
classNameSpace | class 命名空间 |
5.1.9 不要在 Array 上使用 for-in 循环
for-in 循环只用于 object/map/hash
的遍历, 对 Array
用 for-in 循环有时会出错. 因为它并不是从 0 到 length - 1 进行遍历, 而是所有出现在对象及其原型链的键值。
// 不推荐
function printArray(arr) {
for (let key in arr) {
print(arr[key]);
}
}
printArray([0,1,2,3]); // This works.
let a = new Array(10);
printArray(a); // This is wrong.
a = document.getElementsByTagName('*');
printArray(a); // This is wrong.
a = [0,1,2,3];
a.buhu = 'wine';
printArray(a); // This is wrong again.
a = new Array;
a[3] = 3;
printArray(a); // This is wrong again.
// 推荐
function printArray(arr) {
let l = arr.length;
for (let i = 0; i < l; i++) {
print(arr[i]);
}
}
5.1.10 二元和三元操作符
操作符始终写在前一行, 以免分号的隐式插入产生预想不到的问题。
let x = a ? b : c;
let y = a ?
longButSimpleOperandB : longButSimpleOperandC;
let z = a ?
moreComplicatedB :
moreComplicatedC;
.
操作符也是如此:
let x = foo.bar().
doSomething().
doSomethingElse();
5.1.11 条件(三元)操作符 (?😃
三元操作符用于替代 if 条件判断语句。
// 不推荐
if (val != 0) {
return foo();
} else {
return bar();
}
// 推荐
return val ? foo() : bar();
5.2 语言特性
5.2.1 变量
-
[强制] 变量、函数在使用前必须先定义。且需使用
let
或const
,不使用var
。使用
let
和const
定义时,变量作用域范围更明确。// good let name = 'MyName'; const PI = '3.14'; for (let i = 0; i < 10; i++) { } // bad name = 'MyName'; for (var i = 0; i < 10; i++) { }
原则上不建议使用全局变量,对于已有的全局变量或第三方框架引入的全局变量,需要根据检查工具的语法标识。
/* globals jQuery */ let element = jQuery('#element-id');
-
[强制] 每个
let
只能声明一个变量。一个
let
声明多个变量,容易导致较长的行长度,并且在修改时容易造成逗号和分号的混淆。// good let hangModules = []; let missModules = []; let visited = {}; // bad let hangModules = [], missModules = [], visited = {};
-
[强制] 变量必须
即用即声明
,不得在函数或其它形式的代码块起始位置统一声明所有变量。变量声明与使用的距离越远,出现的跨度越大,代码的阅读与维护成本越高。虽然JavaScript的变量是函数作用域,还是应该根据编程中的意图,缩小变量出现的距离空间。
// good function kv2List(source) { let list = []; for (let key in source) { if (source.hasOwnProperty(key)) { let item = { k: key, v: source[key] }; list.push(item); } } return list; } // bad function kv2List(source) { let list = []; let key; let item; for (key in source) { if (source.hasOwnProperty(key)) { item = { k: key, v: source[key] }; list.push(item); } } return list; }
5.2.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) { // ...... }
// null 或 undefined // good if (noValue == null) { // ...... } // bad if (noValue === null || typeof noValue === 'undefined') { // ...... }
-
[建议] 按执行频率排列分支的顺序。
按执行频率排列分支的顺序好处是:
- 阅读的人容易找到最常见的情况,增加可读性。
- 提高执行效率。
-
[建议] 对于相同变量或表达式的多值条件,用
switch
代替if
。// good switch (typeof variable) { case 'object': // ...... break; case 'number': case 'boolean': case 'string': // ...... break; } // bad let type = typeof variable; if (type === 'object') { // ...... } else if (type === 'number' || type === 'boolean' || type === 'string') { // ...... }
-
[建议] 如果函数或全局中的
if
块有return
且else
块后没有任何语句,可以删除else
。// good function getName() { if (name) { return name; } return 'unnamed'; } // bad function getName() { if (name) { return name; } else { return 'unnamed'; } }
5.2.3 循环
-
[建议] 不要在循环体中包含函数表达式,事先将函数提取到循环体外。
循环体中的函数表达式,运行过程中会生成循环次数个函数对象。
// good function clicker() { // ...... } for (let i = 0, len = elements.length; i < len; i++) { let element = elements[i]; addListener(element, 'click', clicker); } // bad for (let i = 0, len = elements.length; i < len; i++) { let element = elements[i]; addListener(element, 'click', function () {}); }
-
[建议] 对循环内多次使用的不变值,在循环外用变量缓存。
// good let width = wrap.offsetWidth + 'px'; for (let i = 0, len = elements.length; i < len; i++) { let element = elements[i]; element.style.width = width; // ...... } // bad for (let i = 0, len = elements.length; i < len; i++) { let element = elements[i]; element.style.width = wrap.offsetWidth + 'px'; // ...... }
-
[建议] 对有序集合进行遍历时,缓存
length
。虽然现代浏览器都对数组长度进行了缓存,但对于一些宿主对象和老旧浏览器的数组对象,在每次
length
访问时会动态计算元素个数,此时缓存length
能有效提高程序性能。for (let i = 0, len = elements.length; i < len; i++) { let element = elements[i]; // ...... }
5.2.4 解构
-
[强制] 不要使用3层及以上的解构。
过多层次的解构会让代码变得难以阅读。
// bad let {documentElement: {firstElementChild: {nextSibling}}} = window;
-
[建议] 使用解构减少中间变量。
常见场景如变量值交换,可能产生中间变量。这种场景推荐使用解构。
// good [x, y] = [y, x]; // bad let temp = x; x = y; y = temp;
-
[强制] 如果不节省编写时产生的中间变量,解构表达式
=
号右边不允许是ObjectLiteral
和ArrayLiteral
。// good let {first: firstName, last: lastName} = person; let one = 1; let two = 2; // bad let [one, two] = [1, 2];
-
[强制] 使用剩余运算符时,剩余运算符之前的所有元素必需具名。
剩余运算符之前的元素省略名称可能带来较大的程序阅读障碍。如果仅仅为了取数组后几项,请使用
slice
方法。// good let [one, two, ...anyOther] = myArray; let other = myArray.slice(3); // bad let [,,, ...other] = myArray;
5.2.5 类型
5.2.5.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'
5.2.5.2 类型转换
-
[建议] 转换成
string
时,使用+ ''
。// good num + ''; // bad new String(num); num.toString(); String(num);
-
[建议] 转换成
number
时,通常使用+
。// good +str; // bad Number(str);
-
[建议]
string
转换成number
,要转换的字符串结尾包含非数字并期望忽略时,使用parseInt
。let width = '200px'; parseInt(width, 10);
-
[建议] 转换成
boolean
时,使用!!
。let num = 3.14; !!num;
5.2.6 字符串
- [强制] 字符串开头和结束使用单引号
'
。
- 输入单引号不需要按住
shift
,方便输入。 - 实际使用中,字符串经常用来拼接 HTML。为方便 HTML 中包含双引号而不需要转义写法。
let str = '我是一个字符串';
let html = '<div class="cls">拼接HTML可以省去双引号转义</div>';
-
[强制] 模板字符串字符串内变量替换时,不要使用
2
次及以上的函数调用。// good let fullName = getFullName(getFirstName(), getLastName()); let s = `Hello ${fullName}`; // bad let s = `Hello ${getFullName(getFirstName(), getLastName())}`;
5.2.7 函数
-
[建议] 使用变量默认语法代替基于条件判断的默认值声明。
// good function foo(text = 'hello') { } // bad function foo(text) { text = text || 'hello'; }
-
[强制] 不要使用
arguments
对象,应使用...args
代替。// good function foo(...args) { console.log(args.join('')); } // bad function foo() { console.log([].join.call(arguments)); }
5.2.8 对象
-
[建议] 对象创建时,如果一个对象的所有
属性
均可以不添加引号,建议所有属性
不添加引号。let info = { name: 'someone', age: 28 };
-
[建议] 对象创建时,如果任何一个
属性
需要添加引号,则所有属性
建议添加'
。
如果属性不符合 Identifier 和 NumberLiteral 的形式,就需要以 StringLiteral 的形式提供。// good let info = { 'name': 'someone', 'age': 28, 'more-info': '...'
};
// bad
let info = {
name: 'someone',
age: 28,
'more-info': '...'
};
```
-
[建议] 定义对象时,如果所有键均指向同名变量,则所有键都使用缩写;如果有一个键无法指向同名变量,则所有键都不使用缩写。
// good let foo = {x, y, z}; let foo2 = { x: 1, y: 2, z: z }; // bad let foo = { x: x, y: y, z: z }; let foo2 = { x: 1, y: 2, z };
-
[强制] 定义方法时使用
MethodDefinition
语法,不使用PropertyName: FunctionExpression
语法。// good let foo = { bar(x, y) { return x + y; } }; // bad let foo = { bar: function (x, y) { return x + y; } };
-
[强制] 不允许修改和扩展任何原生对象和宿主对象的原型。
// 以下行为绝对禁止 String.prototype.trim = function () { };
-
[建议] 属性访问时,尽量使用
.
。属性名符合 Identifier 的要求,就可以通过
.
来访问,否则就只能通过[expr]
方式访问。通常在 JavaScript 中声明的对象,属性命名是使用 Camel 命名法,用
.
来访问更清晰简洁。部分特殊的属性(比如来自后端的 JSON ),可能采用不寻常的命名方式,可以通过[expr]
方式访问。info.age; info['more-info'];
-
[建议] 使用
Object.keys
或Object.entries
进行对象遍历。// good for (let key of Object.keys(foo)) { let value = foo[key]; } // good for (let [key, value] of Object.entries(foo)) { // ... }
-
[建议] 定义对象的方法不应使用箭头函数。
箭头函数将
this
绑定到当前环境,在obj.method()
调用时容易导致不期待的this
。除非明确需要绑定this
,否则不应使用箭头函数。// good let foo = { bar(x, y) { return x + y; } }; // bad let foo = { bar: (x, y) => x + y };
-
[建议] 尽量使用计算属性键在一个完整的字面量中完整地定义一个对象,避免对象定义后直接增加对象属性。
在一个完整的字面量中声明所有的键值,而不需要将代码分散开来,有助于提升代码可读性。
// good const MY_KEY = 'bar'; let foo = { [MY_KEY + 'Hash']: 123 }; // bad const MY_KEY = 'bar'; let foo = {}; foo[MY_KEY + 'Hash'] = 123;
5.2.9 类
-
[强制] 使用
class
关键字定义一个类。直接使用
class
定义类更清晰。不要再使用function
和prototype
形式的定义。// good class TextNode { constructor(value, engine) { this.value = value; this.engine = engine; } clone() { return this; } } // bad function TextNode(value, engine) { this.value = value; this.engine = engine; } TextNode.prototype.clone = function () { return this; };
-
[强制] 使用
super
访问父类成员,而非父类的prototype
。使用
super
和super.foo
可以快速访问父类成员,而不必硬编码父类模块而导致修改和维护的不便,同时更节省代码。// good class TextNode extends Node { constructor(value, engine) { super(value); this.engine = engine; } setNodeValue(value) { super.setNodeValue(value); this.textContent = value; } } // bad class TextNode extends Node { constructor(value, engine) { Node.apply(this, arguments); this.engine = engine; } setNodeValue(value) { Node.prototype.setNodeValue.call(this, value); this.textContent = value; } }
4.2.10 模块
-
[强制]
export
与内容定义放在一起。何处声明要导出的东西,就在何处使用
export
关键字,不在声明后再统一导出。// good export function foo() { } export const bar = 3; // bad function foo() { } const bar = 3; export {foo}; export {bar};
-
[强制] 所有
import
语句写在模块开始处。// good import {bar} from './bar'; function foo() { bar.work(); } // bad function foo() { import {bar} from './bar'; bar.work(); }
4.2.11 集合
-
[建议] 对数组进行连接操作时,使用数组展开语法。
用数组展开代替
concat
方法,数组展开对Iterable
有更好的兼容性。// good let foo = [...foo, newValue]; let bar = [...bar, ...newValues]; // bad let foo = foo.concat(newValue); let bar = bar.concat(newValues);
-
[建议] 不要使用数组展开进行数组的复制操作。
使用数组展开语法进行复制,代码可读性较差。推荐使用
Array.from
方法进行复制操作。// good let otherArr = Array.from(arr); // bad let otherArr = [...arr];
-
[建议] 当需要遍历功能时,使用
Map
和Set
。Map
和Set
是可遍历对象,能够方便地使用for...of
遍历。不要使用使用普通 Object。// good let membersAge = new Map([ ['one', 10], ['two', 20], ['three', 30] ]); for (let [key, value] of map) { } // bad let membersAge = { one: 10, two: 20, three: 30 }; for (let key in membersAge) { if (membersAge.hasOwnProperty(key)) { let value = membersAge[key]; } }
-
[建议] 程序运行过程中有添加或移除元素的操作时,使用
Map
和Set
。使用
Map
和Set
,程序的可理解性更好;普通 Object 的语义更倾向于表达固定的结构。// good let membersAge = new Map(); membersAge.set('one', 10); membersAge.set('two', 20); membersAge.set('three', 30); membersAge.delete('one'); // bad let membersAge = {}; membersAge.one = 10; membersAge.two = 20; membersAge.three = 30; delete membersAge['one'];
6. Vue代码规范
6.1 Vue 项目目录规范
|-- api 所有api接口
|-- assets 静态资源,images, icons, styles等
|-- components 公用组件
|-- common 公共方法
|-- config 配置信息
|-- constants 常量信息,项目所有Enum, 全局常量等
|-- directives 自定义指令
|-- filters 过滤器,全局工具
|-- datas 模拟数据,临时存放
|-- lib 外部引用的插件存放及修改文件
|-- mock 模拟接口,临时存放
|-- plugins 插件,全局使用
|-- router 路由,统一管理
|-- store vuex, 统一管理
|-- themes 自定义样式主题
|-- views 视图目录
6.2 组件规范
组件名必须是多个单词(大于等于2) 且命名规范全部为 驼峰命名法,且第一个单词首字母也需要大写
export default {
name: 'TodoItem'
// ...
};
写组件标签的时候必须写双标签不能写单标签
<MyComponent></MyComponent>
6.3 Vue api 规范
Prop 定义应该尽量详细
- 必须指定类型
- 必须添加注释
- 必须加上 require 或者 default
props: {
// 组件状态,用于控制组件的颜色
status: {
type: String,
required: true,
}
}
-
如果运行时,需要非常频繁地切换,使用 v-show ;如果在运行时,条件很少改变,使用 v-if。
-
指令推荐都使用缩写形式,(用 : 表示 v-bind: 、用 @ 表示 v-on: 和用 # 表示 v-slot:)
<input @input="onInput" @focus="onFocus">
- {{}} moustache 语法中尽量不写复杂的表达式,必要时使用计算属性等方法表示
6.4 Vue Router 规范
-
必须使用路由懒加载机制
-
路由表中的 name 命名与 component 组件保持一致 ,因为 keep-alive 的存在
keep-alive 必须按照 component name 进行缓存 两者必须保持高度统一 ,path
可以使用 name 命名 第一个单词首字母改成小写
6.5 前后端对接 api 规范
-
此目录对应后端 API 接口,按照后端一个 controller 一个 api js 文件。若项目较大时,可以按照业务划分子目录,并与后端保持一致。
-
api 中的方法名字要与后端 api url 尽量保持语义高度一致性。
-
对于 api 中的每个方法要添加注释,注释与后端 swagger 文档保持一致。
6.6 注释说明
以下为必须添加注释的地方
-
公共组件使用说明
-
各组件中重要函数或者类说明
-
复杂的业务逻辑处理说明
-
api 目录的接口 js 文件必须加注释
-
store 中的 state, mutation, action 等必须加注释
-
.vue 文件中的 template 必须加注释,若文件较大添加 start end 注释
-
.vue 文件的 methods,每个 method 必须添加注释
-
.vue 文件的 data, 非常见单词要加注释
-
多行注释按照下面方式进行书写
/**
* 组件名称
* @module 组件存放位置
* @desc 组件描述
* @author 组件作者
* @date 2017年12月05日17:22:43
* @param {Object} [title] - 参数说明
* @param {String} [columns] - 参数说明
* @example 调用示例
**/
<hbTable :title="title" :columns="columns" :tableData="tableData"></hbTable>
6.7 其他
- 使用 Vue 框架之后尽量不要再去手动操作DOM,包括增删改 dom 元素 以及更改样式,添加事件
- router 与 store 一定要将里面的代码按照业务进行拆分,不能放到同一个js文件里面
- 删除无用代码
- 更多参考见 Vue风格指南
7. 移动端规范
-
click 的 300ms 延迟响应
click 的 300ms 延迟是由双击缩放(double tap to zoom)所导致的,由于用户可以进行双击缩放或者双击滚动的操作,当用户一次点击屏幕之后,浏览器并不能立刻判断用户是确实要打开这个链接,还是想要进行双击操作。因此,移动端浏览器就等待 300 毫秒,以判断用户是否再次点击了屏幕。
-
解决方法1:禁止缩放
既然双击缩放是造成300ms延迟的原因,那么只要禁用缩放就可以了。禁用缩放,也就没有了双击产生缩放的操作,那么就不需要等待300ms,也就没有了300ms的延迟。
<meta name="viewport" content="width=device-width,user-scalable=no">
-
解决方法2:FastClick
在检测到touched事件后,立即触发一个模拟click事件,并把浏览器300ms之后真正触发的click事件阻断掉。
window.addEventListener( "load", function() { FastClick.attach( document.body ); }, false );
-
-
快速回弹滚动
如果想要为某个元素拥有 Native 般的滚动效果,可以这样操作:
.element { overflow: auto; /* auto | scroll */ -webkit-overflow-scrolling: touch; }
除了 iScroll 之外,还有一个更加强大的滚动插件 Swiper,支持 3D 和内置滚动条等。
-
获取滚动条的位置
PC 端滚动条的值是通过
document.scrollTop
和document.scrollLeft
获得,但在 iOS 中并没有滚动条的概念,所以仅能通过 windows.scroll 获取,同时也能兼容 Android 。window.scrollY window.scrollX
-
清除输入框内阴影
在 iOS 上,输入框默认有内部阴影,但无法使用 box-shadow 来清除,如果不需要阴影,可以这样操作:
input, textarea { border: 0; /* 方法1 */ -webkit-appearance: none; /* 方法2 */ }
-
页面窗口自动调整到设备宽度,并禁止用户缩放页面
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
-
电话号码识别
iOS Safari ( Android 或其他浏览器不会) 会自动识别看起来像电话号码的数字,将其处理为电话号码链接,比如:
- 7位数字,形如:1234567
- 带括号及加号的数字,形如:(+86)123456789
- 双连接线的数字,形如:00-00-00111
- 11位数字,形如:13800138000
<!-- 关闭电话号码识别: --> <meta name="format-detection" content="telephone=no" /> <!-- 开启电话功能: --> <a href="tel:123456">123456</a> <!-- 开启短信功能: --> <a href="sms:123456">123456</a>
-
邮箱地址的识别
在 Android ( iOS 不会)上,浏览器会自动识别看起来像邮箱地址的字符串,不论有你没有加上邮箱链接,当你在这个字符串上长按,会弹出发邮件的提示。
<!-- 关闭邮箱地址识别: --> <meta name="format-detection" content="email=no" /> <!-- 开启邮件发送: --> <a href="mailto:mobile@gmail.com">mobile@gmail.com</a>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!