前端代码规范

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
    • idname
    • data-*
    • srcfortypehrefvalue
    • titlealt
    • rolearia-*

    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-classid-for-labelid-for-anchor

    除了一个特例:当 class 或 id 用作 JS 操作 DOM 的钩子时,可以加上特定前缀以明确标识仅用于 JS 操作,需注意不要再将此类 class 或 id 用于 CSS。推荐的命名规则为:以 J_ 为前缀,后接小驼峰,例如 J_exampleClassForJsJ_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【参考】属性声明的顺序。

    相关联的属性声明最好写成一组,并按如下顺序排序:

    1. 定位:如 position、left、right、top、bottom、z-index
    2. 盒模型:如 display、float、width、height、margin、padding、border
    3. 文字排版:如 font、color、line-height、text-align
    4. 外观:如 background
    5. 其他属性

    「定位」和「盒模型」放在最前面,是因为它们决定了元素的布局、位置和尺寸。「定位」排在「盒模型」之前,是由于「定位」属性可以让元素脱离正常文本流,从而使「盒模型」属性失效。

    除了「定位」和「盒模型」,其他属性都只在元素内部起作用,不会对前两类属性的结果产生影响,因此放在后面。

    .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

    当嵌套选择器的层级过深时,可能带来一些副作用:

    建议选择器嵌套层级不要超过 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()doif() 或 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,它要求大括号与控制语句放在同一行),且单行代码块可不换行,具体规则如下:

    • 左大括号 { 前面不换行,后面换行
    • 右大括号 } 前面换行
    • 右大括号 } 后面是否换行有两种情况:
      • 如果 } 终结了整个语句,如条件语句、函数或类的主体,则需要换行
      • 如果 } 后面存在 elsecatchwhile 等语句,或存在逗号、分号、右小括号()),则不需要换行
    // 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');
    }

    控制语句的关键字,如 ifelseelse 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.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) { // false(0 相当于 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.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

    以下数组方法:mapfilterfromeveryfindfindIndexreducereduceRightsomesort 的回调函数中必须包含 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 上的方法,例如 hasOwnPropertypropertyIsEnumerableisPrototypeOf

    这些方法可能会被对象上的属性覆盖,导致错误:

    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.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

    在非函数块(如 ifwhile 等)中,不要使用函数声明:

    // 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.19【强制】函数的参数列表中禁止出现重复命名的参数。eslint: no-dupe-args
  • 2.5.20【强制】不要对函数声明重新赋值。eslint: no-func-assign
  • 2.5.23【强制】禁止使用 arguments.caller 和 arguments.callee。eslint: no-caller
  • 2.5.25【强制】禁止不必要的 .bind() 调用。eslint: no-extra-bind
  • 2.5.26【强制】禁止在循环中的函数内出现外部作用域中定义且会发生变化的变量,以防止闭包副作用。eslint: no-loop-func
  • 2.5.27【强制】禁止单独 new 一个构造函数而不用于赋值或比较。eslint: no-new
  • 2.5.29【强制】禁止不必要的 return await。eslint: no-return-await
  • 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.9 控制语句

  • 2.9.1【强制】不要让 case 语句落空。eslint: no-fallthrough

    使用 breakreturn 或 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.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.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 、processObjectundefined 等。除了像 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.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.22【强制】禁止使用 with。eslint: no-with

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 配套工具

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 风格,具体规则如下:

    • 左大括号 { 前面不换行,后面换行
    • 右大括号 } 前面换行
    • 右大括号 } 后面是否换行有两种情况:
      • 如果 } 终结了整个语句,如条件语句、函数或类的主体,则需要换行
      • 如果 } 后面存在 elsecatchwhile 等语句,或存在逗号、分号、右小括号()),则不需要换行
    // 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',
    });

    控制语句(ifwhile 等)的左小括号 ( 前有一个空格:

    // 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) { // false(0 相当于 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-position

    JavaScript中的标签只能与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规范,具体见以下文档:

ESLint(javaScript/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 变量 */

StyleLint(css)规范

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、分支创建规则
  • 所有代码分支须创建和使用须遵循以下规则:

    1. 基于master(当期稳定版分支)分支创建新分支,分支命名规则为:

      【需求】 feat_功能名简称_创建日期

      【bug】 fix_bug概括名简称_创建日期

      【hotfix】hotfix_功能名简称_创建日期

    2. 分支代码提测/发版流程:

      如,当期需求分支在feat_test_xxx

      【提测流程】:feat_test_xxx提merge请求到test(受保护)分支;

      【发版流程】:test分支提merge请求到master(受保护)分支;

      【紧急hotfix发版流程】:hotfix_xxx_xxx分支提merge请求到master

    3. 代码提交流程:

      即: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

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 百度前端代码规范pdf是百度公司出品的一份标准化的前端代码书写规范,旨在提高前端开发人员的代码质量和可维护性。该规范涵盖HTML、CSS、JavaScript等各个方面的编码规范和约定,包括命名规范代码缩进、注释规范、选择器命名、代码风格等多个方面。规范中还提供了实用的代码示例和用法说明,方便开发人员快速上手。遵循该规范能够让代码更易于阅读和理解,提高代码的可读性,同时也有助于团队协作和代码维护。总之,百度前端代码规范pdf是一份非常实用的前端开发规范指南,对于提高代码质量和团队效率都有着积极的作用。 ### 回答2: 百度前端代码规范pdf是一份非常重要的前端开发规范文档,对于前端工程师来说是必修的。百度前端开发规范内容包括HTML、CSS、JavaScript代码规范代码格式化、代码组织,以及前后端分离、性能优化、安全性等方面的规范;同时也包括了不正确的代码示例和详细的错误解释,方便读者理解。在实际的前端开发中,严格遵守百度前端代码规范pdf,可以避免很多常见的前端开发问题,提高代码可读性、可维护性和可扩展性,最终让代码更加优秀。 此外,百度前端代码规范pdf文档还提供了一个开放的GitHub仓库,方便开发者提交规范更新和BUG修复,并给出了详细的贡献指南。这些都能够增加前端开发者社区的参与度,提高整个前端开发行业的水平。因此,建议所有前端开发者都仔细阅读这份百度前端代码规范pdf文档,并将其融入到自己的开发工作中。 ### 回答3: 百度前端代码规范PDF是一份非常有价值的前端开发规范文档,它包含了丰富而详细的代码规范和最佳实践,可以帮助开发者写出更加规范、高效、可读性强的代码。 首先,百度前端代码规范PDF对整个前端开发的流程有着清晰的分工,从HTML、CSS、JavaScript代码实现、性能优化、组件化等多个方面提出了相应的规范和建议,使得开发人员可以在不同的阶段上更好地进行协作。 其次,它还介绍了实用的工具和技术,包括Git、ESLint、Webpack等,这些工具和技术能够极大地提高开发效率和代码效率,使得团队的代码质量得到了很大的提升。 此外,百度前端代码规范PDF还具备可读性和实用性,它用简洁明了的语言和许多实例说明了每一条规则和最佳实践,这样开发者可以更好地理解这些规范并应用到实际的项目中。 总之,百度前端代码规范PDF是一份非常有价值的前端开发规范文档,它可以为开发者提供很多有用的建议并且帮助他们在团队协作中流畅高效地完成各自的工作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值