前端代码规范
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
HTML 编码规约
(WC-HTML)-HTML 编码规约
前言
本规约涉及 HTML 语言的编码风格、最佳实践。
参与和反馈
对规约有任何意见和建议欢迎留言讨论
-
1【推荐】使用 2 个空格缩进。
统一使用 2 个空格缩进不要使用 4 个空格或 tab 缩进
<!DOCTYPE html> <html> <head> <title>Page title</title> </head> <body> <img src="images/company-logo.png" alt="Company"> <h1 class="hello-world">Hello, world!</h1> </body> </html>
-
2【强制】属性值使用双引号不要使用单引号。
<!-- bad --> <link rel='stylesheet' href='example.css'> <!-- good --> <link rel="stylesheet" href="example.css">
-
3【推荐】不要省略自闭合标签结尾处的斜线且斜线前需留有一个空格。
虽然 HTML5 规范 中指出结尾的斜线是可选的但保留它们可以明确表达该标签已闭合的语义更易于维护和理解。
同时在 react 被广泛使用的今天这与 JSX 的规范 相一致JSX 中自闭合标签必须保留结尾的斜线。
<!-- bad --> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <img src="images/foo.png" alt="foo"> <!-- good --> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <img src="images/foo.png" alt="foo" />
-
4【强制】使用 HTML5 doctype。
在每个页面开头使用
<!DOCTYPE html>
这个简单的 doctype以保证使用标准模式并且在不同浏览器中的渲染尽可能一致。<!-- bad不写 DOCTYPE --> <html> <head> </head> </html> <!-- bad使用非 HTML 5 doctype --> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> </head> </html> <!-- good --> <!DOCTYPE html> <html> <head> </head> </html>
-
5【推荐】指定 html 标签上的 lang 属性。
HTML5 规范中说
推荐开发者在
html
元素上指定lang
属性以指出文档的语言。这有助于读屏、翻译等工具的工作。lang
属性的值由language-subtags
组成在 BCP47 中定义了解更多。<html lang="zh-CN"> <!-- ... --> </html>
-
6【推荐】指定 IE 兼容模式。
IE 浏览器支持使用 标签指定使用什么版本的 IE 来渲染页面。除非特殊需要我们一般使用
IE=Edge,chrome=1
来让 IE 使用所支持的最新模式并让支持 Chrome Frame 插件 的旧 IE 使用 Chrome Frame 渲染页面。<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
-
7【推荐】使用 UTF-8 字符编码。
声明一个明确的字符编码可以让浏览器更快速高效地确定适合网页内容的渲染方式。
由于历史原因淘系不同产品采用了不同的字符编码。但对于今后的新业务如无特殊要求统一使用 UTF-8 字符编码以便统一。
在 HTML 中使用
<meta charset="utf-8">
声明文档的编码方式<head> <meta charset="utf-8"> </head>
-
8【推荐】引入 CSS 和 JavaScript 时无需指定 type。
根据 HTML5 规范引入 CSS 和 JavaScript 时通常不需要指明 type因为 text/css 和 text/javascript 分别是他们的默认值。
<!-- bad --> <link type="text/css" rel="stylesheet" href="example.css"> <style type="text/css"> /* ... */ </style> <script type="text/javascript" src="example.js"></script> <!-- good --> <link rel="stylesheet" href="example.css"> <style> /* ... */ </style> <script src="example.js"></script>
-
9【推荐】在 head 标签内引入 CSS在 body 结束标签前引入 JS。
一般情况下CSS 应在
<head></head>
标签里引入而 JavaScript 除了基础库等比较基础性的脚本文件其他都在靠近body
结束标签前引入。<!-- bad --> <!DOCTYPE html> <html> <head> <script src="mod-a.js"></script> <script src="jquery.js"></script> </head> <body> <style> .mod-example { padding-left: 15px } </style> </body> </html> <!-- good --> <!DOCTYPE html> <html> <head> <style> .mod-example { padding-left: 15px } </style> </head> <body> ... <script src="jquery.js"></script> </body> </html>
-
10【参考】属性顺序。
HTML 属性按照特定顺序出现可以提高可读性推荐顺序如下
class
id
,name
data-*
src
,for
,type
,href
,value
title
,alt
role
,aria-*
class
是为高可复用组件设计的所以处在第一位。ids
更加特指且应该尽量少使用所以处在第二位。<a class="..." id="..." data-toggle="modal" href="#"> Example link </a> <input class="form-control" type="text"> <img src="..." alt="...">
-
11【推荐】不要为 Boolean 属性添加取值。
Boolean 属性指不需要声明取值的属性一个元素中 Boolean 属性存在即表示取值
true
不存在则表示取值false
。了解更多XHTML 需要每个属性声明取值但是 HTML5 并不需要。尽量不要为 Boolean 属性添加取值。
<!-- bad --> <input type="text" disabled="disabled" /> <input type="checkbox" value="1" checked="checked" /> <select> <option value="1" selected="selected">1</option> </select> <!-- good --> <input type="text" disabled /> <input type="checkbox" value="1" checked /> <select> <option value="1" selected>1</option> </select>
-
12【推荐】class 和 id 的命名。
class 和 id 的命名规则为
- 全小写字母
- 多个单词间用中划线
-
分割
例如
example-class
,id-for-label
,id-for-anchor
。除了一个特例当 class 或 id 用作 JS 操作 DOM 的钩子时可以加上特定前缀以明确标识仅用于 JS 操作需注意不要再将此类 class 或 id 用于 CSS。推荐的命名规则为以
J_
为前缀后接小驼峰例如J_exampleClassForJs
,J_exampleIdForJs
。<!-- bad --> <form class="verticalForm tiny_form"> <label for="addressInput">Address</label> <input id="addressInput" name="address" /> </form> <!-- good --> <form class="vertical-form tiny-form"> <label for="address-input">Address</label> <input id="address-input" name="address" /> </form> <!-- good 明确标识该 class 或 id 仅用于 JS dom 操作不会影响样式 --> <div class="use-for-css J_useForJS"> </div> <div id="J_useForJS"> </div>
-
13【推荐】自定义属性的命名以 data- 为前缀。
建议自定义属性的命名都以
data-
为前缀以便区分。<!-- bad --> <a modal="toggle" href="#"> Example link </a> <!-- good --> <a data-modal="toggle" href="#"> Example link </a>
-
14【参考】建议的 HTML 脚手架。
根据以上规约建议的 HTML 脚手架如下
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta lang="zh"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="renderer" content="webkit"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta name="description" content="淘宝网 - 亚洲较大的网上交易平台"> <meta name="keyword" content="淘宝,掏宝,网上购物,C2C"> <title>淘宝网</title> <link rel="stylesheet" href="example.css"> </head> <body> <script src="example.js"></script> </body> </html>
-
15【推荐】注释。
HTML 注释用于希望在源码中看到但不被浏览器渲染的信息。其语法为起始自
<!--
终止于-->
。对于单行注释需在注释内容和注释符之间需留有一个空格以增强可读性
<!-- 单行注释 --> <!-- 多行注释 多行注释 -->
需要额外注意的是由于 HTML 代码一般不会经过预处理出于安全考虑不要在 HTML 中出现任何关于业务相关敏感信息的注释。
-
16【参考】注意 HTML 的可访问性Accessibility。
页面的可访问性即Accessibility常缩写为 a11y是让你的网站尽可能多的人使用的做法——我们传统上认为这是关于残疾人的但实际上它也涵盖了其他群体比如使用移动设备的人群或者那些网络连接缓慢的人群。
例如为 img 标签设置 alt 属性
<!-- bad - 缺少 alt 属性无法被无障碍阅读器识别 --> <img src="hello.jpg" /> <!-- good --> <img src="hello.jpg" alt="Welcome to visit!" /> <!-- good - 图片无需被无障碍阅读器识别时 --> <img src="logo.jpg" alt="" /> <!-- good - 图片无需被无障碍阅读器识别时 --> <img src="logo.jpg" role="presentation" />
了解更多 HTML 可访问性的知识可以阅读这篇 MDN 的文章。
-
17【参考】尽量根据语义使用 HTML 标签。
HTML 标签更严谨的叫法是 HTML 元素都有其语义例如
p
标签即 ‘paragraphs’ 用于章节a
标签即 ‘anchors’ 用于锚点链接。我们应优先选取符合当下所需语义的标签这样的好处是有助于可访问性accessibility并且如果 CSS 挂掉时也可以获得较好的展示效果。
<!-- bad --> <div class="list"> <div class="list-item">1</div> <div class="list-item">2</div> <div class="list-item">3</div> </div> <!-- good --> <ul class="list"> <li class="list-item">1</li> <li class="list-item">2</li> <li class="list-item">3</li> </ul>
CSS 编码规约
(WC-CSS)-CSS 编码规约
前言
本规约涉及 CSS 及其预编译语言Sass、Less的编码风格、最佳实践。
参与和反馈
对规约有任何意见和建议欢迎留言讨论
-
1【推荐】使用 2 个空格缩进。stylelint: indentation
统一使用 2 个空格缩进不要使用 4 个空格或 tab 缩进
/* bad */ .selector { padding-left: 15px; } /* good */ .selector { padding-left: 15px; }
-
2【推荐】空格风格。stylelint: block-closing-brace-space-before block-opening-brace-space-after block-opening-brace-space-before declaration-colon-space-after declaration-colon-space-before value-list-comma-space-after
块的左大括号
{
前有一个空格单行的块左大括号后有一个空格右大括号前有一个空格/* bad */ .selector{ padding-left: 15px; } .selector{padding-left: 15px;} /* good */ .selector { padding-left: 15px; } .selector { padding-left: 15px; }
属性声明语句的冒号
:
后面有一个空格前面无空格/* bad */ .selector { padding-left:15px; } /* good */ .selector { padding-left: 15px; }
>
、+
、~
选择器的前后各保留一个空格/* bad */ .selector>.children { padding-left:15px; } .selector+.brother { padding-left:15px; } /* good */ .selector > .children { padding-left:15px; } .selector + .brother { padding-left:15px; }
逗号
,
分隔的属性值逗号之后有一个空格。但rgb()
,rgba()
,hsl()
,hsla()
,rect()
中的颜色值逗号后面无空格。这么做有助于区分哪些是属性值、哪些是颜色值/* bad */ .selector { background-color: rgba(0, 0, 0, 0.5); box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.5),inset 0 1px 0 rgba(255, 255, 255, 0.5); } /* good */ .selector { background-color: rgba(0,0,0,.5); box-shadow: 0px 1px 2px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.5); }
【仅 Sass 或 Less】四则运算符两侧各保留一个空格
/* bad */ .selector { width: $default-width/2; } /* good */ .selector { width: $default-width / 2; }
【仅 Sass 或 Less】Mixin 名称和括号
()
间不要有空格参数的逗号,
前无空格后无空格/* bad */ .selector { @include size (30px,20px); } /* good */ .selector { @include size(30px, 20px); }
-
3【强制】属性声明使用分号结尾。stylelint: declaration-block-trailing-semicolon
所有声明都应该以分号结尾不能省略不写。虽然最后一条声明后的分号是可选的但如果不写代码会更容易出错并不利于编码一致性。
/* bad */ .selector { margin-top: 10px; padding-left: 15px } /* good */ .selector { margin-top: 10px; padding-left: 15px; }
-
4【推荐】大括号换行风格。stylelint: block-closing-brace-newline-before block-opening-brace-newline-after
CSS 的大括号换行风格与 JS 规约 大体相同采用 Egyptian Brackets 风格具体如下
声明块的右大括号
}
应单独成行/* bad */ .selector { padding-left: 15px;} /* good */ .selector { padding-left: 15px; }
-
5【参考】使用多个选择器时让每个选择器单独一行。stylelint: selector-list-comma-newline-after
这可以增强代码可读性
/* bad */ .selector, .selector-secondary, .selector[type=text] { padding:15px; margin:0px 0px 15px; background-color:rgba(0, 0, 0, 0.5); box-shadow:0 1px 2px #CCC,inset 0 1px 0 #FFFFFF } /* good */ .selector, .selector-secondary, .selector[type="text"] { padding: 15px; margin-bottom: 15px; background-color: rgba(0,0,0,.5); box-shadow: 0px 1px 2px #ccc, inset 0 1px 0 #fff; }
-
6【推荐】属性声明应单独成行。stylelint: declaration-block-single-line-max-declarations
不要在一行声明多条语句这不利于可读性也不利于通过错误报告定位问题。
/* bad */ .selector { padding-left: 15px; margin-left: 10px; } /* good */ .selector { padding-left: 15px; margin-left: 10px; }
-
7【参考】声明块内有多条语句时需要写成多行只有一条语句时可以写成一行。
当声明块内有多条语句时必须写成多行。
但当声明块内只有一条语句时可以选择写成一行以保持简洁。也可以仍写成多行以保持统一和方便增加语句。
/* bad - 声明块包含多条语句时必须写成多行 */ .selector { padding-left: 15px; margin-left: 20px; } /* good */ .selector { padding-left: 15px; margin-left: 20px; } /* good - 声明块包含多条语句时可以写成一行也可以写成多行 */ .selector { padding-left: 15px; } .selector { padding-left: 15px; }
-
8【推荐】单行最大字符数100。stylelint: max-line-length
过长的单行代码不易阅读和维护需要进行合理地换行可以在属性值的空格处或逗号后换行。
我们推荐单行代码最多不要超过 100 个字符除了以下两种情况
- 使用 url() 函数时
- CSS 属性值本身无法换行时即属性值内无空格或逗号时
/* bad */ 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))); /* good */ 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)) );
-
9【参考】统一省略或保留小数点前的 0。stylelint: number-leading-zero
在 CSS 中大于 -1 小于 1 的小数小数点前的 0 可以省略
/* 正常写法 */ .selector { opacity: 0.5; left: -0.5px; } /* 省略小数点前 0 的写法 */ .selector { opacity: .5; left: -.5px; }
对于是否省略小数点前的 0业界存在争议
- 省略 0 的好处是代码更简洁可以减少一个字符
- 不省略的好处是代码可读性更好、一致性更强
你可选择自己倾向的风格在代码中风格统一即可要么都省略要么都保留。
我们推荐保留 0因为当今很多 CSS 压缩工具会在压缩时帮我们去掉 0不存在多占用一个字符的问题。保留 0 能增强代码的可读性和一致性。
-
10【推荐】长度值为 0 时省略掉长度单位。stylelint: length-zero-no-unit
在 CSS 中长度值为 0 时它的单位是可选的长度单位包括em, ex, ch, vw, vh, cm, mm, in, pt, pc, px, rem, vmin, and vmax。省略长度单位可以使代码更简洁
/* bad */ .selector { margin-top: 0px; font-size: 0em; } /* good */ .selector { margin-top: 0; font-size: 0; }
-
11【参考】颜色值统一使用十六进制颜色码不要使用 rgb() 和颜色关键字。stylelint: color-named
颜色值统一使用十六进制颜色码以提高代码一致性
/* bad */ .selector { background-color: rgb(99, 76, 217); border-color: grey; color: #333; } /* good */ .selector { background-color: #634cd9; border-color: #808080; color: #333; }
除了代码一致性的考虑不建议使用 CSS 颜色关键字 还有以下原因
- 不便于对颜色进行微调比如想对
red
进行微调还要把它先转换成#f00
- CSS 颜色关键字有一百余个有许多不为人知的生僻颜色名例如
teal
- 不便于对颜色进行微调比如想对
-
12【推荐】十六进制颜色码统一使用小写字母。stylelint: color-hex-case
为了一致性我们需要约定在 hex 数值中统一使用大写或统一使用小写字母。
使用小写字母的理由是小写字母的字形相比大写字母更容易分辨
/* bad */ .selector { color: #FEFEFE; } /* good */ .selector { color: #fefefe; }
-
13【推荐】使用尽可能短的十六进制值。stylelint: color-hex-length
优先使用缩写形式3个字母的十六进制颜色值
/* bad */ .selector { color: #ffffff; } /* good */ .selector { color: #fff; }
-
14【参考】属性选择器的值始终用双引号包裹。stylelint: selector-attribute-quotes
属性选择器的值的引号只在某些情况下可以省略所以统一加上双引号以保证代码一致性。
/* bad */ input[type=text] { height: 20px; } /* good */ input[type="text"] { height: 20px; }
-
15【参考】注意选择器性能。
使用 CSS 选择器时应注意以下性能问题
- 使用 class 而不是原生元素标签
- 减少在经常出现的组件中使用个别属性选择器如
[class^="..."]
- 控制选择器的长度每个组合选择器内的条目尽量不超过 3 个
-
16【推荐】不要使用 id 选择器。stylelint: selector-max-id
id 选择器不可复用而且会带来过高的选择器优先级
-
17【参考】属性声明的顺序。
相关联的属性声明最好写成一组并按如下顺序排序
- 定位如 position、left、right、top、bottom、z-index
- 盒模型如 display、float、width、height、margin、padding、border
- 文字排版如 font、color、line-height、text-align
- 外观如 background
- 其他属性
「定位」和「盒模型」放在最前面是因为它们决定了元素的布局、位置和尺寸。「定位」排在「盒模型」之前是由于「定位」属性可以让元素脱离正常文本流从而使「盒模型」属性失效。
除了「定位」和「盒模型」其他属性都只在元素内部起作用不会对前两类属性的结果产生影响因此放在后面。
.declaration-order { /* 定位 */ position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 100; /* 盒模型 */ display: block; float: right; width: 100px; height: 100px; border: 1px solid #e5e5e5; /* 排版 */ font: normal 13px "Helvetica Neue", sans-serif; line-height: 1.5; color: #333; text-align: center; /* 外观 */ background-color: #f5f5f5; /* 其他 */ opacity: 1; }
-
18【推荐】不要使用 CSS 的 @import。
与
<link>
相比,@import
更慢增加了额外的页面请求并可能引发其他的意想不到的问题。应该避免使用它们而选择其他方案- 使用多个
<link>
标签 - 使用 CSS 预处理器如 Sass 或 Less 将样式编译到一个文件中
- 使用 Rails, Jekyll 或其他环境提供的功能来合并 CSS 文件
<!-- bad --> <style> @import url("more.css"); </style> <!-- good --> <link rel="stylesheet" href="more.css">
- 使用多个
-
19【参考】适时使用简写属性。stylelint: declaration-block-no-shorthand-property-overrides declaration-block-no-redundant-longhand-properties
常见的简写属性包括
font
background
padding
margin
border
border-radius
使用简写属性时需要显示地设置所有值。我们应该在真正需要设置所有值或大多数值时才使用简写属性例如只想设置下边距不应该用
margin: 0 0 10px;
而应该用margin-bottom: 10px;
。过度使用属性简写往往会导致更混乱的代码可能引起不必要的属性重写和意想不到的副作用。
/* bad */ .selector { margin: 0 0 10px; } /* good */ .selector { margin-bottom: 10px; }
-
20【强制】CSS 的注释。stylelint: no-invalid-double-slash-comments
在 CSS 中不要使用双斜杠注释
//
CSS 不支持这种语法Sass 和 Less 支持可能引起异常结果/* bad */ a { // color: pink; } // a { color: pink; } /* good */ a { /* color: pink; */ } /* a { color: pink; } */
在 Sass 或 Less 中可以使用双斜杠注释。但需要注意的是编译为 CSS 后代码中的双斜杠注释会被删除而
/* */
会被保留。 -
21【推荐】注释内容和注释符之间需留有一个空格。stylelint: comment-whitespace-inside
注释内容和注释符之间需留有一个空格以增加可读性
/* bad */ .selector { /*comment*/ /* comment */ /** *comment */ padding-left: 15px; } /* good */ .selector { /* comment */ /** * comment */ padding-left: 15px; }
-
22【参考】注释行上方需留有一行空行除非上一行是注释或块的顶部。stylelint: comment-empty-line-before
注释行上方需留有一行空行除非上一行是注释或块的顶部以提高可读性
-
23.1【推荐】代码组织顺序。
按如下顺序组织 Sass / Less 代码
@import
语句- 全局变量声明
- 样式声明
@import 'common/theme.scss'; $color-red: #f0f0f0; . selector { color: $color-red; }
-
23.2【推荐】命名。
同 class、id 的命名规则一致在 Sass / Less 中变量和 mixin 的命名规则为
- 全小写字母
- 多个单词间用中划线-分割
// Sass 变量和 mixin $my-variable: #f0f0f0; @mixin my-mixin($property) { background: $property; } // Less 变量和 mixin @my-variable: #f0f0f0; .my-mixin(@property) { background: @property; }
-
23.3【推荐】属性声明的顺序。
对于 Sass 和 Less块内的属性声明按如下顺序排序
- 标准属性声明除了 mixin 调用、extend 子级选择器的声明将它们按 CSS「属性声明的顺序」章节的规则进行排序
- mixin 调用Sass 的
@include
声明、Less 的 mixin 调用 - 嵌套的子级选择器将嵌套的选择器放到块的末尾并且在其上方保留一行空行
.btn { background: #ccc; font-weight: bold; @include transition(background 0.5s ease); .icon { margin-right: 10px; } }
-
23.4【参考】嵌套选择器的深度不要超过 3 层。stylelint: max-nesting-depth
当嵌套选择器的层级过深时可能带来一些副作用
- 与 HTML 结构强耦合难以复用
- 过高的选择器优先级
建议选择器嵌套层级不要超过 3 层
.container { .header { .user-name { // STOP不要再嵌套更深选择器 } } }
-
23.5【推荐】Sass 和 Less 的注释。
在 Sass 或 Less 中可以使用双斜杠注释。但需要注意的是编译为 CSS 后代码中的双斜杠注释会被删除而
/* */
会被保留。// 单行注释 .selector-a { padding-left: 15px; } /* * 多行注释 * 多行注释 */ .selector-b { margin-left: 15px; }
编译为 CSS 后双斜杠注释会被删除
.selector-a { padding-left: 15px; } /* * 多行注释 * 多行注释 */ .selector-b { margin-left: 15px; }
-
23.6【推荐】使用 Mixin 而不是 Extend 来达到 DRY 目的。
使用 Mixin (@mixin 和 @include 指令) 来让代码遵循 DRY 原则Don’t Repeat Yourself、增加抽象性和降低复杂度。
应避免使用 @extend 指令它不够直观且具有潜在风险尤其是在嵌套选择器中。即使继承的是顶层选择器如果选择器的顺序发生变化也可能引起问题。比如如果它们存在于其他文件而加载顺序发生了变化。
Extend 相比 Mixin 的好处是如果无参数的 mixin 被多处使用编译后会输出多段重复的代码。这时如果使用 @extend可以避免这个问题。但是 gzip 等压缩工具就可以解决重复代码的问题因此大多数情况下你只需要使用 mixin 来让代码符合 DRY 原则。
JavaScript 编码规约
(WC-JS)-JavaScript 编码规约
前言
JavaScript 是一门十分灵活的编程语言相比其他语言JS 的代码风格更加不受约束百花齐放因此更需要对编码风格进行约束以提高团队协作效率和代码可维护性。另外不少开发者对 JS 语言特性、最佳实践的了解并不全面比如集团内大量在写前端代码的后端、外包同学有必要通过规约和工具帮助他们了解常见的 JS 问题、规避不好的实现。
因此本规约主要围绕以下两部分内容展开
- 统一编码风格给出一套集团层面推荐的尽可能合理、完备、争议少的 JS 风格规范主要见「编码风格」章节
- 普及最佳实践普及 JS 的常见问题和语言特性帮助开发者规避一些不好的实现主要见「语言特性」章节
需要说明的是本规约面向 ES6+ 的 JavaScript 版本编写。ES6 经过几年的普及在集团内前端仓库的占比越来越大尤其是新项目大都使用 ES6 编码。对于还在使用 ES5 及之前版本 JS 的同学本规约的大部分内容同样适用只是有少部分 ES5 需要额外注意的地方可阅读本规约的「关于 ES5」章节。
ECMAScript 6.0即 ES6/ES2015作为 JavaScript 语言的下一代标准于 2015 年 6 月发布。ES6 引入了诸多新的语言特性与之前的 JS 版本有很大差异因此业界通常会以 ES6、ES5 模糊地区别 ES6 之后包括 ES2016/ES2017..、之前的 JS 版本本规约中也会使用这种模糊的叫法。
本规约篇幅较长一次性读完可能比较费力你也可以把它当成一个工具书遇到规约插件扫描出的 issue 时来这里寻找解释。
参与和反馈
对规约有任何意见和建议欢迎留言讨论。
1 编码风格
1.1 缩进
-
1.1.1【强制】使用 2 个空格缩进。eslint: indent no-tabs no-mixed-spaces-and-tabs
统一使用 2 个空格缩进不要使用 4 空格缩进、tab 缩进或混合使用空格与 tab 缩进
// bad function foo() { ∙∙∙∙let name; } // good function foo() { ∙∙let name; }
缩进和分号大概是 JS 中争议最大的两个风格问题。它们基本是纯粹的风格取向问题并无优劣之分。
就缩进而言有人觉得两空格缩进让代码更紧凑能看到更多代码有人觉得两空格区分度不强看起来费力。
综合参考业界主流编码规范和集团内主流编码风格本规约选择了『使用 2 个空格缩进』的风格。但这不是不可动摇的后面我们可能根据争议规约问题的全员投票结果和全集团代码风格的统计结果再做决定。
1.2 分号
-
1.2.1【强制】使用分号。eslint: semi
统一使用分号结束语句
// bad const foo = 'foo' // good const foo = 'foo';
在 JS 中语句的结尾可以不写分号。对于不写分号的语句JS 引擎会自动判断分号应该出现位置并自动添加它这一特性被称为 自动分号插入机制即 Automatic Semicolon Insertion简称 ASI。
ASI 在个别情况下的行为比较怪异会引起令人意外的效果。一种情况是会意外产生多行表达式请看下面两段代码
// 执行报错 => Uncaught TypeError: "world"[(1 , 2 , 3)].forEach is not a function const number = 0 const hello = 'world' [1, 2, 3].forEach((item) => { number += item }) // 执行报错 => Uncaught TypeError: "foo" is not a function const foo = 'foo' (async function bar() { }())
以上两段代码之所以执行报错是由于 ASI 添加分号后的效果为
const number = 0; const hello = 'world' [1, 2, 3].forEach((item) => { number += item; }); const foo = 'foo' (async function bar() { }());
可以看到ASI 没有在以 [ 和 ( 开头的行的上一行末尾添加分号导致出现了多行表达式。不过这种情况可以通过 ESLint 的 no-unexpected-multiline 规则规避在下一条规约「避免意外的多行表达式」中有详细描述。
除了会造成多行表达式还有一种不写分号时易出错的情况
function foo() { return 'bar' } foo() // => undefined
执行函数返回
undefined
是由于 ASI 添加分号后的效果为function foo() { return; 'bar'; }
return
语句在本行直接结束并返回了因此没有返回换行后的值。不过这种情况可以通过 ESLint 的 no-unreachable 规则规避。综上所述只要能规避 ASI 的负面作用通过 ESLint 规则辅助或牢记 ASI 的异常条件带不带分号就完全是一个风格取向问题有人觉得不加分号代码更加简洁有人觉得加分号语义更明确更符合从其他某语言过来的习惯。
综合参考业界主流编码规范和集团内主流编码风格本规约选择了『统一使用分号』的风格。但这不是不可动摇的后面我们可能根据争议规约问题的全员投票结果和全集团代码风格的统计结果再做决定。
-
1.2.2【强制】避免意外的多行表达式。eslint: no-unexpected-multiline
如上条规则所述需要避免由于不加分号导致出现意外的多行表达式。
以下几种情况 ASI 不会自动添加分号
- 语句有一个未闭合的括号、数组字面量、对象字面量或其他某种未有效结束语句的方式比如以
.
或,
结尾 - 该行是
--
或++
此时将对下一行起增/减作用 - 该行是一个
for()
、while()
、do
、if()
或else
且没有{
- 下一行的开头是
[
(
+
-
*
/
,
.
或其它一些在单个表达式中两个元素之间的二元操作符
// bad => Uncaught ReferenceError: bar is not defined const foo = {}; const bar = {} [foo, bar].forEach((item) => { item.baz = 'baz'; }); // good const foo = {}; const bar = {}; [foo, bar].forEach((item) => { item.baz = 'baz'; }); // bad => Uncaught TypeError: "foo" is not a function const foo = 'foo' (async function bar() { }()); // good const foo = 'foo'; (async function bar() { }());
- 语句有一个未闭合的括号、数组字面量、对象字面量或其他某种未有效结束语句的方式比如以
-
1.2.3【强制】禁止不必要的分号。eslint: no-extra-semi
- 1.2.4【强制】分号必须写在行尾。eslint: semi-style
1.3 逗号
-
1.3.1【强制】用逗号分隔的多行结构将逗号放到行尾。eslint: comma-style
// bad const hero = { firstName: 'Luffy' ,lastName: 'Monkey.D' }; const heroes = [ 'Luffy' , 'Ace' , 'Sabo' ]; // good const hero = { firstName: 'Luffy', lastName: 'Monkey.D', }; const heroes = [ 'Luffy', 'Ace', 'Sabo', ];
-
1.3.2【强制】用逗号分隔的多行结构始终加上最后一个逗号。eslint: comma-dangle
// bad const hero = { firstName: 'Luffy', lastName: 'Monkey.D' }; const heroes = [ 'Batman', 'Superman' ]; function createHero( firstName, lastName, inventorOf ) { // ... } // good const hero = { firstName: 'Luffy', lastName: 'Monkey.D', }; const heroes = [ 'Batman', 'Superman', ]; function createHero( firstName, lastName, inventorOf, ) { // ... } // good - 需注意使用扩展运算符的元素后面不能加逗号 function createHero( firstName, lastName, inventorOf, ...heroArgs ) { // ... }
这可以使增删行更加容易也会使 git diff 更加清晰
// bad - 没有结尾逗号时新增一行的 git diff 示例 const hero = { firstName: 'Florence', - lastName: 'Nightingale' + lastName: 'Nightingale', + inventorOf: ['coxcomb chart', 'modern nursing'] }; // good - 有结尾逗号时新增一行的 git diff 示例 const hero = { firstName: 'Florence', lastName: 'Nightingale', + inventorOf: ['coxcomb chart', 'modern nursing'], };
Babel 等编译器会在编译后的代码里去掉最后额外的逗号因此不必担心旧浏览器的兼容性问题。
1.4 换行
-
1.4.1【强制】大括号换行风格。eslint: brace-style
大括号换行采用 1TBS 风格即 One True Brace Style它要求大括号与控制语句放在同一行且单行代码块可不换行具体规则如下
- 左大括号
{
前面不换行后面换行 - 右大括号
}
前面换行 - 右大括号
}
后面是否换行有两种情况- 如果
}
终结了整个语句如条件语句、函数或类的主体则需要换行 - 如果
}
后面存在else
、catch
、while
等语句或存在逗号、分号、右小括号)
则不需要换行
- 如果
// bad - Stroustrup 风格 if (foo) { thing1(); } else { thing2(); } // bad - Allman 风格 if (foo) { thing1(); } else { thing2(); } // good - 1TBS 风格 if (foo) { thing1(); } else { thing2(); }
对于单行代码块大括号可以不换行不过出于可读性和便于新增语句的考虑不推荐这么做
// good - 单行代码块大括号可不换行允许但不推荐 function foo() { return true; }
- 左大括号
-
1.4.2【强制】省略大括号的单行语句前不要换行。eslint: nonblock-statement-body-position
当 if、else、while、do-while、for 内只有一条语句时可以省略大括号这时不要在单行语句前换行
// bad if (foo) return false; // good if (foo) return false;
-
1.4.3【强制】在点号之前换行。eslint: dot-location
JS 允许在成员表达式中的点号之前或之后放置一个换行符。
统一在点号操作符之前换行可以强调这是方法调用而不是新语句提高可读性和一致性。
// bad $('#items'). foo(). bar(). baz(); // good $('#items') .foo() .bar() .baz();
-
1.4.4【推荐】在长方法链式调用时进行换行。eslint: newline-per-chained-call
在使用多个推荐大于 4 个时方法链式调用时进行换行以提高可读性。另外在使用 jQuery 的链式操作时可通过增加缩进来体现链式操作主体的层级。
// bad $('#items').find('.selected').highlight().end().find('.open').updateCount(); // good $('#items') .find('.selected') .highlight() .end() .find('.open') .updateCount();
-
1.4.5【强制】对象的属性需遵循一致的换行风格。eslint: object-property-newline
要么所有属性都换行要么都写在一行。这有利于代码可读性。
// bad const obj = { foo: 1, bar: 2, baz: 3, }; // good const obj = { foo: 1, bar: 2, baz: 3, }; // good - 但注意不要超过单行最大字符限制 const obj = { foo: 1, bar: 2, baz: 3 };
-
1.4.6【强制】函数的小括号需遵循一致的换行风格。eslint: function-paren-newline
函数的左小括号后和右小括号前要么都有换行要么都无换行
// bad function foo(bar, baz, qux ) {} // good function foo(bar, baz, qux) {} // bad foo( () => { return bar; }); // good foo( () => { return bar; } );
-
1.4.7【强制】隐式返回的箭头函数体前不要换行。eslint: implicit-arrow-linebreak
// bad (foo) => bar; // good (foo) => bar;
1.5 空格
-
1.5.1【强制】空格风格。eslint: space-before-blocks keyword-spacing space-in-parens array-bracket-spacing object-curly-spacing space-infix-ops key-spacing arrow-spacing generator-star-spacing yield-star-spacing rest-spread-spacing template-curly-spacing block-spacing comma-spacing computed-property-spacing no-whitespace-before-property semi-spacing space-before-function-paren space-unary-ops switch-colon-spacing template-tag-spacing func-call-spacing
合理并一致地使用空格有助于提升代码可读性和可维护性。本条规约汇总了空格风格相关的规则以方便对照阅读。你不必现在就记住它们lint 和格式化工具会帮助你落地这些规则。
块的左大括号
{
前有一个空格space-before-blocks// bad if (foo){ bar(); } // good if (foo) { bar(); } // bad function test(){ console.log('test'); } // good function test() { console.log('test'); }
控制语句的关键字如
if
、else
、else if
等前后各一个空格位于行首的关键字前无需空格keyword-spacing// bad if(foo) { bar(); }else{ baz(); } // good if (foo) { bar(); } else { baz(); }
函数名与调用它的括号间无空格func-call-spacing
// bad fn (); // bad fn (); // good fn();
声明函数时对于命名函数参数的小括号前无空格对于匿名函数和 async 箭头函数参数的小括号前有空格space-before-function-paren
// bad - 命名函数参数前应无空格 function foo () {} // good function foo() {} // bad - 匿名函数参数前应有空格 const foo = function() {} // good const foo = function () {} // bad - async 箭头函数参数前应有空格 const foo = async(a) => await a // good const foo = async (a) => await a
箭头函数的箭头前后各留一个空格arrow-spacing
// bad ()=>{}; a =>a; // good () => {}; a => a;
generator
函数及yield*
表达式的 * 号前面无空格后面有一个空格generator-star-spacing yield-star-spacing// bad function *foo () { yield *bar(); } // good function* foo () { yield* bar(); } // bad const foo = function * () { yield * bar(); }; // good const foo = function* () { yield* bar(); };
小括号内部两侧无空格space-in-parens
// bad function bar( foo ) { return foo; } // good function bar(foo) { return foo; } // bad if ( foo ) { console.log( foo ); } // good if (foo) { console.log(foo); }
方括号内部两侧无空格array-bracket-spacing computed-property-spacing
// bad const foo = [ 1, 2, 3 ]; console.log(obj[ `key${i}` ]); // good const foo = [1, 2, 3]; console.log(obj[`key${i}`]);
大括号内部两侧有空格object-curly-spacing block-spacing
// bad const foo = {clark: 'kent'}; function foo() {return true;} // good const foo = { clark: 'kent' }; function foo() { return true; }
但对于模板字符串中的大括号内部两侧无空格template-curly-spacing
// bad const hello = `Hello, ${ username }!`; // good const hello = `Hello, ${username}!`;
如果使用了模板字符串的 tag 语法tag 后面无空格template-tag-spacing
// bad securityFn `Your input is ${input}`; // good securityFn`Your input is ${input}`;
操作符两侧有空格除了一元运算符、剩余和扩展操作符space-infix-ops space-unary-ops rest-spread-spacing
// bad const x=y+5; const isRight = result === 0? false: true; // good const x = y + 5; const isRight = result === 0 ? false : true; // bad - 一元运算符与操作对象间不应有空格 const x = ! y; // good const x = !y; // bad - 剩余和扩展操作符与操作对象间不应有空格 const [a, b, ... arr] = [1, 2, 3, 4, 5]; // good const [a, b, ...arr] = [1, 2, 3, 4, 5];
分号的前面无空格后面有空格语句末尾的分号后面无空格semi-spacing
// bad let foo ; for (let i = 0;i < 10;i++) {} // good let foo; for (let i = 0; i < 10; i++) {}
逗号的前面无空格后面有空格comma-spacing
// bad const arr = [1 , 2,3 ,4]; // good const arr = [1, 2, 3, 4];
定义对象字面量时key, value 之间有且只有一个空格不允许所谓的「水平对齐」key-spacing
// bad { a: 'short', looooongname: 'long', } // bad { a : 'short', looooongname: 'long', } // good { a: 'short', looooongname: 'long', }
-
1.5.2【强制】行尾不要留有空格。eslint: no-trailing-spaces
行尾的空格是多余的可能在 git diff 时造成干扰。
// bad const foo = 'foo'; const bar = 'bar'; // good const foo = 'foo'; const bar = 'bar';
-
1.5.3【强制】禁止出现多个空格。eslint: no-multi-spaces
// bad const foo = 'foo'; if (foo === bar) return false; // good const foo = 'foo'; if (foo === bar) return false;
1.6 空行
-
1.6.1【推荐】在文件末尾保留一个空行。eslint: eol-last
在非空文件中保留拖尾换行是一种常见的 UNIX 风格。它的好处同输出文件到终端一样方便在串联和追加文件时不会打断 shell 的提示。
统一在文件末尾保留一行空行即用一个换行符结束文件
// bad - 文件末尾未保留换行符 import { foo } from './Foo'; // ... export default foo; // bad - 文件末尾保留了2个换行符 import { foo } from './Foo'; // ... export default foo;↵ ↵ // good import { foo } from './Foo'; // ... export default foo;↵
-
1.6.2【推荐】在最后一个 import / require 语句后保留一个空行。eslint: import/newline-after-import
// bad import foo from './foo'; import bar from './bar'; const baz = 'baz'; const qux = 'qux'; // good import foo from './foo'; import bar from './bar'; const baz = 'baz'; const qux = 'qux';
-
1.6.3【强制】块的开始和结束不能是空行。eslint: padded-blocks
// bad function bar() { console.log(foo); } // good function bar() { console.log(foo); } // bad if (baz) { console.log(qux); } else { console.log(foo); } // good if (baz) { console.log(qux); } else { console.log(foo); }
-
1.6.4【参考】在块末和新语句间插入一个空行。
// bad if (foo) { return bar; } return baz; // good if (foo) { return bar; } return baz; // bad const obj = { foo() { }, bar() { }, }; return obj; // good const obj = { foo() { }, bar() { }, }; return obj;
-
1.6.5【推荐】类成员之间保留一个空行。eslint: lines-between-class-members
- 1.6.6【强制】禁止出现多个大于 2 个连续空行。eslint: no-multiple-empty-lines
1.7 最大字符数和最大行数
-
1.7.1【推荐】单行最大字符数100。eslint: max-len
过长的单行代码不易阅读和维护需要进行合理换行。
推荐单行代码不要超过 100 个字符除了以下两种情况
- 字符串和模板字符串
- 正则表达式
// bad const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy; // good const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy; // bad $.ajax({ method: 'POST', url: 'https://foo.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.')); // good $.ajax({ method: 'POST', url: 'https://foo.com/', data: { name: 'John' }, }) .done(() => console.log('Congratulations!')) .fail(() => console.log('You have failed this city.'));
-
1.7.2【参考】文件最大行数1000。SonarJs: javascript:S104
过长的文件不易阅读和维护最好对其进行拆分。
-
1.7.3【参考】函数最大行数80。SonarJs: javascript:S138
过长的函数不易阅读和维护最好对其进行拆分。
1.8 其他
-
1.8.1【强制】多行语句必须用大括号包裹单行语句推荐用大括号包裹。eslint: curly
当代码块内只有一条语句时JS 才允许省略大括号。因此多行语句必须用大括号包裹
// bad if (foo) bar(); baz(); // 这一行并不在 if 语句内 // good if (foo) { bar(); baz(); }
当代码块内只有一条语句时可以省略大括号注意此时单行语句必须跟条件写在同一行。但出于一致性和可扩展性考虑推荐对单行语句仍使用大括号包裹
// good - 允许但不推荐 if (foo) return false; // good - 推荐一致性和可扩展性更好 if (foo) { return false; }
-
1.8.2【强制】不要省略小数点前或小数点后的 0。eslint: no-floating-decimal
// bad const foo = .5; const bar = 2.; const baz = -.7; // good const foo = 0.5; const bar = 2.0; const baz = -0.7;
2 语言特性
2.1 变量声明
-
2.1.1【强制】不要使用未声明的变量。eslint: no-undef
不要使用未声明的变量和函数
// bad foo = 'foo'; // foo 将变成全局变量 bar(); // => Uncaught ReferenceError: bar is not defined // good const foo = 'foo'; function bar() { return 'bar'; } bar();
-
2.1.2【强制】使用 const 或 let 声明变量不要使用 var。eslint: no-var
从 ES6 开始可以使用
let
和const
关键字在块级作用域下声明变量。块级作用域在很多其他编程语言中都有使用这样声明的变量不会污染全局命名空间。// bad var foo = 'foo'; var bar; // good const foo = 'foo'; let bar;
-
2.1.3【强制】正确地使用 const 和 let。eslint: prefer-const
声明变量时应优先使用
const
只有当变量会被重新赋值时才使用let
// bad - 声明后未发生重新赋值应使用 const let flag = true; if (flag) { console.log(flag); } // good - 声明后发生重新赋值let 使用正确 let flag = true; if (flag) { flag = false; }
需注意数组和对象是一个引用对数组某项和对象某属性的修改并不是重新赋值因此多数情况下应用
const
声明// bad let arr = []; let obj = {}; arr[0] = 'foo'; obj.name = 'bar'; // good const arr = []; const obj = {}; arr.push('foo'); obj.name = 'bar';
-
2.1.4【强制】一条声明语句声明一个变量。eslint: one-var one-var-declaration-per-line
这样做更易于追加新的声明语句你不需要总去把最后的
;
改成,
了也更易于进行单步调试。// bad const foo = 1, bar = 2; // bad const foo = 1, bar = 2; // good const foo = 1; const bar = 2;
-
2.1.5【强制】声明的变量必须被使用。eslint: no-unused-vars
声明而未使用的变量、表达式可能带来潜在的问题也会给维护者造成困扰应将它们删除。
// bad - 未使用变量 foo const foo = 1; // good const foo = 1; doSomethingWith(foo); // bad - 只修改变量不认为是被使用 let bar = 1; bar = 2; bar += 1; // good let bar = 1; bar = 2; bar += 1; doSomethingWith(foo); // bad - 未使用参数 y function getX(x, y) { return x; } // good function getXPlusY(x, y) { return x + y; }
-
2.1.6【强制】不要在声明前就使用变量。eslint: no-use-before-define
在 ES6 中由于
const
和let
没有变量提升作用如果在声明前就使用变量运行时会直接报错// bad console.log(foo); // => Uncaught ReferenceError: foo is not defined let foo = 'foo'; // good let foo = 'foo'; console.log(foo); // => foo
在 ES5 中由于
var
的变量提升作用变量可以在声明前使用但这样做可能给人带来疑惑和隐患同样不推荐在声明前就使用变量// bad console.log(foo); // => undefined var foo = 'foo'; // good var foo = 'foo'; console.log(foo); // => foo
-
2.1.7【参考】哪里使用哪里声明。
在变量被使用前再进行声明而不是统一在块开始处进行声明。
ES6 提供的
let
和const
是块级作用域不存在类似var
的变量提升问题。因此我们可以把声明写在更合理的地方一般是变量被使用前而不是统一在块开始处进行声明。// bad - 如果权限校验checkUserPermission失败fetchData 是不必要的 function getData(id) { const data = fetchData(id); if (!checkUserPermission()) { return false; } if (data.foo === 'bar') { // ... } return data; } // good function getData(id) { if (!checkUserPermission()) { return false; } const data = fetchData(id); if (data.foo === 'bar') { // ... } return data; }
-
2.1.8【强制】禁止变量与外层作用域已存在的变量同名。eslint: no-shadow
如果变量与外层已存在变量同名会降低可读性也会导致内层作用域无法读取外层作用域的同名变量。
// bad const foo = 1; if (someCondition) { const foo = 2; console.log(foo); // => 2 } // good const foo = 1; if (someCondition) { const bar = 2; console.log(bar); // => 2 console.log(foo); // => 1 }
-
2.1.9【强制】不要重复声明变量和函数。eslint: no-redeclare
在 ES5 中尽管使用
var
重复声明不会报错但这样做会令人疑惑降低程序的可维护性。同理函数的声明也不要与已存在的变量和函数重名// bad var a = 'foo'; var a = 'bar'; function a() {} console.log(a); // => 'bar' // good var a = 'foo'; var b = 'bar'; function c() {} console.log(a); // => 'foo' // bad - arg 已作为函数参数声明 function myFunc(arg) { var arg = 'foo'; console.log(arg); } myFunc('bar'); // => 'foo' // good function myFunc(arg) { var otherName = 'foo'; console.log(arg); } myFunc('bar'); // => 'bar'
在 ES6 中使用
const
或let
重复声明变量会直接报错// bad const a = 'foo'; function a() {} // => Uncaught SyntaxError: Identifier 'a' has already been declared // good const a = 'foo'; function b() {} // bad - arg 已作为函数参数声明 function myFunc(arg) { const arg = 'foo'; console.log(arg); } myFunc('bar'); // => Uncaught SyntaxError: Identifier 'arg' has already been declared // good function myFunc(arg) { const otherName = 'foo'; console.log(arg); } myFunc('bar'); // => 'bar'
-
2.1.10【强制】禁止连续赋值。eslint: no-multi-assign
变量的连续赋值让人难以阅读和理解并且可能导致意想不到的结果如产生全局变量。
// bad - 本例的结果是 let 仅对 a 起到了预想效果b 和 c 都成了全局变量 (function test() { let a = b = c = 1; // 相当于 let a = (b = (c = 1)); })(); console.log(a); // throws ReferenceError console.log(b); // 1 console.log(c); // 1 // good (function test() { let a = 1; let b = a; let c = a; })(); console.log(a); // throws ReferenceError console.log(b); // throws ReferenceError console.log(c); // throws ReferenceError
-
2.1.11【参考】将 let 和 const 分别归类。
将
let
和const
归类写在一起可以提高代码整洁性。此外如果你想按变量的含义排序分组也是允许的。// bad let a; const b = 2; let c; const d = 4; let e; // good const b = 2; const d = 4; let a; let c; let e;
-
2.1.12【强制】禁止使用保留字命名变量。eslint: no-shadow-restricted-names
- 2.1.13【强制】不要将变量初始化成 undefined。eslint: no-undef-init
- 2.1.14【强制】禁止对类声明变量重新赋值。eslint: no-class-assign
- 2.1.15【强制】禁止修改 const 声明的变量。eslint: no-const-assign
2.2 原始类型
JS的数据类型包括 6 种原始类型primitive type即 Boolean, Null, Undefined, Number, String, Symbol (ES6 新定义)以及 Object 类型了解更多。这个章节主要介绍原始类型相关的规约。
-
2.2.1【强制】不要使用 new Number/String/Boolean。eslint: no-new-wrappers
使用 new Number/String//Boolean 声明不会有任何好处还会导致变量成为
object
类型可能引起 bug。// bad const num = new Number(0); const str = new String('foo'); const bool = new Boolean(false); console.log(typeof num, typeof str, typeof bool); // => object, object, object if (num) { // true对象相当于 true } if (bool) { // true对象相当于 true } // good const num = 0; const str = 'foo'; const bool = false; console.log(typeof num, typeof str, typeof bool); // => number, string, boolean if (num) { // false0 相当于 false } if (bool) { // false }
-
2.2.2【推荐】类型转换。
【数字】使用
Number()
或parseInt()
const str = '1'; // bad const num = +str; const num = str >> 0; const num = new Number(str); // good const num = Number(str); // good const num = parseInt(str, 10);
【字符串】使用
String()
const num = 1; // bad const str = new String(num); // typeof str is "object" not "string" const str = num + ''; // invokes num.valueOf() const str = num.toString(); // isn’t guaranteed to return a string // good const str = String(num);
【布尔值】使用
!!
const age = 0; // bad const hasAge = new Boolean(age); const hasAge = Boolean(age); // good const hasAge = !!age;
-
2.2.3【推荐】使用 parseInt() 方法时总是带上基数。eslint: radix
parseInt
方法的第一个参数是待转换的字符串第二个参数是转换基数。当第二个参数省略时parseInt
会根据第一个参数自动判断基数- 如果以 0x 开头则使用 16 作基数
- 如果以 0 开头则使用 8 作基数。正是这条规则经常导致错误ES5 规范中直接将这条规则移除即 ES5 及之后的执行环境以 0 开头也会使用 10 作为基数
- 其他情况则使用 10 作基数
虽然从 ES5 开始就移除了自动以 8 作基数的规则但有时难以保证所有的浏览器和 JS 执行环境都支持了这一特性。了解更多
因此推荐始终给
parseInt()
方法加上基数除非可以保证代码的执行环境不受上述特性的影响。// bad parseInt('071'); // => ES5 前的执行环境中得到的是 57 // good parseInt('071', 10); // => 71
-
2.2.4【强制】避免不必要的布尔类型转换。eslint: no-extra-boolean-cast
在
if
等条件语句中将表达式的结果强制转换成布尔值是多余的// bad if (!!foo) { // ... } while (!!foo) { // ... } const a = !!flag ? b : c; // good if (foo) { // ... } while (foo) { // ... } const a = flag ? b : c;
-
2.2.5【强制】字符串优先使用单引号。eslint: quotes
// bad const name = "tod"; // 模板字符串中应包含变量或换行否则需用单引号 const name = `tod`; // good const name = 'tod';
-
2.2.6【推荐】使用模板字符串替代字符串拼接。eslint: prefer-template
模板字符串让代码更简洁可读性更强
// bad function getDisplayName({ nickName, realName }) { return nickName + ' (' + realName + ')'; } // good function getDisplayName({ nickName, realName }) { return `${nickName} (${realName})`; }
-
2.2.7【强制】禁止不必要的转义字符。eslint: no-useless-escape
转义字符会大大降低代码的可读性因此尽量不要滥用它们。
// bad const foo = '\'this\' \i\s \"quoted\"'; // good const foo = '\'this\' is "quoted"'; const foo = `'this' is "quoted"`;
-
2.2.8【推荐】不要在普通字符串中出现模板字符串占位语法。eslint: no-template-curly-in-string
- 2.2.9【强制】使用 Number.isNaN()而不是直接与 NaN 进行比较。eslint: use-isnan
- 2.2.10【强制】同 typeof 表达式结果进行比较的值必须是有效的字符串。eslint: valid-typeof
- 2.2.11【强制】禁止使用多行字符串。eslint: no-multi-str
- 2.2.12【强制】禁用八进制字面量。eslint: no-octal
- 2.2.13【强制】禁止在字符串字面量中使用八进制转义序列。eslint: no-octal-escape
- 2.2.14【强制】禁止不必要的字符串拼接。eslint: no-useless-concat
- 2.2.15【强制】禁止使用 new Symbol。eslint: no-new-symbol
- 2.2.16【推荐】创建 Symbol 时需要传入参数以便区分。eslint: symbol-description
2.3 数组
-
2.3.1【强制】使用字面量创建数组。eslint: no-array-constructor
不要使用
new Array()
和Array()
创建数组除非为了构造某一长度的空数组。// bad const a = new Array(1, 2, 3); const b = Array(1, 2, 3); // good const a = [1, 2, 3]; const b = new Array(500); // 构造长度为 500 的空数组
-
2.3.2【强制】某些数组方法的回调函数中必须包含 return 语句。eslint: array-callback-return
以下数组方法
map
,filter
,from
,every
,find
,findIndex
,reduce
,reduceRight
,some
,sort
的回调函数中必须包含return
语句否则可能会产生误用或错误。一个常见的误用是本该用
forEach
的场景却用了map
// 欲将 ['a', 'b', 'c'] 转换成 {a: 0, b: 1, c: 2} const myArray = ['a', 'b', 'c']; const myObj = {}; // bad - map 应该用于构建一个新数组单纯想遍历数组应使用 forEach myArray.map((item, index) => { myObj[item] = index; }); // good myArray.forEach((item, index) => { myObj[item] = index; });
某些方法漏掉
return
还可能引起错误// 欲将 ['a', 'b', 'c'] 转换成 {a: 0, b: 1, c: 2} const myArray = ['a', 'b', 'c']; // bad => Uncaught TypeError: Cannot set property 'b' of undefined const myObj = myArray.reduce((memo, item, index) => { memo[item] = index; }, {}); // good const myObj = myArray.reduce((memo, item, index) => { memo[item] = index; return memo; }, {});
-
2.3.3【参考】使用扩展运算符 … 处理数组。
ES6 提供了扩展运算符
...
可以简化一些数组操作。数组拼接
// bad const array1 = [1, 2].concat(array); // good const array1 = [1, 2, ...array]
数组复制
// bad const array1 = []; for (let i = 0; i < array.length; i += 1) { array1[i] = array[i]; } // bad const array1 = array.map(item => item); // good const array1 = [...array];
将类数组结构有 Iterator 接口的对象转换为数组
// bad const foo = document.querySelectorAll('.foo'); // good const nodes = Array.from(foo); // good const nodes = [...foo]; const uniqueNodes = [...new Set(foo)]; // 可以利用 Set 和 ... 将数组去重
特殊的遍历可迭代对象时使用
Array.from
而不是...
以免创建一个临时数组// bad const baz = [...foo].map(bar); // good const baz = Array.from(foo, bar);
-
2.3.4【推荐】使用解构获取数组元素。
使用 ES6 提供的解构方法获取数组元素
// bad const arr = [1, 2, 3, 4]; const first = arr[0]; const second = arr[1]; // good const arr = [1, 2, 3, 4]; const [first, second] = arr;
函数有多个返回值时应使用对象解构而不是数组解构因为数组解构需要考虑返回值的位置
// bad function giveMeDivPosition(div) { return [left, right, top, bottom]; } const [left, _, top] = giveMeDivPosition(div); // good function giveMeDivPosition(div) { return { left, right, top, bottom }; } const { left, top } = giveMeDivPosition(div);
-
2.3.5【强制】禁用稀疏数组。eslint: no-sparse-arrays
2.4 对象
-
2.4.1【强制】使用字面量创建对象。eslint: no-new-object
// bad const obj = new Object(); // good const obj = {};
-
2.4.2【强制】使用对象属性和方法的简写语法。eslint: object-shorthand
ES6 提供了对象属性和方法的简写语法可以使代码更加简洁
const value = 'foo'; // bad const atom = { value: value, addValue: function (value) { return value + ' added'; }, }; // good const atom = { value, addValue(value) { return value + ' added'; }, };
-
2.4.3【参考】将对象的简写属性写在一起。
将简写的属性写在一起置于对象的起始或末尾可以提高代码整洁性。当然如果你出于属性的含义或其他考虑进行排序也是允许的。
const anakinSkywalker = 'Anakin Skywalker'; const lukeSkywalker = 'Luke Skywalker'; // bad const obj = { episodeOne: 1, twoJediWalkIntoACantina: 2, lukeSkywalker, episodeThree: 3, mayTheFourth: 4, anakinSkywalker, }; // good const obj = { lukeSkywalker, anakinSkywalker, episodeOne: 1, twoJediWalkIntoACantina: 2, episodeThree: 3, mayTheFourth: 4, };
-
2.4.4【强制】对象字面量的属性名不要用引号包裹除非包含特殊字符。eslint: quote-props
这样更加简洁也有助于语法高亮和一些 JS 引擎的优化。
// bad const bad = { 'foo': 3, 'bar': 4, 'data-blah': 5, 'one two': 12, }; // good const good = { foo: 3, bar: 4, 'data-blah': 5, 'one two': 12, };
-
2.4.5【强制】优先使用 . 访问对象的属性。eslint: dot-notation
这样可以提高代码可读性。
[]
仅应在访问动态属性名或包含特殊字符的属性名时被使用。const obj = { active: true, [getDynamicKey()]: 'foo', 'data-bar': 'bar', }; // bad const isActive = obj['active']; // good const isActive = obj.active; const foo = obj[getDynamicKey()]; const bar = obj['data-bar'];
-
2.4.6【推荐】使用扩展运算符 … 处理对象。
替代
Object.assign
方法来进行对象的浅拷贝// very bad - original 会被影响 const original = { a: 1, b: 2 }; const copy = Object.assign(original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 } original => { a: 1, b: 2, c: 3 } delete copy.a; // copy => { b: 2, c: 3 } original => { b: 2, c: 3 } // bad const original = { a: 1, b: 2 }; const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 } // good const original = { a: 1, b: 2 }; const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }
获取排除某些属性的新对象
// good const copy = { a: 1, b: 2, c: 3 }; const { a, ...noA } = copy; // noA => { b: 2, c: 3 }
-
2.4.7【推荐】使用解构。eslint: prefer-destructuring
获取对象的同名属性、多个属性时使用解构让代码更简洁也可以减少为了使用属性而创建的临时引用。
// bad function getFullName(user) { const firstName = user.firstName; const lastName = user.lastName; return `${firstName} ${lastName}`; } // good function getFullName(user) { const { firstName, lastName } = user; return `${firstName} ${lastName}`; } // best function getFullName({ firstName, lastName }) { return `${firstName} ${lastName}`; }
-
2.4.8【参考】对象的动态属性名应直接写在字面量定义中。
ES6 允许在新建对象字面量时使用表达式作为属性名这样可以将所有属性定义在一个地方。
function getKey(k) { return `a key named ${k}`; } // bad const obj = { id: 1, name: 'tod', }; obj[getKey('foo')] = 'foo'; // good const obj = { id: 1, name: 'tod', [getKey('foo')]: 'foo', };
-
2.4.9【强制】不要直接在对象上调用 Object.prototypes 上的方法。eslint: no-prototype-builtins
不要直接在对象上调用
Object.prototypes
上的方法例如hasOwnProperty
、propertyIsEnumerable
、isPrototypeOf
。这些方法可能会被对象上的属性覆盖导致错误
const obj = { foo: 'foo', hasOwnProperty: false, }; const objNull = Object.create(null); // bad => Uncaught TypeError: obj.hasOwnProperty is not a function console.log(obj.hasOwnProperty('foo')); console.log(objNull.hasOwnProperty('foo')); // good console.log(Object.prototype.hasOwnProperty.call(obj, 'foo')); console.log(Object.prototype.hasOwnProperty.call(objNull, 'foo'));
-
2.4.10【强制】对象中禁止出现重复命名的 key。eslint: no-dupe-keys
- 2.4.11【强制】禁止将全局对象 Math、JSON、Reflect 当作函数进行调用。eslint: no-obj-calls
- 2.4.12【强制】不要在解构中出现空模式。eslint: no-empty-pattern
- 2.4.13【强制】禁止扩展原生对象。eslint: no-extend-native
- 2.4.14【强制】对象的属性名不要使用无必要的计算属性。eslint: no-useless-computed-key
- 2.4.15【强制】禁止在解构 / import / export时进行无用的重命名。eslint: no-useless-rename
2.5 函数
-
2.5.1【强制】不要使用 Function 构造函数创建函数。eslint: no-new-func
使用
new Function
创建函数会像eval()
方法一样执行字符串带来安全隐患// bad const sum = new Function('a', 'b', 'return a + b'); // good const sum = (a, b) => (a + b);
-
2.5.2【强制】不要在块中使用函数声明。eslint: no-inner-declarations
在非函数块如
if
、while
等中不要使用函数声明// bad - 函数声明不是块作用域而是函数作用域因此在块外也能使用函数容易引起误解 if (true) { function test() { console.log('test'); } } test(); // => test // good - 函数表达式可以清晰地说明函数能否在块外使用 // 不能在块外使用 if (true) { const test = function () { console.log('test'); }; } test(); // => Uncaught ReferenceError: test is not defined // 能在块外使用 let test; if (true) { test = function () { console.log('test'); }; } test(); // => test
-
2.5.3【参考】使用函数表达式替代函数声明。
这样可以保证函数不能在定义前被调用。
函数声明会被提升到当前作用域的顶部因此函数可以在声明语句前就被调用这会影响代码的可读性与可维护性。
// bad function foo() { // ... } // good const foo = () => { // ... }; const foo = function () { // ... }; // 有些规范提出应该给函数表达式起一个不同于被赋值变量名的名字以达到易于调试、查看错误堆栈等目的 // 事实上代码在目前浏览器中或者经过 Babel 转码后匿名函数表达式也能够方便地查看堆栈。所以除非你出于某些目的想给函数起一个不同于被赋值变量的名字否则直接使用匿名函数表达式 const foo = function foo_more_descriptive_name() { // ... };
-
2.5.4【强制】回调函数使用箭头函数而不是匿名函数。eslint: prefer-arrow-callback
ES6 提供的箭头函数可以解决
this
指向的问题而且语法更简洁。// bad [1, 2, 3].map(function (x) { const y = x + 1; return x * y; }); // good [1, 2, 3].map((x) => { const y = x + 1; return x * y; });
-
2.5.5【参考】箭头函数编码风格。eslint: arrow-parens arrow-body-style
箭头函数参数的小括号、函数体的大括号在某些时候可以省略这可能导致编码风格不统一因此建议如下的编码风格
-
函数体风格
当函数体只包含一条
return
语句时可以省略函数体大括号和return
以使代码更简洁。我们推荐使用这个 ES6 提供的语法糖它可以让书写和阅读更简洁。但你也可以选择始终加上大括号和
return
以方便后续在函数体内增加语句。// good - 函数体包含多条语句时始终加上大括号 [1, 2, 3].map((number) => { const nextNumber = number + 1; return `A string containing the ${nextNumber}.`; }); // good - 函数体只包含一条 `return` 语句时可以省略大括号和 `return`这样代码更简洁 [1, 2, 3].map(number => `A string containing the ${number + 1}.`); // good - 也可以选择始终不省略大括号不使用简写语法糖以方便后续在函数体内增加语句 [1, 2, 3].map((number) => { return `A string containing the ${number + 1}.`; });
当
return
的内容为对象或者有多行时需要用小括号包裹// bad - Uncaught SyntaxError: Unexpected token [1, 2, 3].map((item) => { foo: item, bar: item + 1, }); // good [1, 2, 3].map((item) => ({ foo: item, bar: item + 1, })); // bad ['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod, ) ); // good ['get', 'post', 'put'].map(httpMethod => ( Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod, ) ));
-
函数参数风格
当函数只有一个参数且函数体为
return
简写语法时可以省略包裹参数的小括号以使代码更简洁。我们建议仅在这种情况下省略包裹参数的小括号其余情况都不要省略小括号。但你也可以选择始终加上小括号以方便后续可能要增加参数。
// good - 未使用 return 简写语法时参数始终加上小括号 [1, 2, 3].map((number) => { const nextNumber = number + 1; return `A string containing the ${nextNumber}.`; }); // good - 使用 return 简写语法、且只有一个参数时可以省略参数的小括号这样代码更简洁 [1, 2, 3].map(x => x * x); // good - 也可以选择始终不省略参数的小括号以方便后续可能要增加参数 [1, 2, 3].map((x) => x * x);
-
-
2.5.6【强制】不要将函数参数命名为 arguments。
这会覆盖掉函数作用域中的
arguments
对象。// bad function foo(name, options, arguments) { // ... } // good function foo(name, options, args) { // ... }
-
2.5.7【推荐】使用 rest 操作符替代 arguments 对象。eslint: prefer-rest-params
ES6 提供了 rest 操作符
...
与arguments
相比可以更清晰地聚合函数的剩余参数。此外...
得到的是一个真正的数组而arguments
得到的则是类数组结构。// bad function foo(a, b) { const args = Array.prototype.slice.call(arguments, foo.length); console.log(args); } foo(1, 2, 3, 4); // => [3, 4] // good function foo(a, b, ...args) { console.log(args); } foo(1, 2, 3, 4); // => [3, 4]
-
2.5.8【推荐】使用扩展运算符替代 apply()。eslint: prefer-spread
// bad const args = [1, 2, 3, 4]; Math.max.apply(Math, args); // good const args = [1, 2, 3, 4]; Math.max(...args);
-
2.5.9【推荐】使用默认参数语法。
ES6 中引入了默认参数语法相比之前为参数赋默认值的方法更加简洁、可读性更好。重新对参数赋值是不推荐的行为且当参数的布尔类型转换结果是
false
时可能会错误地被赋予默认值。因此当函数参数需要默认值时使用默认参数语法而不是去修改参数
// bad const multiple = (a, b) => { a = a || 0; b = b || 0; return a * b; } // good const multiple = (a = 0, b = 0) => { return a * b; }
-
2.5.10【推荐】有默认值的函数参数需要放到参数列表的最后。
否则你将无法享受到默认参数的便利只能通过传
undefined
触发参数使用默认值。// bad function multiply(a = 1, b) { return a * b; } const x = multiply(42); // => NaN const y = multiply(undefined, 42); // => 42 // good function multiply(a, b = 1) { return a * b; } const x = multiply(42); // => 42
-
2.5.11【推荐】不要修改函数参数。eslint: no-param-reassign
不要修改引用类型的参数这可能导致作为入参的原变量发生变化
// bad const f1 = function f1(obj) { obj.key = 1; } const originalObj = { key: 0 }; f1(originalObj); console.log(originalObj); // => { key: 1 } // good const f2 = function f2(obj) { const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1; }
更不要给参数重新赋值这可能导致意外的行为和内核优化问题
// bad function foo(bar, baz) { if (!baz) { bar = 1; } } // good function foo(bar, baz) { let qux = bar; if (!baz) { qux = 1; } }
-
2.5.12【强制】立即执行函数表达式IIFE需要用小括号包裹。eslint: wrap-iife
IIFE 是一个独立的执行单元将它用小括号包裹可以更清晰的体现这点。需要提醒的是由于 ES6 模块语法的引入你可能不再需要使用 IIFE 了。
// bad const x = function () { return { y: 1 }; }(); // good const x = (function () { return { y: 1 }; }());
-
2.5.13【推荐】函数的复杂度不应过高。SonarJs: javascript:FunctionComplexity javascript:S3776
过高的复杂度意味着代码难以维护和测试。我们推荐函数的复杂度不要超过以下阈值
- 圈复杂度不超过 10
- 认知复杂度不超过 15
-
2.5.14【推荐】函数的参数不应过多。SonarJs: javascript:ExcessiveParameterList
如果函数的参数过多将不利于函数的维护和调用。这时你需要考虑是否函数做了太多的事情是否有必要对其进行拆分。
如果必须使用过多的参数可以考虑用对象代替参数列表
// bad function doSomething(param1, param2, param3, param4, param5, param6, param7, param8) { // ... } doSomething(1, 2, 3, 4, 5, 6, 7, 8); // good function doSomething({ param1, param2, param3, param4, param5, param6, param7, param8 }) { // ... } doSomething({ param1: 1, param2: 2, param3: 3, param4: 4, param5: 5, param6: 6, param7: 7, param8: 8 });
-
2.5.15【推荐】generator 函数内应该有 yield 语句。eslint: require-yield
如果一个
generator
中没有yield
语句那么这个generator
就不是必须的。// bad function* foo() { return 10; } // good function* foo() { yield 5; return 10; }
-
2.5.16【参考】优先使用 JS 提供的高阶函数进行迭代运算。
需要迭代运算时应优先使用 JS 提供的高阶函数减少直接使用 for 循环包括 for-in 和 for-of。
如使用 map() / every() / filter() / find() / findIndex() / reduce() / some() / … 来迭代数组使用 Object.keys() / Object.values() / Object.entries() 方法来迭代对象
const numbers = [1, 2, 3, 4, 5]; // bad let sum = 0; for (let num of numbers) { sum += num; } console.log(sum); // => 15; // good let sum = 0; numbers.forEach((num) => { sum += num; }); console.log(sum); // => 15; // best const sum = numbers.reduce((total, num) => total + num, 0); console.log(sum); // => 15; // bad const increasedByOne = []; for (let i = 0; i < numbers.length; i++) { increasedByOne.push(numbers[i] + 1); } // good const increasedByOne = []; numbers.forEach((num) => { increasedByOne.push(num + 1); }); // best const increasedByOne = numbers.map(num => num + 1);
-
2.5.17【强制】不要使用 async 函数作为 Promise 的 executor。eslint: no-async-promise-executor
- 2.5.18【推荐】Promise 的 reject 需要传入 Error 对象。eslint: prefer-promise-reject-errors
- 2.5.19【强制】函数的参数列表中禁止出现重复命名的参数。eslint: no-dupe-args
- 2.5.20【强制】不要对函数声明重新赋值。eslint: no-func-assign
- 2.5.21【推荐】不要在循环中使用 await。eslint: no-await-in-loop
- 2.5.22【推荐】避免因使用 await 或 yield 导致的竞争性赋值。eslint: require-atomic-updates
- 2.5.23【强制】禁止使用 arguments.caller 和 arguments.callee。eslint: no-caller
- 2.5.24【强制】不要出现空函数。eslint: no-empty-function
- 2.5.25【强制】禁止不必要的 .bind() 调用。eslint: no-extra-bind
- 2.5.26【强制】禁止在循环中的函数内出现外部作用域中定义且会发生变化的变量以防止闭包副作用。eslint: no-loop-func
- 2.5.27【强制】禁止单独 new 一个构造函数而不用于赋值或比较。eslint: no-new
- 2.5.28【强制】禁止在 return 语句中赋值。eslint: no-return-assign
- 2.5.29【强制】禁止不必要的 return await。eslint: no-return-await
- 2.5.30【强制】禁止多余的 return; 语句。eslint: no-useless-return
-
2.5.31【强制】避免箭头函数可能与比较操作符产生混淆的情况。eslint: no-confusing-arrow
// bad - 可能让人误以为是 a >= 1 ? 2 : 3 let x = a => 1 ? 2 : 3; // good let x = a => (1 ? 2 : 3);
-
2.5.32【强制】禁止在调用构造函数时省略小括号。eslint: new-parens
2.6 类
-
2.6.1【推荐】使用 class 语句声明类而不是使用 prototype。
class
语句是 ES6 中引入的用于声明类的语法糖更加简洁易维护。// bad function Person() { this.age = 1; } Person.prototype.growOld = function () { this.age += 1; } // good class Person { constructor() { this.age = 1; } growOld() { this.age += 1; } }
-
2.6.2【推荐】使用 extends 语句进行类的继承。
extends
是用于原型继承的内建方法不会破坏instanceof
。// bad const inherits = require('inherits'); function PeekableQueue(contents) { Queue.apply(this, contents); } inherits(PeekableQueue, Queue); PeekableQueue.prototype.peek = function () { return this.queue[0]; }; // good class PeekableQueue extends Queue { peek() { return this.queue[0]; } }
-
2.6.3【强制】避免不必要的 construtor。eslint: no-useless-constructor
ES6 class 会提供一个默认的
construtor
空construtor
或者只调用父类的construtor
是不必要的。// bad - 以下两种 construtor 可以省略 class Parent { constructor() { } method() { // ... } } class Child extends Parent { constructor (value) { super(value); } method() { // ... } } // good class Parent { method() { // ... } } class Child extends Parent { method() { // ... } }
-
2.6.4【强制】子类的 construtor 中必须使用 super非子类的 construtor 中不能使用 super。eslint: constructor-super
// bad - 非子类的 constructor 不能使用 super class Parent { constructor() { super(); this.name = 'parent'; } } // good class Parent { constructor() { this.name = 'parent'; } } // bad - 子类的 constructor 必须使用 super class Child extends Parent { constructor() { this.name = 'child'; } } // good class Child extends Parent { constructor (value) { super(value); this.name = 'foo'; } }
-
2.6.5【强制】在 constructor 中禁止在调用 super() 前使用 this 或 super 关键字。eslint: no-this-before-super
// bad - this 必须在调用 super() 后使用 class Child extends Parent { constructor (value) { this.name = 'foo'; super(value); } } // good class Child extends Parent { constructor (value) { super(value); this.name = 'foo'; } }
-
2.6.6【强制】避免重复的类成员命名。eslint: no-dupe-class-members
重复的类成员声明最终生效的将是最后一个
// bad class Foo { bar() { console.log('bar'); } bar() { console.log('baz'); } } const foo = new Foo(); foo.bar(); // => baz // good class Foo { bar() { console.log('bar'); } }
2.7 模块
-
2.7.1【推荐】使用 ES6 modules 而非其他非标准的模块系统。eslint: import/no-amd
使用 ES6 modules (
import
/export
)而不是其他非标准的模块系统如 CommonJS、AMD、CMD。ES6 modules 作为标准代表着未来让我们拥抱未来吧。
// bad const React = require('react'); module.exports = React.Component; // good import React, { Component } from 'react'; export default Component;
-
2.7.2【强制】不要用多个 import 引入同一模块。eslint: import/no-duplicates no-duplicate-imports
多条
import
语句引入了同一模块会降低可维护性你需要将它们合成一条语句。// bad import React from 'react'; import { Component } from 'react'; // good import React, { Component } from 'react';
-
2.7.3【强制】import 语句需要放到模块的最上方。eslint: import/first
由于
import
语句会被变量提升将它们放到模块的最上方以防止异常行为。// bad import foo from 'foo'; foo.init(); import bar from 'bar'; bar.init(); // good import foo from 'foo'; import bar from 'bar'; foo.init(); bar.init();
-
2.7.4【推荐】当模块内只有一个 export 时使用 default export。eslint: import/prefer-default-export
// bad - 代码中只有一个 export 时不使用命名的 export export const foo = 'foo'; // good export default 'foo';
-
2.7.5【参考】不要在 import 时直接 export。
虽然一行代码更简洁但这不利于代码的可读性和一致性。
// bad export { Com as Component } from 'react'; // good import { Component } from 'react'; export default Component;
-
2.7.6【参考】import 语句的排序。eslint: import/order
import
语句需按以下规则排序- 先
import
第三方模块再import
自己工程里的模块 - 先
import
绝对路径再import
相对路径
// bad import foo from 'components/foo'; import './index.scss'; import React from 'react'; // good import React from 'react'; import foo from 'components/foo'; import './index.scss';
- 先
-
2.7.7【强制】不要产生循环引用和自引用。eslint: import/no-cycle import/no-self-import
`
javascript
// bad - 产生了循环引用
// foo.js 中
import bar from ‘./bar’;// bar.js 中
import foo from ‘./foo’;
// bad - 引用了自己
// foo.js 中
import foo from ‘./foo’;
// index.js 中
import index from ‘.’;
### 2.8 操作符
- 2.8.1【推荐】使用严格相等运算符。eslint: [eqeqeq](https://eslint.org/docs/rules/eqeqeq)
非严格相等运算符`==` 和 `!=`会在比较前将被比较值转换为相同类型对于不熟悉 JS 语言特性的人来说这可能造成不小的隐患。[了解更多](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness)
因此一般情况下我们应该使用严格比较运算符 `===` 和 `!==`进行比较。如果要比较的两个值类型不同应该显性地将其转换成相同类型再进行严格比较而不是依赖于 `==` 和 `!=` 的隐式类型转换。
```javascript
const id = '83949';
// bad - 为了兼容 id 可能是字符串的情况而有意使用 == 与数字比较
if (id == 83949) {
// do something
}
// good - 如果 id 可能是字符串应该先将其进行类型转换再使用 === 进行比较
if (Number(id) === 83949) {
// do something
}
-
2.8.2【推荐】不要使用一元自增自减运算符。eslint: no-plusplus
不要使用一元自增自减运算符
++
和--
除非在for
循环条件中。++
和--
会带来值是否会提前变化以及由自动添加分号机制造成理解成本推荐使用num += 1
来代替num++
。但出于习惯在for
循环的条件中依然可以使用自增自减运算符。let num = 1; // bad num++; --num; // good num += 1; num -= 1;
-
2.8.3【强制】不要使用 void 运算符。eslint: no-void
在很老版本的 JS 中
undefined
值是可变的因此使用void
语句一般是用来得到一个undefined
值。而在新版本的 JS 中上面的问题已不复存在。因此出于程序可读性的考虑禁止使用void
运算符。// bad const foo = void 0; // good const foo = undefined;
-
2.8.4【强制】不要使用嵌套的三元表达式。eslint: no-nested-ternary
嵌套的三元表达式会降低代码可读性。
// bad const foo = bar ? baz : qux === quxx ? bing : bam; // good const qu = qux === quxx ? bing : bam; const foo = bar ? baz : qu;
-
2.8.5【强制】避免不必要的三元表达式。eslint: no-unneeded-ternary
// bad const foo = a ? a : b; const bar = c ? true : false; const baz = c ? false : true; // good const foo = a || b; const bar = !!c; const baz = !c;
-
2.8.6【强制】混合使用多种操作符时用小括号包裹分组。eslint: no-mixed-operators
这可以更清晰地表达代码意图提高可读性。四则运算符
+
,-
,*
,/
可以不包裹因为大多数人熟知它们的优先级。了解更多// bad const foo = a && b < 0 || c > 0 || d + 1 === 0; // good const foo = (a && b < 0) || c > 0 || (d + 1 === 0); // bad const bar = a ** b - 5 % d; // good const bar = (a ** b) - (5 % d); // bad - 有些新手可能误以为执行顺序是 (a || b) && c if (a || b && c) { return d; } // good if (a || (b && c)) { return d; } // good - 四则运算可以不用小括号包裹 const bar = a + b / c * d;
-
2.8.7【强制】不要与负零进行比较。eslint: no-compare-neg-zero
- 2.8.8【强制】禁止对关系运算符左边的运算元使用否定操作符。eslint: no-unsafe-negation
- 2.8.9【强制】禁止使用逗号操作符除非用于 for 循环条件或明确用小括号包裹。eslint: no-sequences
- 2.8.10【推荐】不要使用按位操作符。eslint: no-bitwise
- 2.8.11【推荐】尽可能使用简写形式的赋值操作符。eslint: operator-assignment
2.9 控制语句
-
2.9.1【强制】不要让 case 语句落空。eslint: no-fallthrough
使用
break
、return
或throw
来结束case
语句不要让case
语句落空。// bad switch(foo) { case 1: doSomething(); case 2: doSomethingElse(); default: doSomething(); } // good switch(foo) { case 1: doSomething(); break; case 2: doSomethingElse(); break; default: doSomething(); }
-
2.9.2【推荐】switch 语句需要始终包含 default 分支。eslint: default-case
在使用
switch
语句时有时会出现因开发者忘记设置default
而导致错误因此建议总是给出default
。如果有意省略default
请在switch
语句末尾用// no default
注释指明// bad let foo; switch (bar) { case 1: foo = 2; break; } // good let foo; switch (bar) { case 1: foo = 2; break; default: foo = 0; } // good - 如果有意省略 default请在 switch 语句末尾用 `// no default` 注释指明 let foo = 0; switch (bar) { case 1: foo = 2; break; // no default }
-
2.9.3【参考】switch 语句应包含至少 3 个条件分支。SonarJs: javascript:S1301
switch
语句在有许多条件分支的情况下可以使代码结构更清晰。但对于只有一个或两个条件分支的情况更适合使用if
语句if
语句更易于书写和阅读。// bad let foo; switch (bar) { case 1: foo = 2; break; default: foo = 0; } // good let foo; if (bar === 1) { foo = 2; } else { foo = 0; }
-
2.9.4【强制】for 循环中的计数器应朝着正确方向移动。eslint: for-direction
当
for
循环中更新子句的计数器朝着错误的方向移动时循环的终止条件将永远无法达到这会导致死循环的出现。这时要么是程序出现了错误要么应将for
循环改为while
循环。// bad for (let i = 0; i < length; i--) { // do something } // good for (let i = 0; i < length; i++) { // do something }
-
2.9.5【推荐】for-in 循环中需要对 key 进行验证。eslint: guard-for-in
使用
for-in
循环时需要避免对象从原型链上继承来的属性也被遍历出来因此保险的做法是对 key 是否是对象自身的属性进行验证// bad for (const key in foo) { doSomething(key); } // good for (const key in foo) { if (Object.prototype.hasOwnProperty.call(foo, key)) { doSomething(key); } }
-
2.9.6【强制】不要出现空代码块。eslint: no-empty
不要让代码中出现空代码块这会使阅读者感到困惑。如果必须使用空块需在块内写明注释
// bad if (condition) { thing1(); } else { } // good if (condition) { thing1(); } else { // TODO I haven’t determined what to do. }
-
2.9.7【参考】控制语句的嵌套层级不要过深。SonarJs: javascript:NestedIfDepth
控制语句的嵌套层级不要超过 4 级否则将难以阅读和维护
// bad if (condition1) { // depth = 1 if (condition2) { // depth = 2 for (let i = 0; i < 10; i++) { // depth = 3 if (condition4) { // depth = 4 if (condition5) { // bad - depth = 5 } return; } } } }
-
2.9.8【参考】如果一个 if 语句的结果总是返回一个 return 语句那么最后的 else 是不必要的。eslint: no-else-return
// bad function foo() { if (x) { return x; } else { return y; } } // good function foo() { if (x) { return x; } return y; }
-
2.9.9【参考】条件表达式的计算结果。
条件表达式例如
if
语句的条件的值为通过抽象方法 ToBoolean 进行强制转换所得其计算结果遵守下面的规则- 对象、数组 被计算为 true
- Undefined 被计算为 false
- Null 被计算为 false
- 布尔值 被计算为 布尔的值
- 数字 如果是 +0、-0 或 NaN 被计算为 false否则为 true
- 字符串 如果是空字符串
''
被计算为 false否则为 true
if ({}) { // => true } if ([]) { // => true } if (undefined || null) { // => false } if (0) { // => false } if ('0') { // => true } if ('') { // => false }
-
2.9.10【强制】不要在条件表达式中使用赋值语句。eslint: no-cond-assign
- 2.9.11【推荐】不要在条件表达式中使用常量。eslint: no-constant-condition
- 2.9.12【强制】switch 语句中禁止出现重复的 case。eslint: no-duplicate-case
- 2.9.13【强制】不要在 return 等语句之后出现不可达的代码。eslint: no-unreachable
- 2.9.14【强制】禁止在 finally 中出现控制流语句。eslint: no-unsafe-finally
- 2.9.15【强制】case 或 default 字句出现词法声明时必须用块包裹。eslint: no-case-declarations
- 2.9.16【推荐】不要使用 label。eslint: no-labels
- 2.9.17【强制】禁止不必要的 label。eslint: no-extra-label
- 2.9.18【强制】禁止未使用的标签。eslint: no-unused-labels
- 2.9.19【强制】禁止标签与变量同名。eslint: no-label-var
- 2.9.20【推荐】在条件判断中使用 color === ‘red’ 而不是 ‘red’ === color。eslint: yoda
- 2.9.21【强制】禁止 if 作为唯一语句出现在 else 中此时应写成 else if。eslint: no-lonely-if
2.10 其他
-
2.10.1【强制】禁止使用 eval。eslint: no-eval
eval
语句存在安全风险可能导致注入攻击。// bad const obj = { x: 'foo' }; const key = 'x'; const value = eval('obj.' + key); // good const obj = { x: 'foo' }; const key = 'x'; const value = obj[key];
-
2.10.2【强制】禁止使用 debugger。eslint: no-debugger
debugger
语句会让程序暂停并在当前位置开启调试器。它通常在程序调试阶段使用不应发布到线上。// bad function isTruthy(x) { debugger; return Boolean(x); }
-
2.10.3【推荐】禁止使用 alert。eslint: no-alert
alert
语句会使浏览器弹出原生警告框这可能让人感觉你的程序出错了。如果需要对用户弹出警告信息好的做法是使用第三方的弹窗组件或自己定义警告框样式。同理confirm
和prompt
语句也不应被使用。// bad alert('Oops!'); // good - 使用自定义的 Alert 组件 Alert('Oops!');
-
2.10.4【推荐】生产环境禁止使用 console。eslint: no-console
console
语句通常在调试阶段使用发布上线前应该去掉代码里所有的console
语句。// bad console.log('Some debug messages..'); // good - 如果你非要使用 console 语句可以考虑自己进行封装以确保不要在生产环境暴露调试信息 const utils = { log: (msg) => { if (window.env !== 'product') { console.log(msg); } }, }; utils.log('Some debug messages..');
-
2.10.5【强制】禁止对原生对象或只读的全局对象进行赋值。eslint: no-global-assign
JS 执行环境中会包含一些全局变量和原生对象如浏览器环境中的
window
node 环境中的global
、process
Object
undefined
等。除了像window
这样的众所周知的对象JS 还提供了数百个内置全局对象你可能在定义全局变量时无意对它们进行了重新赋值因此最好的做法是不要定义全局变量。// bad window = {}; Object = null; undefined = 1;
-
2.10.6【强制】getter 需要有返回值。getter-return
get
语法用于将对象属性绑定到查询该属性时将被调用的函数函数的返回值即查询的对象属性值// bad const object = { log: [1, 2, 3], get latest() { }, } // good const object = { log: [1, 2, 3], get latest() { return this.log[this.log.length - 1]; }, }
-
2.10.7【强制】禁止在正则中使用空字符集 []。eslint: no-empty-character-class
- 2.10.8【强制】禁止对 catch 的入参重新赋值。eslint: no-ex-assign
- 2.10.9【强制】禁止在 RegExp 构造函数中使用无效的正则表达式。eslint: no-invalid-regexp
- 2.10.10【强制】禁止不规则的空白符。eslint: no-irregular-whitespace
- 2.10.11【强制】禁止在正则的字符集语法 [] 中使用由多个字符点构成的字符。eslint: no-misleading-character-class
- 2.10.12【强制】禁止在正则表达式中出现多个连续空格。eslint: no-regex-spaces
- 2.10.13【强制】禁止使用类 eval 的方法如 setTimeout 第一个参数传入字符串。eslint: no-implied-eval
- 2.10.14【强制】禁止使用 iterator 属性。eslint: no-iterator
- 2.10.15【强制】禁止使用不必要的代码块。eslint: no-lone-blocks
- 2.10.16【强制】禁止使用 proto 属性。eslint: no-proto
- 2.10.17【强制】禁止使用 javascript:url。eslint: no-script-url
- 2.10.18【强制】禁止自我赋值。eslint: no-self-assign
- 2.10.19【强制】禁止自我比较。eslint: no-self-compare
- 2.10.20【推荐】不要抛出字面量异常。eslint: no-throw-literal
- 2.10.21【强制】禁止出现未使用的表达式。eslint: no-unused-expressions
- 2.10.22【强制】禁止使用 with。eslint: no-with
- 2.10.23【强制】禁止 delete 变量。eslint: no-delete-var
3 注释
注释的目的提高代码的可读性从而提高代码的可维护性
注释的原则如无必要勿增注释As short as possible如有必要尽量详尽As long as necessary
-
3.1【参考】单行注释使用 //。eslint: line-comment-position no-inline-comments lines-around-comment
单行注释使用
//
语法建议遵循如下风格以提高可读性注释应单独一行写在被注释对象的上方不要追加在某条语句的后面line-comment-position no-inline-comments
// bad const active = true; // is current tab // good // is current tab const active = true;
注释行上方需要有一个空行除非注释行上方是一个块的顶部以增加可读性lines-around-comment
// bad - 注释行上方需要一个空行 function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this.type || 'no type'; return type; } // good function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this.type || 'no type'; return type; } // bad - 注释行上面是一个块的顶部时不需要空行 function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this.type || 'no type'; return type; } // good function getType() { // set the default type to 'no type' const type = this.type || 'no type'; return type; }
-
3.2【推荐】多行注释使用 /* … */而不是多行的 //。eslint: multiline-comment-style
// bad // make() returns a new element // based on the passed in tag name function make(tag) { // ... return element; } // good /* * make() returns a new element * based on the passed-in tag name */ function make(tag) { // ... return element; }
-
3.3【强制】注释内容和注释符之间需留有一个空格。eslint: spaced-comment
注释内容和注释符之间需留有一个空格以增加可读性
// bad //is current tab const active = true; // good // is current tab const active = true; // bad /* *make() returns a new element *based on the passed-in tag name */ function make(tag) { // ... return element; } // good /* * make() returns a new element * based on the passed-in tag name */ function make(tag) { // ... return element; }
-
3.4【推荐】合理使用特殊注释标记。SonarJs: javascript:S1134 javascript:S1135
有时我们发现某个可能的 bug但因为一些原因还没法修复或者某个地方还有一些待完成的功能这时我们需要使用相应的特殊标记注释来告知未来的自己或合作者。最常用的特殊标记有两种
// FIXME: 说明问题是什么
// TODO: 说明还要做什么或者问题的解决方案
一个我们不愿看到却很普遍的情况是我们给代码标记
FIXME
或TODO
后却一直没找到时间处理。所以当你做了特殊标记你应该为它负责在某个时间把它解决。class Calculator extends Abacus { constructor() { super(); // FIXME: shouldn’t use a global here total = 0; // TODO: total should be configurable by an options param this.total = 0; } }
-
3.5【参考】文档类注释使用 jsdoc 规范。eslint: valid-jsdoc
文档类注释如函数、类、文件、事件等推荐使用 jsdoc 规范或类 jsdoc 的规范。
例如
/** * Book类代表一个书本. * @constructor * @param {string} title - 书本的标题. * @param {string} author - 书本的作者. */ function Book(title, author) { this.title=title; this.author=author; } Book.prototype={ /** * 获取书本的标题 * @returns {string|*} */ getTitle:function(){ return this.title; }, /** * 设置书本的页数 * @param pageNum {number} 页数 */ setPageNum:function(pageNum){ this.pageNum=pageNum; } };
-
3.6【参考】无用的代码注释应被即时删除。SonarJs: javascript:CommentedCode
无用的注释代码会使程序变得臃肿并降低可读性应被即时删除。你可以通过版本控制系统找回被删除的代码。
4 命名
命名的原则同注释一样As short as possible, but as long as necessary
-
4.1【参考】使用小驼峰风格命名原始类型、对象、函数、实例。eslint: camelcase
// bad const this_is_my_string = 'foo'; const this_is_my_object = {}; function this_is_my_function() {} // good const thisIsMyString = 'foo'; const thisIsMyObject = {}; function thisIsMyFunction() {}
-
4.2【强制】使用大驼峰风格命名类和构造函数。eslint: new-cap
// bad function user(options) { this.name = options.name; } const bad = new user({ name: 'nope', }); // good class User { constructor(options) { this.name = options.name; } } const good = new User({ name: 'yup', });
-
4.3【参考】全部大写字母&单词间用下划线分割的命名模式UPPERCASE_VARIABLES。
全大写字母、单词间使用下划线分割的命名模式UPPERCASE_VARIABLES仅用于命名常量且该常量需同时满足如下条件
- 使用
const
关键字声明 - 用于
export
而不是本文件内
ES6 后
const
关键字用于声明常量被广泛使用如果所有用const
声明的值都用 UPPERCASE_VARIABLES 模式命名会使可读性变差是没有必要的。因此我们约定 UPPERCASE_VARIABLES 命名模式只用于 export 给其他文件用的常量如果只在同文件内使用依然使用正常的命名风格。// bad - 在本文件中使用的常量不需使用 UPPERCASE_VARIABLES 风格 const PRIVATE_VARIABLE = 'should not be unnecessarily uppercased within a file'; // bad export let REASSIGNABLE_VARIABLE = 'do not use let with uppercase variables'; // good export const THIS_IS_CONSTANT = '一个常量';
此外如果
export
一个对象只有对象本身需要使用 UPPERCASE_VARIABLES 对象属性的 key 仍然使用正常命名风格// bad - unnecessarily uppercases key while adding no semantic value export const AN_OBJECT = { KEY: 'value', }; // good export const AN_OBJECT = { key: 'value', };
- 使用
-
4.4【参考】模块相关的命名规范。
使用小驼峰camelCase命名
export
的函数function makeStyleGuide() { // ... } export default makeStyleGuide;
使用大驼峰PascalCase命名
export
的 class、函数库、字面量对象const AnObject = { foo: { // ... }, }; export default AnObject;
文件的命名最好和默认的
export
一致// 文件1 class CheckBox { // ... } export default CheckBox; // 文件2 export default function fortyTwo() { return 42; } // 在另一个文件中引入2个文件 // bad import CheckBox from './checkBox'; // PascalCase import/export, camelCase filename import FortyTwo from './FortyTwo'; // PascalCase import/filename, camelCase export // bad import CheckBox from './check_box'; // PascalCase import/export, snake_case filename import forty_two from './forty_two'; // snake_case import/filename, camelCase export // good import CheckBox from './CheckBox'; // PascalCase export/import/filename import fortyTwo from './fortyTwo'; // camelCase export/import/filename
-
4.5【参考】命名不要以下划线开头或结尾。eslint: no-underscore-dangle
JS 没有私有属性或私有方法的概念这样的命名可能会让人误解。
// bad this.__firstName__ = 'Panda'; this.firstName_ = 'Panda'; this._firstName = 'Panda'; // good this.firstName = 'Panda';
5 关于 ES5
这个章节是为还在使用 ES5 及之前版本 JS 的同学准备。因为本规约以 ES6 编写你可以通过阅读本章节来了解 ES5 中有哪些需要额外注意的地方。
-
5.1【参考】在模块顶部声明严格模式。eslint: strict
如果你的项目还在使用 ES5推荐始终在模块顶部声明严格模式这会让你的代码更加健壮。ES6 代码无需声明
use strict
因为经过 Babel 转换会自动添加(function () { 'use strict'; // ... }());
-
5.2【推荐】ES5 中的变量声明。eslint: block-scoped-var
使用
var
进行声明// good var foo = 'foo';
需注意
var
声明的变量不是块作用域而是函数作用域// 将打印 2, 2, 2而非 0, 1, 2 for (var i = 0; i < 3; ++i) { var iteration = i; setTimeout(function() { console.log(iteration); }, i * 1000); }
虽然
var
是函数作用域但推荐把它当作块作用域使用不要在块外使用块内声明的变量(eslint: block-scoped-var)// bad function doIf() { if (foo) { var build = true; } console.log(build); } // good function doIf() { var build; if (foo) { build = true; } console.log(build); }
另外
var
声明的变量会被提升到其作用域顶部// 变量声明会被提升到函数顶部但赋值不会被提升 function example() { console.log(declaredButNotAssigned); // => undefined console.log(notDeclared); // => throws a ReferenceError var declaredButNotAssigned = true; }
即便如此我们还是推荐在变量使用前再进行声明而不是统一在作用域开始处声明以增强可读性。当然如果你担心变量提升问题的隐患也可以选择统一在作用域开始处进行声明。
不要在声明前就使用变量这样做可能给人带来疑惑和隐患。eslint: no-use-before-define
// bad console.log(foo); // => undefined var foo = 'foo'; // good var foo = 'foo'; console.log(foo); // => foo
-
5.3【强制】ES5 环境下对于逗号分隔的多行结构不要加上最后一个行末逗号。eslint: comma-dangle
这样做会在 IE6/7 和 IE9 怪异模式下引起问题。另外多余的逗号在某些 ES3 的实现里会增加数组的长度。
// bad var hero = { firstName: 'Kevin', lastName: 'Flynn', }; // good var hero = { firstName: 'Kevin', lastName: 'Flynn' };
-
5.4【参考】使用 Array 的 slice 方法进行数组复制和类数组对象转换。
数组复制
var items = [1, 2, 3]; // bad var itemsCopy = []; for (var i = 0; i < items.length; i++) { itemsCopy[i] = items[i]; } // good var itemsCopy = items.slice();
将类数组对象转换成数组
function trigger() { var args = Array.prototype.slice.call(arguments); // ... }
-
5.5【推荐】不要使用保留字作为对象的属性名。
不要使用保留字作为对象的属性名它们在 IE8 中不工作
// bad var superman = { class: 'alien', default: { clark: 'kent' }, private: true }; // good var superman = { type: 'alien', defaults: { clark: 'kent' }, hidden: true };
6 配套工具
- eslint-config-ali集团 JS 规约配套的 ESLint 规则包
- 集团前端规约站点版在规约站点版你可以对每条规则点赞点踩、讨论吐槽
TypeScript 编码规约
(WC-TS)-TypeScript 编码规约
前言
本规约涉及 TypeScript 的编码风格、最佳实践编码风格部分与 JS 规约保持一致。
配套工具
@ali/tslint-config-ali"target="_blank">tslint-config-ali集团 TS 规约配套 TSLint 规则包
参与和反馈
对规约有任何意见和建议欢迎到这里提 issue或到规约站点版的对应条目中留言讨论
1 编码风格
类型无关的约定均继承于 JavaScript 编码规约
1.1 缩进
-
1.1.1【强制】使用 2 个空格缩进并垂直对齐。tslint: indent [ align](TSLint core rules align)
统一使用 2 个空格缩进不要使用 4 个空格或 tab 缩进并垂直对齐
// bad function foo() { ∙∙∙∙let name; } // good function foo() { ∙∙let name; }
1.2 分号
-
1.2.1【强制】使用分号。tslint: semicolon
统一以分号结束语句可以避免 JS 引擎自动分号插入机制的怪异行为在语义上也更加明确。
自动分号插入机制即 Automatic Semicolon Insertion简称 ASI 是当 JS 遇到不带分号的语句时判断是否自动添加分号的机制它在个别情况下的行为比较怪异可能导致意想不到的效果。此外随着 JS 新特性的增加异常的情况可能变得更加复杂。
// bad - 导致 Uncaught ReferenceError 报错 const luke = {} const leia = {} [luke, leia].forEach((jedi) => { jedi.father = 'vader' }) // good const luke = {}; const leia = {}; [luke, leia].forEach((jedi) => { jedi.father = 'vader'; }); // bad - 导致 Uncaught ReferenceError 报错 const reaction = 'No! That's impossible!' (async function meanwhileOnTheFalcon() { }()) // good const reaction = 'No! That's impossible!'; (async function meanwhileOnTheFalcon() { }()); // bad - 函数将返回 `undefined` 而不是换行后的值 function foo() { return 'Result want to be returned' } // good function foo() { return 'Result want to be returned'; }
1.3 逗号
-
1.3.1【强制】用逗号分隔的多行结构不使用行首逗号始终在尾部添加逗号。tslint: trailing-comma
包括数组和对象字面量、解构赋值、命名导入和导出、函数参数等解构的rest和函数的rest参数除外。
这样可以使增删行更加容易也会使 git diffs 更清晰。Babel 等编译器会在编译后的代码里帮我们去掉最后额外的逗号因此不必担心在旧浏览器中的问题。
// bad - 没有结尾逗号时新增一行的 git diff 示例 const hero = { firstName: 'Florence', - lastName: 'Nightingale' + lastName: 'Nightingale', + inventorOf: ['coxcomb chart', 'modern nursing'] }; // good - 有结尾逗号时新增一行的 git diff 示例 const hero = { firstName: 'Florence', lastName: 'Nightingale', + inventorOf: ['coxcomb chart', 'modern nursing'], };
// bad const hero = { firstName: 'Dana', lastName: 'Scully' }; const heroes = [ 'Batman', 'Superman' ]; function createHero( firstName, lastName, inventorOf ) { // ... } createHero( firstName, lastName, inventorOf ); // good const hero = { firstName: 'Dana', lastName: 'Scully', }; const heroes = [ 'Batman', 'Superman', ]; function createHero( firstName, lastName, inventorOf, ) { // ... } createHero( firstName, lastName, inventorOf, ); // good - 需注意使用扩展运算符的元素后面不能加逗号 function createHero( firstName, lastName, inventorOf, ...heroArgs ) { // ... }
不使用行首逗号
// bad const story = [ once , upon , aTime ]; // good const story = [ once, upon, aTime, ]; // bad const hero = { firstName: 'Ada' , lastName: 'Lovelace' , superPower: 'computers' }; // good const hero = { firstName: 'Ada', lastName: 'Lovelace', superPower: 'computers', };
1.4 块
-
1.4.1【强制】不要使用空代码块。tslint: no-empty
不要让代码中出现空代码块这会使阅读者感到困惑。如果必须使用空块需在块内写明注释。
// bad if (condition) { thing1(); } else { } // good if (condition) { thing1(); } else { // TODO I haven’t determined what to do. }
-
1.4.2【强制】代码块始终需用大括号包裹。tslint: curly
多行代码块必须用大括号包裹
// bad if (foo) bar(); baz(); // 这一行并不在 if 语句里 // good if (foo) { bar(); baz(); }
代码块只有一条语句时可以省略大括号并跟控制语句写在同一行。但出于一致性和可读性考虑不推荐这样做
// bad if (foo) return false; // bad - 允许但不推荐 if (foo) return false; // good if (foo) { return false; }
-
1.4.3【强制】对于非空的代码块大括号的换行方式采用 Egyptian Brackets 风格 指定的标记和后续相关的表达式在同一行 比如
catch finally else
和前面的花括号。tslint: one-line对于非空的代码块大括号的换行方式采用 Egyptian Brackets 风格具体规则如下
- 左大括号
{
前面不换行后面换行 - 右大括号
}
前面换行 - 右大括号
}
后面是否换行有两种情况- 如果
}
终结了整个语句如条件语句、函数或类的主体则需要换行 - 如果
}
后面存在else
、catch
、while
等语句或存在逗号、分号、右小括号)
则不需要换行
- 如果
// bad - else 应与 if 的 } 放在同一行 if (foo) { thing1(); } else thing2(); } // good if (foo) { thing1(); } else { thing2(); }
- 左大括号
-
1.4.4【推荐】不要声明空的 interface。tslint: no-empty-interface
// bad interface A {} // good interface B { value: number; }
1.5 空格
-
1.5.1【强制】空格风格。tslint: whitespace
块的左大括号
{
前有一个空格// bad function test(){ console.log('test'); } // good function test() { console.log('test'); } // bad dog.set('attr',{ age: '1 year', breed: 'Bernese Mountain Dog', }); // good dog.set('attr', { age: '1 year', breed: 'Bernese Mountain Dog', });
控制语句
if
、while
等的左小括号(
前有一个空格// bad if(isJedi) { fight (); } // good if (isJedi) { fight(); }
声明函数时函数名和参数列表之间无空格
// bad function fight () { console.log ('Swooosh!'); } // good function fight() { console.log('Swooosh!'); }
小括号内部两侧无空格
// bad function bar( foo ) { return foo; } // good function bar(foo) { return foo; } // bad if ( foo ) { console.log( foo ); } // good if (foo) { console.log(foo); }
方括号内部两侧无空格
// bad const foo = [ 1, 2, 3 ]; console.log(foo[ 0 ]); // good const foo = [1, 2, 3]; console.log(foo[0]);
大括号内部两侧有空格
// bad const foo = {clark: 'kent'}; // good const foo = { clark: 'kent' };
运算符两侧有空格除了一元运算符
// bad const x=y+5; // good const x = y + 5; // bad const isRight = result === 0? false: true; // good const isRight = result === 0 ? false : true; // bad - 一元运算符与操作对象间不应有空格 const x = ! y; // good const x = !y;
定义对象字面量时 key, value 之间有且只有一个空格不允许所谓的「水平对齐」
// bad { a: 'short', looooongname: 'long', } // bad { a : 'short', looooongname: 'long', } // good { a: 'short', looooongname: 'long', }
-
1.5.2【推荐】在使用长方法链式调用时进行缩进。tslint: newline-per-chained-call
在使用多个大于两个方法链式调用时进行换行缩进把点
.
放在行首以强调这是方法调用而不是新语句// bad $('#items').find('.selected').highlight().end().find('.open').updateCount(); // bad $('#items'). find('.selected'). highlight(). end(). find('.open'). updateCount(); // good $('#items') .find('.selected') .highlight() .end() .find('.open') .updateCount(); // bad const leds = stage.selectAll('.led').data(data).enter().append('svg:svg').classed('led', true) .attr('width', (radius + margin) * 2).append('svg:g') .attr('transform', `translate(${radius + margin},${radius + margin})`) .call(tron.led); // good const leds = stage.selectAll('.led') .data(data) .enter() .append('svg:svg') .classed('led', true) .attr('width', (radius + margin) * 2) .append('svg:g') .attr('transform', `translate(${radius + margin},${radius + margin})`) .call(tron.led); // good - 大于 2 个方法的链式调用才需要进行换行 const leds = stage.selectAll('.led').data(data);
-
1.5.3【强制】建议删除行尾的空格。tslint: no-trailing-whitespace
// bad let a = 1;∙ // good let a = 1;
-
1.5.4【强制】type 声明中冒号前不需要空格。tslint: typedef-whitespace
// bad type value : number; // good type value: number;
-
1.5.5【强制】紧邻括号的内侧不使用空格。tslint: space-within-parens
// bad foo( 'bar' ); var x = ( 1 + 2 ) * 3; // good foo('bar'); var x = (1 + 2) * 3;
-
1.5.6【强制】函数的括号之前不需要加空格使用
async
的箭头函数除外。tslint: space-before-function-paren// bad function f () {} // good function f() {}
1.6 空行
-
1.6.1【推荐】在文件末尾保留一行空行。tslint: eofline
在非空文件中保留拖尾换行是一种常见的 UNIX 风格。它的好处同输出文件到终端一样方便在串联和追加文件时不会打断 shell 的提示。
我们统一在文件末尾保留一行空行即用一个换行符结束文件
// bad - 文件末尾未保留换行符 import { foo } from './Foo'; // ... export default foo; // bad - 文件末尾保留了2个换行符 import { foo } from './Foo'; // ... export default foo;↵ ↵ // good import { foo } from './Foo'; // ... export default foo;↵
1.7 最大字符数和最大行数
-
1.7.1【推荐】单行最大字符数100。tslint: max-line-length
过长的单行代码不易阅读和维护需要进行合理换行。
我们推荐单行代码最多不要超过 100 个字符除了以下两种情况
- 字符串和模板字符串
- 正则表达式
// bad const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy; // good const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy; // bad $.ajax({ method: 'POST', url: 'https://foo.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.')); // good $.ajax({ method: 'POST', url: 'https://foo.com/', data: { name: 'John' }, }) .done(() => console.log('Congratulations!')) .fail(() => console.log('You have failed this city.'));
-
1.7.2【推荐】文件最大行数1000。tslint: max-file-line-count
过长的文件不易阅读和维护最好对其进行拆分。
-
1.7.4【推荐】函数最大行数80。
过长的函数不易阅读和维护最好对其进行拆分。
2 语言特性
2.1 变量声明
-
2.1.1【强制】禁止使用 var 关键词使用 const 或 let 声明变量。tslint: no-var-keyword
从 ES6 开始可以使用
let
和const
关键字在块级作用域下声明变量。块级作用域在很多其他编程语言中都有使用这样声明的变量不会污染全局命名空间。不要使用
var
// bad var foo = 'foo'; var bar; // good const foo = 'foo'; let bar;
更不要什么都不用这将产生全局变量从而污染全局命名空间
// bad foo = 'foo'; // good const foo = 'foo';
-
2.1.2【强制】优先考虑使用 const 代替 let。tslint: prefer-const
声明变量时应优先使用
const
只有当变量会被重新赋值时才使用let
// bad - 声明后未发生重新赋值应使用 const let flag = true; if (flag) { console.log(flag); } // good - 声明后发生重新赋值let 使用正确 let flag = true; if (flag) { flag = false; }
需注意数组和对象是一个引用对数组某项和对象某属性的修改并不是重新赋值因此多数情况下应用
const
声明// bad let arr = []; let obj = {}; arr[0] = 'foo'; obj.name = 'bar'; // good const arr = []; const obj = {}; arr.push('foo'); obj.name = 'bar';
-
2.1.3【强制】一条声明语句声明一个变量。tslint: one-variable-per-declaration
这样做更易于追加新的声明语句你不需要总去把最后的
;
改成,
了也更易于进行单步调试。// bad const foo = 1, bar = 2; // good const foo = 1; const bar = 2;
-
2.1.4【推荐】声明的变量必须被使用。
TypeScript 原生支持
声明而未使用的变量、表达式可能带来潜在的问题也会给维护者造成困扰应将它们删除。
// bad - 未使用变量 foo const foo = 1; // good const foo = 1; doSomethingWith(foo); // bad - 只修改变量不认为是被使用 let bar = 1; bar = 2; bar += 1; // good let bar = 1; bar = 2; bar += 1; doSomethingWith(foo); // bad - 未使用参数 y function getX(x, y) { return x; } // good function getXPlusY(x, y) { return x + y; }
-
2.1.5【强制】变量不要与外层作用域已存在的变量同名。tslint: no-shadowed-variable
如果变量与外层已存在变量同名会降低可读性也会导致内层作用域无法读取外层作用域的同名变量。
// bad const foo = 1; if (someCondition) { const foo = 2; console.log(foo); // => 2 } // good const foo = 1; if (someCondition) { const bar = 2; console.log(bar); // => 2 console.log(foo); // => 1 }
-
2.1.6【强制】禁止在一个块作用域内重复声明一个变量和函数。tslint: no-duplicate-variable
在 ES5 中尽管使用
var
重复声明不会报错但这样做会令人疑惑降低程序的可维护性。同理函数的声明也不要与已存在的变量和函数重名// bad var a = 'foo'; var a = 'bar'; function a() {} console.log(a); // => 'bar' // good var a = 'foo'; var b = 'bar'; function c() {} console.log(a); // => 'foo' // bad - arg 已作为函数参数声明 function myFunc(arg) { var arg = 'foo'; console.log(arg); } myFunc('bar'); // => 'foo' // good function myFunc(arg) { var otherName = 'foo'; console.log(arg); } myFunc('bar'); // => 'bar'
在 ES6 中使用
const
或let
重复声明变量会直接报错// bad const a = 'foo'; function a() {} // => Uncaught SyntaxError: Identifier 'a' has already been declared // good const a = 'foo'; function b() {} // bad - arg 已作为函数参数声明 function myFunc(arg) { const arg = 'foo'; console.log(arg); } myFunc('bar'); // => Uncaught SyntaxError: Identifier 'arg' has already been declared // good function myFunc(arg) { const otherName = 'foo'; console.log(arg); } myFunc('bar'); // => 'bar'
-
2.1.7【强制】【ESLint】禁止连续赋值。eslint: no-multi-assign
变量的连续赋值让人难以阅读和理解并且可能导致意想不到的结果如产生全局变量。
// bad - 本例的结果是 let 仅对 a 起到了预想效果b 和 c 都成了全局变量 (function test() { let a = b = c = 1; // 相当于 let a = (b = (c = 1)); })(); console.log(a); // throws ReferenceError console.log(b); // 1 console.log(c); // 1 // good (function test() { let a = 1; let b = a; let c = a; })(); console.log(a); // throws ReferenceError console.log(b); // throws ReferenceError console.log(c); // throws ReferenceError
-
2.1.8【强制】禁止对通过
var/let
或者解构赋值的变量初始化赋值undefined
。tslint: no-unnecessary-initializer因为 Javascript 中默认就是
undefined
-
2.1.9【推荐】变量命名使用小驼峰或者全大写避免使用关键词。tslint: variable-name
避免使用关键词
any, Number, number, String, string, Boolean, boolean, Undefined, undefined
等// bad const Tom = 'tom'; // good const HEIGHT = 100; const myName = 'tom';
2.2 原始类型
-
2.2.1【强制】不要使用 new Number/String/Boolean。tslint: no-construct
使用 new Number/String//Boolean 声明不会有任何好处还会导致变量成为
object
类型可能引起 bug。// bad const num = new Number(0); const str = new String('foo'); const bool = new Boolean(false); console.log(typeof num, typeof str, typeof bool); // => object, object, object if (num) { // true对象相当于 true } if (bool) { // true对象相当于 true } // good const num = 0; const str = 'foo'; const bool = false; console.log(typeof num, typeof str, typeof bool); // => number, string, boolean if (num) { // false0 相当于 false } if (bool) { // false }
-
2.2.2【推荐】使用 parseInt() 方法时总是带上基数。tslint: radix
parseInt
方法的第一个参数是待转换的字符串第二个参数是转换基数。当第二个参数省略时parseInt
会根据第一个参数自动判断基数- 如果以 0x 开头则使用 16 作基数
- 如果以 0 开头则使用 8 作基数。正是这条规则经常导致错误ES5 规范中直接将这条规则移除即 ES5 及之后的执行环境以 0 开头也会使用 10 作为基数
- 其他情况则使用 10 作基数
虽然从 ES5 开始就移除了自动以 8 作基数的规则但有时难以保证所有的浏览器和 JS 执行环境都支持了这一特性。了解更多
因此推荐始终给
parseInt()
方法加上基数除非可以保证代码的执行环境不受上述特性的影响。// bad parseInt('071'); // => ES5 前的执行环境中得到的是 57 // good parseInt('071', 10); // => 71
-
2.2.3【强制】【ESLint】避免不必要的布尔类型转换。eslint: no-extra-boolean-cast
在
if
等条件语句中将表达式的结果强制转换成布尔值是多余的// bad if (!!foo) { // ... } while (!!foo) { // ... } const a = !!flag ? b : c; // good if (foo) { // ... } while (foo) { // ... } const a = flag ? b : c;
-
2.2.4【强制】字符串优先使用单引号。tslint: quotemark
// bad const name = "tod"; const name = `tod`; // 模板字符串中应包含变量或换行否则需用单引号 // good const name = 'tod';
-
2.2.5【推荐】使用模板字符串替代字符串拼接。tslint: prefer-template
模板字符串让代码更简洁可读性更强
// bad function getDisplayName({ nickName, realName }) { return nickName + ' (' + realName + ')'; } // good function getDisplayName({ nickName, realName }) { return `${nickName} (${realName})`; }
2.3 对象
-
2.3.1【强制】使用对象属性和方法的简写语法。tslint: object-literal-shorthand
ES6 提供了对象属性和方法的简写语法可以使代码更加简洁
const value = 'foo'; // bad const atom = { value: value, addValue: function (value) { return value + ' added'; }, }; // good const atom = { value, addValue(value) { return value + ' added'; }, };
-
2.3.2【强制】对象的属性名不要用引号包裹除非包含特殊字符。tslint: object-literal-key-quotes
这样更加简洁也有助于语法高亮和一些 JS 引擎的优化。
// bad const bad = { 'foo': 3, 'bar': 4, 'data-blah': 5, 'one two': 12, }; // good const good = { foo: 3, bar: 4, 'data-blah': 5, 'one two': 12, };
-
2.3.3【强制】禁止不必要的使用字符串进行属性访问。tslint: no-string-literal
允许一些特殊性情况比如
obj['prop-erty']
// bad obj['property'] // good obj.property
2.4 条件和遍历
-
2.4.1【推荐】for…in 语句使用 if 进行过滤同时推荐使用 for…of。tslint: forin
// bad for (let key in someObject) { // code here } // good for (let key of someObject) { if (someObject.hasOwnProperty(key)) { // code here } }
-
2.4.2【强制】仅允许在
do/for/while/switch
中使用label
。tslint: label-positionJavaScript中的标签只能与break或continue结合使用用于循环流控制的结构
-
2.4.3【强制】不允许条件语句内进行赋值。tslint: no-conditional-assignment
常见于 do-while, for, if, while
// bad if (var1 = var2)
-
2.4.4【强制】禁止在 switch 语句中出现重复的 case。tslint: no-duplicate-switch-case
属于重复的代码甚至带来代码执行两次的风险
-
2.4.5【强制】不要直接 throw 字符串。tslint: no-string-throw
// bad // 抛出一个字符串缺少任何堆栈跟踪信息和其他重要的数据属性 if (!productToAdd) { throw ('How can I add new product when no value provided?'); } // good // 抛出典型函数的错误无论是同步还是异步 if (!productToAdd) { throw new Error('How can I add new product when no value provided?'); }
-
2.4.6【强制】禁止在
finally
中使用控制流程的语句。tslint: no-unsafe-finally例如
return, continue, break and throws
等// bad try { // tryCode - Block of code to try } catch(err) { // catchCode - Block of code to handle errors } finally { // finallyCode - Block of code to be executed regardless of the try / catch result throw new Error('something'); } // good try { // tryCode - Block of code to try } catch(err) { // catchCode - Block of code to handle errors throw new Error('something'); } finally { // finallyCode - Block of code to be executed regardless of the try / catch result }
-
2.4.7【推荐】switch 语句中添加一个
default
case。tslint: switch-default// bad switch (p){ case 1: // come code break; case ... } // good switch (p){ case 1: // come code break; case ... default: // other code }
-
2.4.8【推荐】使用
=== and !==
代替== and !=
。tslint: triple-equals和
null
比较时仍允许使用== and !=
// bad a == b; // good a === b;
-
2.4.9【强制】使用
isNaN()
函数来检查NaN
引用而不是和NaN
对比较。tslint: use-isnan// bad if (myVar === NaN) // good do if (isNaN(myVar))
-
2.4.10【强制】禁止和 boolean 字面量进行对比。tslint: no-boolean-literal-compare
// bad if(x === true){...} // good if(x){...}
2.5 函数
-
2.5.1【推荐】优先使用箭头函数允许有独立的函数声明和具名的函数表达式不允许匿名函数。tslint: only-arrow-functions
// bad function (a){ return a; } function(){} // good const identity = a => a function myName(name) { return `my name is ${name}`; }
-
2.5.2【强制】禁止直接使用函数的构造函数。tslint: function-constructor
// bad let doesNothing = new Function(); // good let doesNothing = () => {};
-
2.5.3【强制】使用箭头函数代替
bind
。tslint: unnecessary-bind// bad constructor(){ this.onClick.bind(this); } onClick(){} // good onClick = () => {}
2.6 类
-
2.6.1【推荐】类成员声明需要添加访问修饰符public 除外。tslint: member-access
TypeScript 中默认是
public
不必再强调其他的修饰符则需要写明// bad public value: number; // good value: number; private name: string;
-
2.6.2【强制】static 类成员放在类的最前面。tslint: member-ordering
增加可读性
-
2.6.3【强制】不要使用内部的 module 和 namespace 组织代码。tslint: no-namespace [ no-internal-module](TSLint core rules no-internal-module)
TypeScript 中的 module 和 namespace 是可以用来对代码进行逻辑组织的方式推荐统一使用 ES6 的 import/export 语法进行代码组织。
对于扩充外部模块定义的语法 declare module ‘xxx’ 依然可以使用。// bad module a { } // good import { a } from './a';
-
2.6.4【强制】不允许对 this 创建引用能用箭头函数的就用箭头函数。tslint: no-this-assignment
// bad const self = this; setTimeout(function () { self.doWork(); }); // good setTimeout(() => { this.doWork(); });
-
2.6.5【强制】在同一个 constructor 中只能出现一次 super。tslint: no-duplicate-super
super调用一次即可
-
2.6.6【强制】删除空的或者不必要的
constructor
。tslint: unnecessary-constructor如果没有用到则可以不写
-
2.6.7【强制】类名和接口名首字母大写。tslint: class-name
// bad class myClass { } interface myInterface { } // good class MyClass { } interface MyInterface { }
2.7 模块
-
2.7.1【推荐】禁止使用
require
来引用模块。tslint: no-require-imports [ no-var-requires](TSLint core rules no-var-requires)统一使用 ES6 语法
// bad var module = require('module'); import foo = require('foo'); // good import foo from 'foo'; // priority
-
2.7.2【强制】禁止对同一模块多次
import
引入。tslint: no-duplicate-imports同一个模块不需要多次引入
2.8 其他
-
2.8.1【强制】禁止使用逗号运算符。tslint: ban-comma-operator
// bad foo((bar, baz)); // good foo(baz);
// bad switch (foo) { case 1, 2: return true; } // good switch (foo) { case 1: case 2: return true; }
-
2.8.2【推荐】不推荐使用按位运算。tslint: no-bitwise
主要有 &, &=, |, |=, ^, ^=, <<, <<=, >>, >>=, >>>, >>>=, and ~
一般情况下很少用到
-
2.8.3【推荐】不保留 console 语句。tslint: no-console
正式环境不推荐保留console语句当然可以使用构建工具去除
-
2.8.4【强制】不允许使用 debugger。tslint: no-debugger
正式环境不允许存在 debugger
-
2.8.5【强制】禁止使用 eval。tslint: no-eval
可能存在被利用遭受xss攻击的风险
-
2.8.6【强制】删除未使用的表达式语句。tslint: no-unused-expression
可能存在潜在的风险
3 类型
3.1 类型定义
-
3.1.1【推荐】定义数组类型时若数组元素为基础类型则推荐使用 T[] 形式定义。tslint: array-type
// bad function (arr: Array<number>) { } // good function (arr: number[]) { }
-
3.1.2【强制】当需要强制转换类型时必须只用 as Type 。tslint: no-angle-bracket-type-assertion
在 .tsx 文件中只有 as 语法生效为保持类型转换风格一致强制统一使用 as Type 方式转换类型避免使用 。
// bad <number>foo // good foo as number
-
3.1.3【参考】尽量减少 any 类型声明。tslint: no-any
允许适度的使用 as any 方法进行类型转换或将变量类型声明为 any
-
3.1.4【参考】尽量不要对 any 类型数据进行操作定义接口来声明结构。tslint: no-unsafe-any
如果数据来源不能控制来自第三方 API 等则在类型声明时建议使用泛型或 Partial 语法保证代码中读取的结构一定存在。
// bad const obj = JSON.parse(str) as any; const result = obj.a.b; // good interface JSONResult { a: { b: number; } } const obj = JSON.parse(str) as JSONResult; const result = obj.a.b;
-
3.1.5【推荐】对于可以自动推导的类型无需显式声明。tslint: no-inferrable-types
对
number string boolean
类型的变量进行初始化时不需要进行类型声明因为编译器可以轻松推断出来。// bad const str: string = 'str'; // good const str = 'str';
-
3.1.6【强制】禁止使用
<reference />
方式引用文件和模块。tslint: no-reference [ no-reference-import](TSLint core rules no-reference-import)禁止使用
<reference types='foo' />
引入模块禁止使用
/// <reference path='' />
引入声明文件推荐使用 ES6 的 import 语法引入定义文件
// bad /// <reference path='react/index.d.ts' /> // good import React from 'react';
-
3.1.7【强制】函数重载的定义要连续。tslint: adjacent-overload-signatures
// bad function pickCard(x: {suit: string; card: number; }[]): number; function insert(x: number): number[]; function pickCard(x: number): {suit: string; card: number; }; // good function pickCard(x: {suit: string; card: number; }[]): number; function pickCard(x: number): {suit: string; card: number; }; function pickCard(x: any): any { // 具体实现 // some code }
-
3.1.8【推荐】重载函数保持简洁尽量使用 union 或者 optional/rest 参数统一可能合并的函数。tslint: unified-signatures
// bad
function f(a: number, b: string): object;
function f(a: number, b?: string, c?: string): object;
function f(a: number, b?: string, c?: string) {
return {
x: a,
y: b,
z: c,
};
}
// good
function f(a: number, ...args: string[]): object;
function f(a: number, b?: string, c?: string) {
return {
x: a,
y: b,
z: c,
};
}
-
3.1.9【强制】类型定义避免使用特定类型默认不要使用包装类型。tslint: ban-types
Object ==> object
Function ==> () => void
Boolean ==> boolean
Number ==> number
String ==> string
Symbol ==> symbol
-
3.1.10【强制】类型声明里的属性值以分号结尾。tslint: type-literal-delimiter
// bad interface ComProps { value: number name: string } // good interface ComProps { value: number; name: string; }
3.2 模块定义
14 其他
-
14.1【强制】引用的第三方依赖一定要在 package.json 中声明。tslint: no-implicit-dependencies
如果没有在
package.json
声明则在使用云构建时无法加载到依赖包 -
14.2【强制】强制使用 UTF-8 编码。tslint: encoding
统一编码
git版本管理/编辑器设置
1、 代码规范
为保持前端项目编码风格统一降低跨项目协调开发难度同时规范个人编码习惯和提高个人编码能力公司前端项目搭建及开发时须配置ESLint与stylelint规范具体见以下文档
ESLintjavaScript/html规范
'eqeqeq': [ 0, 'allow-null' ], /**要求使用 === 和 !== */
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', /**禁用 debugger */
'quotes': [ 'error', 'single' ], /**要求使用单引号 */
'linebreak-style': [ 0, 'error', 'windows' ], /**强制使用一致的换行风格 */
'no-use-before-define': [ 'error', {
'functions': true,
'classes': true
} ], /**禁止在变量定义之前使用它们 */
'no-param-reassign': [ 'error', { 'props': false } ], /**禁止对 function 的参数进行重新赋值 */
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', /**禁用 console */
//TODO:
'comma-dangle': [ 'error', 'never' ], /**禁止使用拖尾逗号 */
'indent': [ 'error', 2 ], /**2个空格缩进 */
'max-len': [ 'error', { 'code': 1000 } ], /**强制一行的最大长度1000 */
'no-mixed-spaces-and-tabs': [ 'error', 'smart-tabs' ], /**使用 空格 和 tab 混合缩进 */
'no-ternary': 'off', /**允许使用三元表达式 */
'no-nested-ternary': 'off', /**允许使用嵌套的三元表达式 */
'prefer-template': 'error', /**建议使用模板字面量而非字符串‘+’拼接 */
'arrow-parens': [ 'error', 'as-needed' ], /**必要时要求箭头函数的参数使用圆括号 */
'arrow-spacing': 'error', /**箭头函数的箭头之前或之后要有空格 */
'require-await': 'off', /**禁止使用不带 await 表达式的 async 函数 */
// TODO:
'array-bracket-spacing': [ 'error', 'never' ], /**方括号内部两侧无空格 */
'no-trailing-spaces': 'error', /**禁止使用行尾空白 */
'no-const-assign': 'error', /**不允许改变用const声明的变量 */
'no-this-before-super': 'error', /**在构造函数中禁止在调用super()之前使用this或super 【大多用于React中类的继承】*/
'no-useless-rename': 'error', /**禁止在 import 和 export 和解构赋值时将引用重命名为相同的名字 */
'template-curly-spacing': [ 'error', 'always' ], /**模板字符串中,花括号内要有一个或多个空格 */
'newline-per-chained-call': [ 'error', { 'ignoreChainWithDepth': 3 } ], /**要求方法链中每3个调用必须有一个换行符 */
'no-lonely-if': 'error', /**禁止 if 语句作为唯一语句出现在 else 语句块中 */
'no-mixed-operators': 'error', /**禁止混合使用不同的操作符如错误用法:let foo = a && b || c || d; */
'no-whitespace-before-property': 'error', /**禁止属性前有空白 */
'wrap-regex': 'error', /**要求正则表达式要被小括号()包裹起来 */
'eol-last': [ 'error', 'always' ], /**要求文件末尾须保留一行空行 */
'require-await': 'error', /**禁止使用不带 await 表达式的 async 函数 */
'no-redeclare': 'error', /**不要重复声明变量和函数 */
'no-multi-spaces': 'error', /**禁止出现多个空格 */
'no-unreachable': 'error', /**禁止在 return、throw、continue 和 break 语句后出现不可达代码 */
'vue/max-attributes-per-line': [ 'error', {
'singleline': 4,
'multiline': {
'max': 1,
'allowFirstLine': false
}
} ], /**dom标签同行最多写4个属性超过后须换行换行后的属性同行最多写1个属性 */
'vue/singleline-html-element-content-newline': 'off', /**空标签换行||不换行都可以 */
'vue/name-property-casing': [0, 'PascalCase' | 'kebab-case'], /**属性首字母大小写不限 */
'no-param-reassign': 'off',
'no-use-before-define': 'off',
'no-tabs': 'error', /**不允许Tab缩进标签 */
'semi': ['error', 'always', { 'omitLastInOneLineBlock': true }], /**统一使用分号结束语句 */
'no-unexpected-multiline': 'error', /**避免意外的多行表达式 */
'no-extra-semi': 'error', /**禁用不必要的分号 */
'semi-style': ['error', 'last'], /**分号必须写在行尾 */
'comma-style': ['error', 'last'], /**用逗号分隔的多行结构将逗号放到行尾 */
'brace-style': ['error'], /**大括号换行采用 1TBS 风格,具体如
左大括号 { 前面不换行后面换行
右大括号 } 前面换行
右大括号 } 后面是否换行有两种情况
如果 } 终结了整个语句如条件语句、函数或类的主体则需要换行
如果 } 后面存在 else、catch、while 等语句或存在逗号、分号、右小括号)则不需要换行、
*/
'nonblock-statement-body-position': ['error', 'beside'], /**省略大括号的单行语句前不要换行 */
'dot-location': ['error', 'property'], /**在点号之前换行 */
'newline-per-chained-call': ['error', { 'ignoreChainWithDepth': 4 }], /**在长方法链式调用时进行换行最多一行存在4个 */
// TODO:
'function-paren-newline': ['error', 'multiline'], /**函数的小括号需遵循一致的换行风格 */
'implicit-arrow-linebreak': ['error', 'beside'], /**隐式返回的箭头函数体前不要换行 */
'space-before-blocks': 'error', /**空格风格... */
'keyword-spacing': ['error', { 'before': true }], /**控制语句的关键字如 if、else、else if 等前后各一个空格 */
'func-call-spacing': ['error', 'never'], /**函数名与调用它的括号间无空格 */
'space-before-function-paren': ['error', {
'anonymous': 'ignore',
'named': 'never',
'asyncArrow': 'never'
}], /**声明函数时对于命名函数参数的小括号前无空格对于匿名函数和 async 箭头函数参数的小括号前有空格 */
'space-in-parens': ['error', 'never'], /**小括号内部两侧无空格 */
'computed-property-spacing': ['error', 'never'], /**不允许括号和括号内的值之间有空格 */
'block-spacing': 'error', /**在同一行上的开放块标记和下一个标记内保持一致的间距 */
// TODO:
'template-curly-spacing': 'error', /**模板字符串中的大括号内部两侧无空格 */
'template-tag-spacing': 'error', /**模板字符串的 tag 语法tag 后面无空格 */
'space-infix-ops': 'error', /**操作符两侧有空格 */
'space-unary-ops': 'error', /**一元运算符之后/之前的空格保持一致 */
'rest-spread-spacing': ['error', 'never'], /**强制rest和spread运算符及其表达式之间保持一致的间距 */
'semi-spacing': 'error', /**分号的前面无空格后面有空格语句末尾的分号后面无空格 */
'comma-spacing': ['error', { 'before': false, 'after': true }], /**逗号的前面无空格后面有空格 */
'key-spacing': ['error', { 'beforeColon': false }], /**key, value 之间有且只有一个空格不允许所谓的「水平对齐」 */
'padded-blocks': ['error', 'never'], /**块的开始和结束不能是空行 */
'no-multiple-empty-lines': ['error', { 'max': 2, 'maxEOF': 1 }], /**禁止出现多个大于 2 个连续空行, 在文件末尾强制执行最大数量为2行的连续空行 */
'curly': ['error'], /**多行语句必须用大括号包裹单行语句推荐用大括号包裹 */
'no-floating-decimal': 'error', /**不要省略小数点前或小数点后的 0 */
'no-undef': ['off', { 'typeof': true }], /**不要使用未声明的变量... */
'no-var': 'error', /**使用 const 或 let 声明变量不要使用 var */
'prefer-const': 'off', /**正确地使用 const 和 let(声明变量时应优先使用 const只有当变量会被重新赋值时才使用 let) */
'no-unused-vars': 'error', /**声明的变量必须被使用 */
'no-shadow': ['error', { 'hoist': 'never' }], /**禁止变量与外层作用域已存在的变量同名 */
'no-multi-assign': 'error', /**禁止连续赋值 */
'no-shadow-restricted-names': 'error', /**禁止使用保留字命名变量 */
'no-undef-init': 'error', /**不要将变量初始化成 undefined */
'no-class-assign': 'error', /**禁止对类声明变量重新赋值 */
'no-const-assign': 'error', /**禁止修改 const 声明的变量 */
'use-isnan': 'error', /**不允许与“NaN”进行比较 */
'valid-typeof': ['error', { 'requireStringLiterals': true }], /**同 typeof 表达式结果进行比较的值必须是有效的字符串 */
'no-multi-str': 'error', /**禁止使用多行字符串 */
'no-octal': 'error', /**禁用八进制字面量 */
'no-octal-escape': 'error', /**禁止在字符串字面量中使用八进制转义序列 */
'no-useless-concat': 'error', /**禁止不必要的字符串拼接 */
'no-array-constructor': 'error', /**使用字面量创建数组 */
'no-sparse-arrays': 'error', /**禁用稀疏数组 */
'no-new-object': 'error', /**使用字面量创建对象 */
'object-shorthand': [0, 'consistent'], /**[待定...]使用对象属性和方法的简写语法 */
'quote-props': ['error', 'as-needed'], /**对象字面量的属性名不要用引号包裹除非包含特殊字符 */
'no-prototype-builtins': 'error', /**不要直接在对象上调用 Object.prototypes 上的方法 */
'no-dupe-keys': 'error', /**对象中禁止出现重复命名的 key */
'no-obj-calls': 'error', /**禁止将全局对象 Math、JSON、Reflect 当作函数进行调用 */
'no-empty-pattern': 'error', /**不要在解构中出现空模式 */
'no-useless-computed-key': 'error', /**对象的属性名不要使用无必要的计算属性 */
'no-useless-rename': 'error', /**禁止在解构 / import / export时进行无用的重命名 */
'wrap-iife': ['error', 'outside'], /**立即执行函数表达式IIFE需要用小括号包裹 */
'no-func-assign': 'error', /**不要对函数声明重新赋值 */
'no-useless-return': 'error', /**禁止多余的 return; 语句 */
'no-confusing-arrow': 'error', /**避免箭头函数可能与比较操作符产生混淆的情况 */
'no-duplicate-imports': ['error', { 'includeExports': true }], /**不要用多个 import 引入同一模块 */
'no-unneeded-ternary': 'error', /**避免不必要的三元表达式 */
'no-fallthrough': 'error', /**不要让 case 语句落空 */
'no-empty': 'error', /**不要出现空代码块 */
'no-duplicate-case': 'error', /**switch 语句中禁止出现重复的 case */
'no-eval': 'error', /**禁止使用 eval,eval 语句存在安全风险可能导致注入攻击 */
'spaced-comment': ['error', 'always'], /**注释内容和注释符之间需留有一个空格 */
'no-global-assign': 'error', /**禁止对原生对象或只读的全局对象进行赋值 */
'no-invalid-regexp': 'error', /**禁止在 RegExp 构造函数中使用无效的正则表达式 */
'no-regex-spaces': 'error', /**禁止在正则表达式中出现多个连续空格 */
'no-proto': 'error', /**禁止使用 proto 属性 */
'no-script-url': 'error', /**禁止使用 javascript:url避免脚本恶意注入 */
'no-delete-var': 'error', /**禁止 delete 变量 */
StyleLintcss规范
StyleLint规范具体见Visual Studio Code代码编辑器插件
2、visual studio code 编辑器配置自动格式化ESLint和StyleLint并自动修复
- 安装vscode 插件
stylelint
; - vscode
setting.json
文件中添加以下配置
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.fixAll.stylelint": true,
},
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.formatOnSave": true,
"editor.formatOnType": true,
"stylelint.enable": true,
"css.validate": true,
"less.validate": true,
"scss.validate": true,
"eslint.format.enable": true,
"eslint.alwaysShowStatus": true,
"eslint.codeAction.showDocumentation": {
"enable": true
},
3、git的开发和发版规则
背景
目前是多客户端开发、有pc\小程序后期可能还会引进其他。pc端平台有公车5.0、机关大后勤、运维平台等、小程序有公车4.0和5.0。虽然开发技术不一样目前有些业务开发是单人开发考虑都后期前端融合会多人涉及多平台业务线开发为了后期统一管理并可维护代码版本管理也需要有规范性减少因代码丢失、冲突等问题引发的项目问题
推荐使用终端命令管理版本标准项目版本管理流程如下
commit说明规范
fix 修复bug
如git commit -m "fix:修复了...bug问题"
feat 开发新需求、功能
如git commit -m "feat开发了...新需求"
docs 只修改了文档不影响代码
如git commit -m "docs修改...文档"
style 调整代码格式未修改代码逻辑比如修改空格、格式化、缺少分号等
如git commit -m "style修改了...样式"
config 修改项目配置
如git commit -m "config修改了...项目配置"
merge git代码合并解决冲突时...
如git commit -m "merge解决冲突..."
4、分支创建规则
-
所有代码分支须创建和使用须遵循以下规则
-
基于master当期稳定版分支分支创建新分支分支命名规则为
【需求】
feat_功能名简称_创建日期
【bug】
fix_bug概括名简称_创建日期
【hotfix】
hotfix_功能名简称_创建日期
-
分支代码提测/发版流程
如当期需求分支在
feat_test_xxx
【提测流程】
feat_test_xxx
提merge请求到test
受保护分支【发版流程】
test
分支提merge请求到master
受保护分支【紧急hotfix发版流程】
hotfix_xxx_xxx
分支提merge请求到master
-
代码提交流程
即
git push origin branch_xxx
之前必须先pull即git pull origin dev
一下远程dev
代码完整操作流程如下
-
$ git checkout dev
$ git pull origin dev
$ git checkout -b branch_yangjinpeng #branch_yangjinpeng分支名自己定
开发新功能...
开发完成后...
$ git status -s #检查所有git缓存区文件是否时自己需要提交的
$ git add . #添加需要提交的文件到git缓存区
$ git commit -m "feat:新组建Base_XXX开发其他说明xxxxxx"
$ git pull origin dev #避免将冲突提交到远程在本地解决完与主分支的冲突然后再提交
冲突解决完成...
$ git add .
$ git commit -m "fix:解决冲突其他说明xxxxxx"
$ git push origin dev
至此结束一个git版本管理流程
再继续新组件开发重新走流程git checkout dev