Skip to content

fengling-inc/frontend-code-guide

Repository files navigation

前端代码规范

本文档定义了 HTML、CSS、JavaScript 以及 Vue 的编写格式和代码规范。旨在促进协作编程、提高代码质量并提供相应的编辑器配置和构建检查工具。

掌握本规范的最好方法是安装并在自己的代码中使用它。

约定

  • 🚨表示强制,必须执行
  • 📌表示强烈建议,非特殊情况下,必须执行
  • 💡表示建议,尽可能执行
  • ✅表示编辑器保存后可以自动修复一些该规则报告的问题

目录

项目规范

项目命名

🚨全部采用小写方式,以中划线分隔。

例:project-name

目录命名

🚨全部采用小写方式,以中划线分隔。

📌有复数结构时,要采用复数命名法。

例:scripts, assets, components, main-header

文件命名

🚨全部采用小写方式,以中划线分隔。

例:index.html, test-data.js, error-report.sass, custom-component.vue

返回目录 ⏫

Git

设置用户名

📌在项目中配置 Git 的 username 和 email,并设置为花名和公司邮箱

git config user.name ${花名}
git config user.email ${公司邮箱}

commits

📌Git Commit Message 按 「git Conventional Commits」 约定 提交。 待定

分支命名

🚨分支分为三种:版本分支、特性分支、BUG分支。

  • 版本分支: 一般无需管理,主要有:master, release, dev
  • 特性分支: 新需求时启用此分支,命名为: f_{username}{desc}${date:yyyyMMddHH}
  • BUG 分支: 当有 BUG 时启用此分支,命名为: b_{username}{desc}${date:yyyyMMddHH}

💡命名需要简洁易懂。

b_qianxun_fixTmPropsTooltipBug_20171211   # bad
b_qianxun_tianmao_props_tooltip_20171211  # good

返回目录 ⏫

HTML

语法

🚨用四个空格来代替制表符(tab)。 —— 「这是唯一能保证在所有环境下获得一致展现的方法」
🚨嵌套元素应当缩进一次(即四个空格)。
🚨对于属性的定义,永远全部使用双引号,绝对不要使用单引号。
🚨属性名全小写,用中划线做分隔符。
🚨不要省略可选的结束标签(closing tag)(例如,</li></body>)。
📌不要在自闭合(self-closing)元素的尾部添加斜线。 —— 「HTML5 规范 中明确说明斜线是可忽略的」

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

语言属性

📌强烈建议为 html 根元素指定 lang 属性,从而为文档设置正确的语言。

根据 HTML5 规范:

强烈建议为 html 根元素指定 lang 属性,从而为文档设置正确的语言。这将有助于语音合成工具确定其所应采用的发音,有助于翻译工具确定其翻译时所应遵守的规则等等。

更多关于 lang 属性的知识可以从 此规范 中了解。Sitepoint 站点上 给出了一份语言代码表

<html lang="en">
    <!-- ... -->
</html>

IE 兼容模式

📌强烈建议将 IE 浏览器指定为 edge mode 模式。

IE 支持通过特定的 <meta> 标签来确定绘制当前页面所应该采用的 IE 版本。除非有强烈的特殊需求,否则最好是设置为 edge mode,从而通知 IE 采用其所支持的最新的绘制模式。

了解更多信息请阅读 Stack Overflow 上的文章。

<meta http-equiv="x-ua-compatible" content="ie=edge">

协议

📌尽可能使用「https:」声明协议头。

除非图像等资源不能通过 HTTPS 获得,否则请始终对图像和其他媒体文件、样式表和脚本使用 HTTPS。

<!-- bad -->
<script src="http://alicdn.dancf.com/package/hlg-ui@0.26.4/index.js"></script>

<!-- good -->
<script src="https://alicdn.dancf.com/package/hlg-ui@0.26.4/index.js"></script>

属性顺序

💡HTML 属性应当按照以下给出的顺序依次排列,确保代码的易读性。

  • class
  • id, name
  • data-*
  • src, for, type, href, value
  • title, alt
  • role, aria-*

class 用于标识高度可复用组件,因此应该排在首位。id 用于标识具体组件,应当谨慎使用,因此排在第二位。

<a class="..." id="..." data-toggle="modal" href="#">
    Example link
</a>

<input class="form-control" type="text">

<img src="..." alt="...">

布尔(boolean)型属性

💡布尔型属性可以在声明时不赋值。

XHTML 规范要求为其赋值,但是 HTML5 规范不需要。更多信息请参考 WhatWG 的 Boolean attributes

元素的布尔型属性如果有值,就是 true,如果没有值,就是 false

如果一定要为其赋值的话,请参考 WhatWG 规范:

If the attribute is present, its value must either be the empty string or a value that is an ASCII case-insensitive match for the attribute's canonical name, with no leading or trailing whitespace.

<input type="text" disabled>

<input type="checkbox" value="1" checked>

<select>
    <option value="1" selected>1</option>
</select>

减少标签的数量

💡编写 HTML 代码时,尽量避免多余的父元素。

<!-- Not so great -->
<span class="avatar">
  <img src="...">
</span>

<!-- Better -->
<img class="avatar" src="...">

JavaScript 生成的标签

📌通过 JavaScript 生成的标签让内容变得不易查找、编辑,并且降低性能。能避免时尽量避免。

语义化和实用性

📌强烈推荐遵循 HTML 标准和语义,但是不要以牺牲实用性为代价。任何时候都要尽量使用最少的标签并保持最小的复杂度。

<!-- bad -->
<div onclick="goToItems();">jump to items</div>

<!-- good -->
<a href="/items">jump to items</a>

返回目录 ⏫

CSS

缩进

🚨✅使用四个空格来代替制表符 (tab) 缩进。

.element {
    position: absolute;
    top: 10px;
    left: 10px;

    border-radius: 10px;
    width: 50px;
    height: 50px;
}

分号

🚨✅每个属性声明末尾都要加分号。「.sass」文件除外。

.element {
    width: 20px;
    height: 20px;

    background-color: red;
}

空格

🚨以下几种情况不需要空格:

  • ✅属性名后
  • 多个规则的分隔符「,」前
  • !important!」后
  • 属性值中「(」后和「)」前
  • ✅行末不要有多余的空格

🚨以下几种情况需要空格:

  • ✅每条声明语句的「:」后
  • ✅选择器「>」、「+」、「~」前后
  • ✅在每个声明块的左花括号前,「{」前
  • !important!」前
  • 属性值中的「,」后
  • 注释「\*」后和「*/」前
/* bad */
.element {
    color :red! important;
    background-color: rgba(0,0,0,.5);
}

/* good */
.element {
    color: red !important;
    background-color: rgba(0, 0, 0, .5);
}

/* bad */
.element ,
.dialog{
    ...
}

/* good */
.element,
.dialog {
    ...
}

/* bad */
.element>.dialog{
    ...
}

/* good */
.element > .dialog {
    ...
}

/* bad */
.element{
    ...
}

/* good */
.element {
    ...
}

/* bad */
@media screen and ( max-width: 300px ){
    body {
        background-color:lightblue;
    }
}

/* good */
@media screen and (max-width: 300px) {
    body {
        background-color: lightblue;
    }
}

换行

🚨以下几种情况不需要换行:

  • {」前

🚨以下几种情况需要换行:

  • {」后和「}」前
  • 每个属性独占一行
  • 多个规则的分隔符「,」后
/* bad */
.selector, .selector-secondary, .selector[type=text]{
    padding:15px;
    margin:0px 0px 15px;
    background-color:rgba(0, 0, 0, 0.5);
    box-shadow:0px 1px 2px #CCC,inset 0 1px 0 #FFFFFF
}

/* good */
.selector,
.selector-secondary,
.selector[type="text"] {
    margin-bottom: 15px;
    padding: 15px;

    background-color: rgba(0, 0, 0, .5);

    box-shadow: 0 1px 2px #ccc, inset 0 1px 0 #fff;
}

引号

🚨✅最外层统一使用双引号;

🚨url的内容要用引号;

🚨属性选择器中的属性值需要引号。

.element:after {
    content: "";

    background-image: url("logo.png");
}

li[data-type="single"] {
    ...
}

命名

🚨按照 bem 的规则命名

.template-card{
    ...
}

.template-card__image{
    ...
}

.template-card__imageactive{
    ...
}

颜色

🚨✅颜色16进制用小写字母;

🚨✅颜色16进制尽量用全写;

/* bad */
.element {
    color: #ABCDEF;
    background-color: #001122;
}

/* good */
.element {
    color: #abcdef;
    background-color: #012;
}

属性简写

📌在需要显示地设置所有值的情况下,应当尽量限制使用简写形式的属性声明。

常被滥用的简写属性如下:

  • padding
  • margin
  • font
  • background
  • border
  • border-radius

大部分情况下,我们不需要为简写形式的属性声明指定所有值。例如,HTML 的标题元素只需要设置上、下边距(margin)的值,因此,在必要的时候,只需覆盖这两个值就可以了。0 值表示对浏览器默认值或以前指定的值的覆盖。

过度的使用属性简写会导致代码出现不必要的覆盖和意外的副作用。

在 MDN 上有一篇关于 shorthand properties 的写得非常好的文章,对于不太熟悉简写属性声明及其行为的同学可以看看。

/* bad */
.element {
    margin: 0 0 10px;
    background: red;
    background: url("image.jpg");
    border-radius: 3px 3px 0 0;
}

/* good */
.element {
    margin-bottom: 10px;
    border-top-left-radius: 3px;
    border-top-right-radius: 3px;

    background-color: red;
    background-image: url("image.jpg");
}

属性声明顺序

📌✅相关的属性声明按顺序做分组处理,组之间需要有一个空行。

这是推荐的属性的顺序

.declaration-order {
    display: block;
    float: right;

    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 100;

    border: 1px solid #e5e5e5;
    border-radius: 3px;
    width: 100px;
    height: 100px;

    font: normal 13px "Helvetica Neue", sans-serif;
    line-height: 1.5;
    text-align: center;

    color: #333;
    background-color: #f5f5f5;

    opacity: 1;
}

带前缀的属性

📌✅当使用特定厂商的带有前缀的属性时,通过缩进的方式,让每个属性的值在垂直方向对齐,这样便于多行编辑。

.element {
    -webkit-border-radius: 3px;
       -moz-border-radius: 3px;
            border-radius: 3px;

    background: -webkit-linear-gradient(top, #fff 0, #eee 100%);
    background:    -moz-linear-gradient(top, #fff 0, #eee 100%);
    background:         linear-gradient(top, #fff 0, #eee 100%);
}

清除浮动

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

如希望使用更小副作用的清除浮动方法,参见 A new micro clearfix hack 一文。对已经触发 BFC 的元素不需要再进行 clearfix。

.clearfix {
    /**
     * IE < 8
     * Include this rule to trigger hasLayout and contain floats.
     */
    zoom: 1;

    /**
     * For modern browsers
     * 1. The space content is one way to avoid an Opera bug when the
     *    contenteditable attribute is included anywhere else in the document.
     *    Otherwise it causes space to appear at the top and bottom of elements
     *    that are clearfixed.
     * 2. The use of `table` rather than `block` is only necessary if using
     *    `:before` to contain the top-margins of child elements.
     */
    &:before,
    &:after {
        content: " "; /* 1 */
        display: table; /* 2 */
    }
    &:after {
        clear: both;
    }
}

Less 和 Sass 中的嵌套

📌避免不必要的嵌套。

这是因为虽然你可以使用嵌套,但是并不意味着应该使用嵌套。只有在必须将样式限制在父元素内(也就是后代选择器),并且存在多个需要嵌套的元素时才使用嵌套。

扩展阅读:Nesting in Sass and Less

/* bad */
.table > thead > tr > th {
    ...
}
.table > thead > tr > td {
    ...
}

/* bad */
.table > thead > tr {
    > th {
        ...
    }
    > td {
        ...
    }
}

/* best use bem */
.table {
    > &__item {
        ...
    }
}

注释

💡请确保你的代码能够自描述、注释良好并且易于他人理解。好的代码注释能够传达上下文关系和代码目的。不要简单地重申组件或 class 名称。

对于较长的注释,务必书写完整的句子;对于一般性注解,可以书写简洁的短语。

/* bad */
/* Modal header */
.modal-header {
    ...
}

/* good example */
/* Wrapping element for .modal-title and .modal-close */
.modal-header {
    ...
}

返回目录 ⏫

JavaScript

缩进

🚨✅使用四个空格进行缩进。

eslint: indent

🚨✅禁止混合使用空格与 tab 作为缩进。

eslint: no-mixed-spaces-and-tabs

🚨✅禁止使用制表符(tab)。

eslint: no-tabs

function hello(name) {
    console.log('hi', name)
}

单行长度

🚨避免一行代码超过160个字符(包含空格)。

eslint: max-len

虽然现在基本人手拥有大屏显示器作为开发工具,但是过长的代码仍然不利于可读性和可维护性,所以该换行时就要换行。

// bad
$.ajax({ method: 'POST', url: 'https://baidu.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this name.'));

// good
$.ajax({
    method: 'POST',
    url: 'https://baidu.com/',
    data: { name: 'John' },
})
    .done(() => console.log('Congratulations!'))
    .fail(() => console.log('You have failed this name.'));

空格

🚨✅关键字后面加空格。

eslint: keyword-spacing

🚨✅运算符(space-infix-ops)周围要有空格。

eslint: space-infix-ops

🚨✅逗号后面加空格

eslint: comma-spacing

🚨✅单行代码块两边加空格

eslint: block-spacing

🚨✅除了缩进,不要使用多个空格。

eslint: no-multi-spaces

🚨✅行末不留空格。

eslint: no-trailing-spaces

🚨✅属性前面不要加空格。

eslint: no-whitespace-before-property

🚨✅分号后面必须有一个空格,前面不能有空格。

eslint: semi-spacing

🚨✅代码块首尾留空格。

eslint: space-before-blocks

🚨✅圆括号间不留空格。

eslint: space-in-parens

🚨✅一元运算符后面跟一个空格。

eslint: space-unary-ops

🚨✅注释首尾留空格。

eslint: spaced-comment

🚨✅模板字符串中变量前后不加空格。

eslint: template-curly-spacing

🚨✅键值对当中冒号与值之间要留空格。

eslint: key-spacing

🚨✅展开运算符与它的表达式间不要有空格。

eslint: rest-spread-spacing

🚨✅代码块中避免多余留白

eslint: padded-blocks

// bad
if(condition){ ... }

// good
if (condition) { ... }

// bad
const x=y+5;

// good
const x = y + 5;

// bad
function foo() {return true;}
if (foo) { bar = 0;}

// good
function foo() { return true; }
if (foo) { bar = 0; }

// bad
for (let i = 0 ;i < items.length ;i++) {...}

// good
for (let i = 0; i < items.length; i++) {...}

// bad
typeof!admin

// good
typeof !admin

// bad
const message = `Hello, ${ name }`;

// good
const message = `Hello, ${name}`;

// bad
const obj = { 'key' : 'value' };
const obj = { 'key' :'value' };
const obj = { 'key':'value' };

// good
const obj = { 'key': 'value' };

// bad
fn(... args);

// good
fn(...args);

if (user) {
                            // bad
    const name = getName();

}

// good
if (user) {
    const name = getName();
}

引号

🚨✅最外层统一使用单引号。

eslint: quotes

🚨✅只对那些无效的标示使用引号 ''

eslint: quote-props

// bad
const name = "Capt. Janeway";

// bad
const name = `Capt. Janeway`;

// good
const name = 'Capt. Janeway';

// bad
const bad = {
    'foo': 3,
    'bar': 4,
    'data-blah': 5,
};

// good
const good = {
    foo: 3,
    bar: 4,
    'data-blah': 5,
};

分号

🚨✅结束语句后面需要加分号

eslint: semi

当 JavaScript 遇到没有分号结尾的一行,它会执行自动插入分号 Automatic Semicolon Insertion的规则来决定行末是否加分号。如果 JavaScript 在你的断行里错误的插入了分号,就会出现一些古怪的行为。当新的功能加到 JavaScript 里后,这些规则会就变得更复杂难懂。显示的结束语句,并通过配置代码检查去捕获没有带分号的地方可以帮助你防止这种错误。

Read more.

// bad
(function () {
    const name = 'Skywalker'
    return name
})()

// good
(function () {
    const name = 'Skywalker';
    return name;
}());

// good, 行首加分号,避免文件被连接到一起时立即执行函数被当做变量来执行。
;(() => {
    const name = 'Skywalker';
    return name;
}());

逗号

🚨✅多行结尾要有逗号。

eslint: comma-dangle

多行结尾添加逗号可以使 git diffs 更清洁。另外,像 Babel 这样的转换器会删除转换代码中的额外的逗号,这意味着你不必担心旧版浏览器~~(我们也不需要支持旧版 IE)~~中的结尾逗号问题。

🚨✅始终将逗号置于行末。

eslint: comma-style

// bad - 没有结尾逗号的 git diff
const hero = {
    firstName: 'Florence',
-    lastName: 'Nightingale'
+    lastName: 'Nightingale',
+    inventorOf: ['coxcomb chart', 'modern nursing']
};

// good - 有结尾逗号的 git diff
const hero = {
    firstName: 'Florence',
    astName: 'Nightingale',
+    inventorOf: ['coxcomb chart', 'modern nursing'],
};

括号

🚨return 语句中的赋值必需有括号包裹。

eslint: no-return-assign

🚨✅else 关键字要与花括号保持在同一行。

eslint: brace-style

🚨✅ 多行 if 语句的的括号不能省。

eslint: curly

🚨 条件语句中赋值语句使用括号包起来。这样使得代码更加清晰可读,而不会认为是将条件判断语句的全等号(===)错写成了等号(=)。

eslint: no-cond-assign

// bad
function sum(a, b) {
    return result = a + b;
}

// good
function sum(a, b) {
    return (result = a + b);
}

// good
if (condition) {
} else {
}

// bad
if (condition)
{
}
else
{
}

// good
if (options.quiet !== true) console.log('done');

// good
if (options.quiet !== true) {
    console.log('done');
}

// bad
if (options.quiet !== true)
console.log('done');

// good
while ((m = text.match(expr))) { }

// bad
while (m = text.match(expr)) { }

空行

🚨✅不允许有连续多行空行。

eslint: no-multiple-empty-lines

🚨✅文件末尾留一空行。

eslint: eol-last

// bad
const value = 'hello world';


console.log(value);

// good
const value = 'hello world';
console.log(value);

换行

🚨✅对象属性换行时注意统一代码风格。

eslint: object-property-newline

// bad
const user = {
    name: 'Jane Doe', age: 30,
    username: 'jdoe86',
};

// good
const user = { name: 'Jane Doe', age: 30, username: 'jdoe86' };

// good
const user = {
    name: 'Jane Doe',
    age: 30,
    username: 'jdoe86',
};

变量与声明

📌不要定义未使用的变量。

eslint: no-unused-vars

🚨使用浏览器全局变量时加上 window. 前缀。
documentconsolenavigator 除外。

eslint: no-undef

🚨✅声明变量使用 letconst 而不是 var

eslint: no-var

🚨每个变量都用一个 constlet

eslint: one-var

🚨不要修改使用 const 声明的变量。

eslint: no-const-assign

🚨不要对变量使用 delete 操作。

eslint: no-delete-var

🚨不要重复声明变量。

eslint: no-redeclare

🚨避免将变量赋值给自己。

eslint: no-self-assign

🚨避免将变量与自己进行比较操作。

esint: no-self-compare

🚨不要使用 undefined 来初始化变量。

eslint: no-undef-init

🚨循环语句中注意更新循环变量。

eslint: no-unmodified-loop-condition

🚨不要随意更改关键字的值。

eslint: no-shadow-restricted-names

function myFunction () {
    // bad unused result
    const result = something();
}

// good
const link = window.location.href;

// bad
var silent = true, verbose = true;
let silent = true,
    verbose = true;

// good
let silent = true;
let verbose = true;


// bad
let name = 'John';
let name = 'Jane';

// good
let firstName = 'John';
firstName = 'Jane';

// bad
name = name;
if (name === name) {}

// bad
let foo = undefined;

// good
let bar;
bar = 'value';

// bad
for (let i = 0; i < items.length; j++) {...}

// good
for (let i = 0; i < items.length; i++) {...}

// bad
let undefined = 'value';

变量命名

🚨对于变量和函数名统一使用驼峰命名法。

eslint: camelcase

// bad
function my_function () { }

// good
function myFunction () { }

// bad
const my_var = 'hello';

// good
const myVar = 'hello';

函数

🚨✅函数声明时括号与函数名间加空格。

eslint: space-before-function-paren

🚨✅函数调用时标识符与括号间不留间隔。

eslint: func-call-spacing

🚨构造函数要以大写字母开头。

eslint: new-cap

🚨无参的构造函数调用时要带上括号。

eslint: new-parens

🚨不要定义冗余的函数参数。

eslint: no-dupe-args

🚨避免多余的函数上下文绑定。

eslint: no-extra-bind

🚨不要使用多余的括号包裹函数。

eslint: no-extra-parens

🚨避免对声明过的函数重新赋值。

eslint: no-func-assign

🚨嵌套的代码块中禁止再定义函数。

eslint: no-inner-declarations

🚨不要将全局对象的属性作为函数调用。

eslint: no-obj-calls

🚨自调用匿名函数 (IIFEs) 使用括号包裹。

eslint: wrap-iife

🚨禁止使用 Function 构造器。

eslint: no-new-func

🚨避免不必要的 .call().apply()

eslint: no-useless-call

🚨returnthrowcontinuebreak 后不要再跟代码。

eslint: no-unreachable

🚨避免使用 arguments.calleearguments.caller

eslint: no-caller

// bad
function name ( arg ) { ... }
run(function () { ... });
console.log ('hello');

// good
function name(arg) { ... }
run(function() { ... });
console.log('hello');

// bad
function animal() {}
const dog = new animal;
const dog = new animal();

// good
function Animal() {}
const dog = new Animal();

// bad
function sum(a, b, a) {}

// good
function sum(a, b, c) {}

// bad
const name = function() {
    getName();
}.bind(user);

// good
const name = function() {
    this.getName();
}.bind(user);

// bad
const myFunc = (function() {});

// bad
function myFunc() {}
myFunc = myOtherFunc;

if (authenticated) {
    // bad
    function setAuthUser() {}
}

// bad
const math = Math();

// bad
const getName = function() { }();

// good
const getName = (function() { }());
const getName = (function() { })();

// bad
const sum = new Function('a', 'b', 'return a + b');

// bad
sum.call(null, 1, 2, 3);

// bad
function doSomething () {
    return true;
    console.log('never called');
}

// bad
function foo (n) {
    if (n <= 0) return;
    arguments.callee(n - 1);
}

// bad
function foo (n) {
    if (n <= 0) return;
    foo(n - 1);
}

数组

🚨使用数组字面量而不是构造器。

eslint: no-array-constructor

🚨禁止使用稀疏数组(Sparse arrays)。

eslint: no-sparse-arrays

// bad
const nums = new Array(1, 2, 3);

// good
const nums = [1, 2, 3];

// bad
const fruits = ['apple',, 'orange'];

对象

🚨禁止使用 Object 构造器。

eslint: no-new-object

🚨对象中定义了存值器,一定要对应的定义取值器。

eslint: accessor-pairs

🚨对象字面量中不要定义重复的属性。

eslint: no-dupe-keys

🚨不要扩展原生对象。

eslint: no-extend-native

🚨不要对全局只读对象重新赋值。

eslint: no-global-assign

🚨外部变量不要与对象属性重名。

eslint: no-label-var

🚨new 创建对象实例后需要赋值给变量。

eslint: no-new

🚨✅避免使用不必要的计算值作对象属性。

eslint: no-useless-computed-key

🚨使用 getPrototypeOf 来替代 __proto__

eslint: no-proto

🚨禁止直接使用 ``Object.prototypes`的内置属性

eslint: [no-prototype-builtins](http://cn.eslint.org/docs/rules/no-prototype-builtins)

// bad
let config = new Object();

// bad
const person = {
    set name (value) {
        this._name = value;
    }
};

// good
const person = {
    set name (value) {
        this._name = value;
    },
    get name () {
        return this._name;
    }
};

// bad
const user = {
    name: 'Jane Doe',
    name: 'John Doe',
};

// bad
Object.prototype.age = 21;

// bad
window = {};

let score = 100
function game () {
    // bad
    score: while (true) {
        score -= 10;
        if (score > 0) continue score;
        break;
    }
}

// bad
new Character();

// good
const character = new Character();

// bad
const user = { ['name']: 'John Doe' };

// good
const user = { name: 'John Doe' };

// bad
const foo = obj.__proto__;

// good
const foo = Object.getPrototypeOf(obj);

// bad
const hasBarProperty = foo.hasOwnProperty("bar");

// good
const hasBarProperty = Object.prototype.hasOwnProperty.call(foo, "bar");

字符串

🚨禁止使用原始包装器。

eslint: no-new-wrappers

🚨不要使用多行字符串。

eslint: no-multi-str

🚨字符串字面量中也不要使用八进制转义字符。

eslint: no-octal-escape

🚨禁止不必要的转义。

eslint: no-useless-escape

// bad
const message = new String('hello');

// bad
const message = 'Hello \
                world';

// bad
const copyright = 'Copyright \251';

// bad
let message = 'Hell\o';

运算符和条件语句

🚨避免使用逗号操作符。

eslint: no-sequences

🚨不要使用非法的空白符。

eslint: no-irregular-whitespace

🚨点号操作符须与属性需在同一行。

eslint: dot-location

🚨始终使用 === 替代 ==
例外: obj == null 可以用来检查 null || undefined

eslint: eqeqeq

🚨对于三元运算符 ?: 与他们所负责的代码处于同一行

eslint: operator-linebreak

🚨避免不必要的布尔转换。

eslint: no-extra-boolean-cast

🚨不要省去小数点前面的0。

eslint: no-floating-decimal

🚨如果有更好的实现,尽量不要使用三元表达式。

eslint: no-unneeded-ternary

🚨关系运算符的左值不要做取反操作。

eslint: no-unsafe-negation

🚨检查 NaN 的正确姿势是使用 isNaN()

eslint: use-isnan

🚨用合法的字符串跟 typeof 进行比较操作。

eslint: valid-typeof

🚨请书写优雅的条件语句(avoid Yoda conditions)。

eslint: yoda

🚨避免使用常量作为条件表达式的条件(循环语句除外)。

eslint: no-constant-condition

// bad
if (doSomething(), !!test) {}

// bad
function myFunc () /*<\NBSP>*/{}

// bad
console.
log('hello');

// good
console.log('hello');

// good
if (name === 'John')

// bad
if (name == 'John')

// good
if (name !== 'John')

// bad
if (name != 'John')

// good
const location = env.development ? 'localhost' : 'www.api.com';

// good
const location = env.development
    ? 'localhost'
    : 'www.api.com';

// ✗ avoid
const location = env.development ?
'localhost' :
'www.api.com';

const result = true;

// bad
if (!!result) {}

// good
if (result) {}

// bad
const discount = .5;

// good
const discount = 0.5;

// bad
let score = val ? val : 0;

// good
let score = val || 0;

// bad
if (!key in obj) {}

// bad
if (price === NaN) { }

// good
if (isNaN(price)) { }

// bad
typeof name === 'undefimed'

// good
typeof name === 'undefined'

// bad
if (42 === age) { }

// good
if (age === 42) { }

// bad
if (false) {}

// good
if (x === 0) {}

// good
while (true) {}

正则

🚨✅正则中避免使用多个空格。

eslint: no-regex-spaces

🚨正则中不要使用控制符。

eslint: no-control-regex

🚨正则中不要使用空字符。

eslint: no-empty-character-class

🚨不要向 RegExp 构造器传入非法的正则表达式。

eslint: no-invalid-regexp

// bad
const regexp = /test   value/;

// good
const regexp = /test {3}value/;
const regexp = /test value/;

// bad
const pattern = /\x1f/;

// good
const pattern = /\x20/;
// bad
const myRegex = /^abc[]/;

// good
const myRegex = /^abc[a-z]/;

// bad
RegExp('[a-z');

// good
RegExp('[a-z]');

ES6

🚨✅所有变量的赋值都尽量用const

该规则旨在标记那些使用 let 声明,但在初始化赋值后从未被修改过的变量。

eslint: prefer-const

🚨import, export 和解构操作中,禁止赋值到同名变量。

eslint: no-useless-rename

🚨子类的构造器中一定要调用 super

eslint: constructor-super

🚨避免对类名重新赋值。

eslint: no-class-assign

🚨类中不要定义冗余的属性。

eslint: no-dupe-class-members

🚨同一模块有多个导入时一次性写完。

eslint: no-duplicate-imports

🚨不要解构空值。

eslint: no-empty-pattern

🚨禁止使用 Symbol 构造器。

Symbol 不和 new 操作符一起使用,而是作为函数调用。

eslint: no-new-symbol

🚨要求有 symbol 描述 (symbol-description)

eslint: symbol-description

🚨正确使用 ES6 中的字符串模板。

eslint: no-template-curly-in-string

🚨使用 this 前请确保 super() 已调用。

eslint: no-this-before-super

🚨禁止多余的构造器。

eslint: no-useless-constructor

🚨✅yield * 中的 * 前后都要有空格。

eslint: yield-star-spacing

🚨✅generator * 中的 * 前后都要有空格。

eslint: generator-star-spacing

🚨✅要求箭头函数的箭头前后都有空格

eslint: arrow-spacing

// bad
var name = 'John';

// good
const name = 'Jane';

// bad
import { config as config } from './config';

// good
import { config } from './config';

// bad
class Dog {
    constructor () {
        super();
    }
}

// good
class Dog extends Mammal {
    constructor () {
        super();
    }
}

// bad
Dog = 'Fido';

// bad
class Dog {
    bark () {}
    bark () {}
}

// bad
import { myFunc1 } from 'module';
import { myFunc2 } from 'module';

// good
import { myFunc1, myFunc2 } from 'module';

// bad
const { a: {} } = foo;

// good
const { a: { b } } = foo;

// bad
const foo = Symbol();
const foo = new Symbol('foo');

// good
const foo = Symbol("some description");

// bad
const message = 'Hello ${name}';

// good
const message = `Hello ${name}`;
class Dog extends Animal {
    constructor () {
        // bad
        this.legs = 4;
        super();
    }
}

// bad
class Car {
    constructor () {
    }
}

// bad
yield* increment();

// good
yield * increment();

// good
function * generator() {
    yield "44";
    yield "55";
}

// bad
(a)=>{};

// good
(a) => {};

最佳实践

🚨不要丢掉异常处理中err参数。

eslint: handle-callback-err

🚨不要使用 debugger

eslint: no-debugger

🚨switch 语句中不要定义重复的 case 分支。

eslint: no-duplicate-case

🚨不要使用 eval()

eslint: no-eval

🚨catch 中不要对错误重新赋值。

eslint: no-ex-assign

🚨switch 一定要使用 break 来将条件分支正常中断。

eslint: no-fallthrough

🚨注意隐式的 eval()

eslint: no-implied-eval

🚨禁止使用 __iterator__

eslint: no-iterator

🚨禁止使用标签语句。

eslint: no-labels

🚨不要书写不必要的嵌套代码块。

eslint: no-lone-blocks

🚨不要使用八进制字面量。

eslint: no-octal

🚨finally 代码块中不要再改变程序执行流程。

eslint: no-unsafe-finally

🚨用 throw 抛错时,抛出 Error 对象而不是字符串。

eslint: no-throw-literal

🚨禁止使用 with

eslint: no-with

// good
run(function (err) {
    if (err) throw err;
    window.alert('done');
});

// bad
run(function (err) {
    window.alert('done');
});

// bad
function sum (a, b) {
    debugger;
    return a + b
}

switch (id) {
    case 1:
    // bad
    case 1:
}

// bad
eval( "var result = user." + propName );

// good
const result = user[propName];

try {
    // ...
} catch (e) {
    // bad
    e = 'new value';
}

switch (filter) {
    case 1:
        doSomething();    // bad
    case 2:
        doSomethingElse();
}

switch (filter) {
    case 1:
        doSomething();
        break           // good
    case 2:
        doSomethingElse();
}

switch (filter) {
  case 1:
      doSomething();
      // fall through  // good too
  case 2:
      doSomethingElse();
}

// bad
setTimeout("alert('Hello world')");

// good
setTimeout(function () { alert('Hello world') });

// bad
Foo.prototype.__iterator__ = function () {};

// bad
label:
    while (true) {
        break label;
    }
// bad
function myFunc () {
    {
        myOtherFunc();
    }
}

// good
function myFunc () {
    myOtherFunc();
}

// bad
const octal = 042;

// good
const decimal = 34;
const octalString = '042';

// bad
try {
    // ...
} catch (e) {
    // ...
} finally {
    return 42;
}

// bad
throw 'error';

// good
throw new Error('error');

// bad
with (val) {...}

Node.js

🚨使用 __dirname__filename 时尽量避免使用字符串拼接。

eslint: no-path-concat

🚨禁止使用 new require

eslint: no-new-require

// bad
const pathToFile = __dirname + '/app.js';

// good
const pathToFile = path.join(__dirname, 'app.js');

// bad
const myModule = new require('my-module');

返回目录 ⏫

Vue

风格指南

请参考官方的 Vue 风格指南,这里会根据 eslint-plugin-vue 做部分改造。

缩进

🚨✅使用四个空格进行缩进。

eslint: vue/html-indentvue/script-indent

<!-- good -->
<template>
    <div class="foo">
        Hello world.
    </div>
</template>

<script>
export default {
    name: 'ExampleComponent',
    data() {
        return {
            text: 'world',
        };
    },
}
</script>

<!-- bad -->
<template>
  <div class="foo">
    Hello world.
  </div>
</template>

<script>
export default {
  name: 'ExampleComponent',
  data() {
    return {
      text: 'world',
    };
  },
}
</script>

命名方式

🚨✅HTML 模板中自定义组件名始终大写。

eslint: vue/component-name-in-template-casing

🚨自定义组件文件命名全部采用小写方式,以中划线分隔。

<!-- bad -->
<!-- FooBar.vue -->
<template>
    <div class="foo">
        <hlg-select v-model="type">
            <hlgOption v-for="item in types" :key="item.value" :label="item.text" :value="item.value" />
        </hlg-select>
    </div>
</template>

<!-- good -->
<!-- foo-bar.vue -->
<template>
    <div class="foo">
        <HlgSelect v-model="type">
            <HlgOption v-for="item in types" :key="item.value" :label="item.text" :value="item.value" />
        </HlgSelect>
    </div>
</template>

<!-- good -->
<script>
import { HlgSelect, HlgOption } from 'hlg-ui';
import FooBar from './foo-bar.vue';

export default {
    name: 'ExampleComponent',
    components: { HlgSelect, HlgOption, FooBar },
    ...
}
</script>

多个特性的元素

🚨✅多个特性的元素应该分多行撰写,每个特性一行。(单行特性最多不能超过四个)

eslint: vue/max-attributes-per-line

<!-- bad -->
<template>
    <HlgSelect v-model="type" multiple size="small" placeholder="请选择" @change="onChange">
        <HlgOption v-for="item in types" :key="item.value" :label="item.text" :value="item.value" :disabled="item.disabled" />
    </HlgSelect>
</template>

<!-- good -->
<template>
    <HlgSelect
        v-model="type"
        multiple
        size="small"
        placeholder="请选择"
        @change="onChange"
    >
        <HlgOption
            v-for="item in types"
            :key="item.value"
            :label="item.text"
            :value="item.value"
            :disabled="item.disabled"
        />
    </HlgSelect>
</template>

换行

🚨✅在单行元素的内容的之前和之后需要换行

以下标签除外:

&lt;pre&gt;, &lt;textarea&gt;, &lt;a&gt;, &lt;abbr&gt;, &lt;audio&gt;, &lt;b&gt;, &lt;bdi&gt;, &lt;bdo&gt;, &lt;canvas&gt;, &lt;cite&gt;, &lt;code&gt;, &lt;data&gt;, &lt;del&gt;, &lt;dfn&gt;, &lt;em&gt;, &lt;i&gt;, &lt;iframe&gt;, &lt;ins&gt;, &lt;kbd&gt;, &lt;label&gt;, &lt;map&gt;, &lt;mark&gt;, &lt;noscript&gt;, &lt;object&gt;, &lt;output&gt;, &lt;picture&gt;, &lt;q&gt;, &lt;ruby&gt;, &lt;s&gt;, &lt;samp&gt;, &lt;small&gt;, &lt;span&gt;, &lt;strong&gt;, &lt;sub&gt;, &lt;sup&gt;, &lt;svg&gt;, &lt;time&gt;, &lt;u&gt;, &lt;var&gt;, &lt;video&gt;, &lt;li&gt;, &lt;route&gt;-link', &lt;template&gt;, &lt;button&gt;, &lt;p&gt;, &lt;h1&gt;, &lt;h2&gt;, &lt;h3&gt;, &lt;h4&gt;, &lt;h5&gt;, &lt;h6&gt;

eslint: vue/singleline-html-element-content-newline

<template>
    <!-- good -->
    <div attr>
        content
    </div>

    <tr attr>
        <td>data</td>
        <td>data</td>
    </tr>

    <div attr>
        <!-- comment -->
    </div>

    <!-- bad -->
    <div attr>content</div>

    <tr attr><td>data</td><td>data</td></tr>

    <div attr><!-- comment --></div>
</template>

返回目录 ⏫

编辑器和代码配置

VSCode

  1. 安装插件

  2. 修改编辑器配置

    {
        "editor.tabSize": 4,                  // tab 为 4 个空格
        "emmet.triggerExpansionOnTab": true,  // 按下 tab 时展开 Emmet 缩写
        "vetur.completion.autoImport": false,
        "files.trimTrailingWhitespace": true, // 保存文件时删除文件末尾的空格
        "csscomb.formatOnSave": true,         // 保存文件时自动修复 CSScomb 问题
        "editor.codeActionsOnSave": {
            "source.fixAll.eslint": true      // 保存文件时自动修复 Eslint 问题
        }
    }

返回目录 ⏫

代码配置

  1. 安装依赖

    npm install --save-dev eslint eslint-plugin-import eslint-config-gaoding eslint-plugin-vue eslint-config-gaoding-vue
  2. 在根目录创建一个 .eslintrc 文件,并输入以下内容:

    {
        "env": {
            "browser": true,
            "node": true
        },
        "extends": [
            "gaoding",
            "gaoding-vue"
        ]
    }
  3. 复制 .csscomb.json 配置至根目录。

相关文档