前端开发代码规范

前端开发代码规范

本文介绍前端开发js规范和react规范。html规范和css,less规范以后再说。

JavaScript 编码规约

1 编码风格

1.1 缩进

1.1.1【强制】使用 2 个空格缩进。eslint: indent

统一使用 2 个空格缩进,不要使用 4 个空格或 tab 缩进:

// bad
function foo() {
∙∙∙∙let name;
}

// good
function foo() {
∙∙let name;
}

1.2 分号

1.2.1【强制】使用分号。eslint: semi

统一以分号结束语句,可以避免 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【强制】对于用逗号分隔的多行结构,不使用行首逗号。eslint: comma-style

// 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.3.2【强制】对于用逗号分隔的多行结构,始终加上最后一个逗号。eslint: comma-dangle

这样可以使增删行更加容易,也会使 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
) {
  // ...
}

1.4 块

术语解释:块(block) 可以理解为类、函数、控制语句等由大括号 {} 分隔的代码块状结构,由一对大括号界定,用于组合若干条语句 了解更多

1.4.1【推荐】始终使用大括号包裹代码块。eslint: curly, nonblock-statement-body-position

多行代码块必须用大括号包裹:

// 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.2【强制】对于非空代码块,大括号的换行方式采用 Egyptian Brackets 风格。eslint: brace-style

具体规则如下:

  • 左大括号 { 前面不换行,后面换行

  • 右大括号 } 前面换行

  • 右大括号 } 后面是否换行有两种情况:

    • 如果 } 终结了整个语句,如条件语句、函数或类的主体,则需要换行
    • 如果 } 后面存在 elsecatchwhile 等语句,或存在逗号、分号、右小括号()),则不需要换行
// bad - else 应与 if 的 } 放在同一行
if (foo) {
  thing1();
}
else
  thing2();
}

// good
if (foo) {
  thing1();
} else {
  thing2();
}

1.4.3【推荐】对于空代码块,且不在类似 if..else..try..catch..finally.. 的多块结构中时,可以立即将大括号闭合。eslint: brace-style

// good
function doNothing() {}


如果空代码块在多块结构中,则仍需要按上一条非空块的规则换行:

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

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

// bad
try {
  // …
} catch (e) {}

// good
try {
  // …
} catch (e) {
}

1.4.4【强制】不要让代码中出现空代码块,这会使阅读者感到困惑。如果必须使用空块,需在块内写明注释。eslint: no-empty

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

// good
if (condition) {
  thing1();
} else {
  // TODO I haven’t determined what to do.
}

1.5 空格

1.5.1【强制】块的左大括号 { 前有一个空格。eslint: space-before-blocks

// 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',
});

1.5.2【强制】控制语句(ifwhile 等)的左小括号 ( 前有一个空格。声明函数时,函数名和参数列表之间无空格。eslint: keyword-spacing

// bad
if(isJedi) {
  fight ();
}

// good
if (isJedi) {
  fight();
}

// bad
function fight () {
  console.log ('Swooosh!');
}

// good
function fight() {
  console.log('Swooosh!');
}

1.5.3【强制】小括号内部两侧无空格。eslint: 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);
}

1.5.4【强制】方括号内部两侧无空格。eslint: array-bracket-spacing

// bad
const foo = [ 1, 2, 3 ];
console.log(foo[ 0 ]);

// good
const foo = [1, 2, 3];
console.log(foo[0]);

1.5.5【强制】大括号内部两侧有空格。eslint: object-curly-spacing

// bad
const foo = {clark: 'kent'};

// good
const foo = { clark: 'kent' };

1.5.6【强制】运算符两侧有空格,除了一元运算符。eslint: space-infix-ops

// 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;

1.5.7【强制】定义对象字面量时,不允许所谓的「水平对齐」,即 key、value 之间应该有且只有一个空格。eslint: key-spacing

// bad
{
  a:            'short',
  looooongname: 'long',
}

// bad
{
  a           : 'short',
  looooongname: 'long',
}

// good
{
  a: 'short',
  looooongname: 'long',
}

1.5.8【强制】在使用多个(大于两个)方法链式调用时进行换行缩进,把点 . 放在前面以强调这是方法调用而不是新语句。eslint: newline-per-chained-call no-whitespace-before-property

// 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.6 空行

1.6.1【推荐】在文件末尾保留一行空行。eslint: eol-last

统一在文件末尾保留一行空行,即用一个换行符结束文件:

// 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【强制】块的开始和结束不能是空行。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.3【参考】在块末和新语句间插入一个空行。

// 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.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。eslint: max-lines

过长的文件不易阅读和维护,最好对其进行拆分。

1.7.3【推荐】函数最大行数:80。eslint: max-lines-per-function

过长的函数不易阅读和维护,最好对其进行拆分。

2 语言特性

2.1 变量声明

2.1.1【强制】使用 constlet 声明变量。eslint: no-var, no-undef

从 ES6 开始,可以使用 letconst 关键字在块级作用域下声明变量。块级作用域在很多其他编程语言中都有使用,这样声明的变量不会污染全局命名空间。

不要使用 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。eslint: prefer-const

// 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【强制】一条声明语句声明一个变量。eslint: one-var

这样做更易于追加新的声明语句(你不需要总去把最后的 ; 改成 , 了),也更易于进行单步调试。

// bad
const foo = 1,
      bar = 2;

// good
const foo = 1;
const bar = 2;

2.1.4【强制】声明的变量和表达式必须被使用。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.5【参考】哪里使用,哪里声明。

在变量被使用前再进行声明,而不是统一在块开始处进行声明。

ES6 提供的 letconst 是块级作用域,不存在类似 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.6【参考】将 letconst 分别归类

letconst 归类写在一起,可以提高代码整洁性。当然,如果你出于变量的含义或其他考虑进行排序归类也是允许的。

// 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.7【强制】禁止连续赋值。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【强制】变量不要与外层作用域已存在的变量同名。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 中,使用 constlet 重复声明变量会直接报错:

// 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.2 数字

2.2.1【强制】禁止使用 new Number()。eslint: no-new-wrappers

使用 new Number() 声明不会有任何好处,还会导致变量成为 object 类型,可能引起 bug。

// bad
const num = new Number(1);
console.log(typeof num); // => object

// good
const num = 1;
console.log(typeof num); // => number

2.2.2【推荐】类型转换:使用 Number() 或带基数的 parseInt() 方法

不要使用 new Number() 或其他隐式方式进行类型转换,会导致问题或使程序难以理解。

const str = '1';

// bad
const num = +str;
const num = str >> 0;
const num = new Number(str);

// good
const num = Number(str);
const num = parseInt(str, 10);

2.2.3【推荐】使用 parseInt() 方法时总是带上基数。eslint: radix

parseInt 方法的第一个参数是待转换的字符串,第二个参数是转换基数。当第二个参数省略时,parseInt 会根据第一个参数自动判断基数:

  • 如果以 0x 开头,则使用 16 作基数
  • 如果以 0 开头,则使用 8 作基数。正是这条规则经常导致错误,ES5 规范中直接将这条规则移除,即 ES5 及之后的执行环境以 0 开头也会使用 10 作为基数
  • 其他情况则使用 10 作基数

了解更多
`parseInt()````javascript
// bad
parseInt(“071”); // => ES5 前的执行环境中得到的是 57

// good
parseInt(“071”, 10); // => 71


[]()<a name="76dd3b9f"></a>
### 2.3 字符串

- 
2.3.1【强制】禁止使用 `new String()`。eslint: [`no-new-wrappers`](https://eslint.org/docs/rules/no-new-wrappers.html)
<br />使用 `new String()` 声明不会有任何好处,还会导致变量成为 `object` 类型,可能引起 bug。
```javascript
// bad
const str = new String('foo');
console.log(typeof str); // => object

// good
const str = 'foo';
console.log(typeof str); // => string

2.3.2【强制】类型转换:使用 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);

2.3.3【强制】优先使用单引号 ''。eslint: quotes

// bad
const name = "foo";
const name = `foo`; // 模板字符串中应包含变量或换行,否则需用单引号

// good
const name = 'foo';

2.3.4【推荐】使用模板字符串而非多个字符串的拼接。eslint: prefer-template

模板字符串让代码更简洁,可读性更强。

// bad
function getDisplayName({ nickName, realName }) {
  return nickName + ' (' + realName + ')';
}

// good
function getDisplayName({ nickName, realName }) {
  return `${nickName} (${realName})`;
}

2.3.5【参考】定义长的字符串时,不要使用字符串拼接

字符串不受单行最大 100 个字符的限制,字符串拼接会影响可读性和可检索性。

// bad
const errorMessage = 'This is a super long error that was thrown because \
of Batman. When you stop to think about how Batman had anything to do \
with this, you would get nowhere \
fast.';

// bad
const errorMessage = 'This is a super long error that was thrown because ' +
  'of Batman. When you stop to think about how Batman had anything to do ' +
  'with this, you would get nowhere fast.';

// good
const errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.';

2.3.6【强制】禁止不必要的转义字符

转义字符会大大降低代码的可读性,因此尽量不要滥用它们。

// bad
const foo = '\'this\' \i\s \"quoted\"';

// good
const foo = '\'this\' is "quoted"';
const foo = `'this' is "quoted"`;

2.4 布尔值

2.4.1【强制】禁止使用 new Boolean()。eslint: no-new-wrappers

使用 new Boolean() 声明不会有任何好处,还会导致变量成为 object 类型,可能引起 bug。

// bad
const bool = new Boolean(false);
console.log(typeof bool); // => object
if (bool) { // true(对象相当于 true)
}

// good
const bool = false;
console.log(typeof bool); // => boolean
if (bool) { // false
}

2.4.2【推荐】类型转换:使用 !!

const age = 0;

// bad
const hasAge = new Boolean(age);
const hasAge = Boolean(age);

// good
const hasAge = !!age;

2.4.3【强制】避免不必要的布尔类型转换。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.5 数组

2.5.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.5.2【强制】某些数组方法的回调函数中必须包含 return 语句。eslint: array-callback-return

以下数组方法:map, filter, from, every, find, findIndex, reduce, reduceRight, some, sort 的回调函数中必须包含 return 语句,否则可能会产生误用或错误。

一个常见的误用是,本该用 forEach 的场景却用了 map

// 欲将 ['a', 'b', 'c'] 转换成 {a: 0, b: 1, c: 2}
const myArray = ['a', 'b', 'c'];
const myObj = {};

// bad - map 应用于构建一个新数组,单纯想遍历数组应使用 forEach
myArray.map((item, index) => {
  myObj[item] = index;
});

// good
myArray.forEach((item, index) => {
  myObj[item] = index;
});


某些方法漏掉 return 还可能引起错误:

// 欲将 ['a', 'b', 'c'] 转换成 {a: 0, b: 1, c: 2}
const myArray = ['a', 'b', 'c'];

// bad => Uncaught TypeError: Cannot set property 'b' of undefined
const myObj = myArray.reduce(function(memo, item, index) {
  memo[item] = index;
}, {});

// good
const myObj = myArray.reduce(function(memo, item, index) {
  memo[item] = index;
  return memo;
}, {});

2.5.3【推荐】使用扩展运算符 ... 处理数组

ES6 提供了扩展运算符 ...,可以简化一些数组操作。

数组复制:

// 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 和 ... 将数组去重


数组拼接:

// bad
const array1 = [1, 2].concat(array);

// good
const array1 = [1, 2, ...array]


... 替代 apply

// bad
const args = [1, 2, 3, 4];
Math.max.apply(Math, args);

// good
const args = [1, 2, 3, 4];
Math.max(...args);


特殊的,遍历可迭代对象时,使用 Array.from 而不是 ...,以免创建一个临时数组:

// bad
const baz = [...foo].map(bar);

// good
const baz = Array.from(foo, bar);

2.5.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.6 对象

2.6.1【强制】使用字面量创建对象。eslint: no-new-object

// bad
const obj = new Object();

// good
const obj = {};

2.6.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.6.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.6.4【强制】对象的属性名不要用单引号包裹,除非包含特殊字符。eslint: quote-props

这样更加简洁,也有助于语法高亮和一些 JS 引擎的优化。

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

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

2.6.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.6.6【推荐】使用解构获取对象属性。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.6.7【参考】对象的动态属性名应直接写在字面量定义中

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.6.8【强制】不要直接在对象上调用 Object.prototypes 上的方法,例如 hasOwnPropertypropertyIsEnumerableisPrototypeOf。eslint: no-prototype-builtins

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

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.6.9【推荐】使用扩展运算符 ... 处理对象

替代 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.7 函数

2.7.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.7.2【推荐】使用函数表达式替代函数声明。eslint: func-styleno-inner-declarations

这样可以保证函数不能在定义前被调用。

函数声明会被提升到当前作用域的顶部,因此函数可以在声明语句前就被调用,这会影响代码的可读性与可维护性。

// bad
function foo() {
  // ...
}

// good
const foo = () => {
  // ...
};

const foo = function () {
  // ...
};

// 有些规范提出,应该给函数表达式起一个不同于被赋值变量名的名字,以达到易于调试、查看错误堆栈等目的
// 事实上,代码在目前浏览器中或者经过 Babel 转码后,匿名函数表达式也能够方便地查看堆栈。所以除非你出于某些目的想给函数起一个不同于被赋值变量的名字,否则直接使用匿名函数表达式
const foo = function foo_more_descriptive_name() {
  // ...
};


尤其是在非函数块(如 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.7.3【强制】使用箭头函数代替匿名函数。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.7.4【推荐】箭头函数编码风格。eslint: arrow-parens, arrow-body-style

箭头函数参数的小括号、函数体的大括号在某些时候可以省略,可能造成风格的不统一,因此需要规范其编码风格:

2.7.4.1【推荐】函数体风格

当函数体只包含一条 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,
  )
));

2.7.4.2 【推荐】函数参数风格

当函数只有一个参数,且函数体为 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.7.5【强制】不要将参数命名为 arguments

这会覆盖掉函数作用域中的 arguments 对象。

// bad
function foo(name, options, arguments) {
  // ...
}

// good
function foo(name, options, args) {
  // ...
}

2.7.6【强制】不要使用 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.7.7【强制】使用默认参数语法

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.7.8【推荐】有默认值的函数参数需要放到参数列表的最后

否则你将无法享受到默认参数的便利,只能通过传 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.7.9【推荐】不要改变函数参数。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.7.10【强制】将立即执行函数表达式(IIFE)用小括号包裹。eslint: wrap-iife

IIFE 是一个独立的执行单元,将它用小括号包裹可以更清晰的体现这点。需要提醒的是,由于 ES6 模块语法的引入,你可能不再需要使用 IIFE 了。

(function () {
  console.log('Welcome to the Internet. Please follow me.');
}());

2.7.11【推荐】函数的复杂度不应过高。eslint: complexity

过高的复杂度意味着代码难以维护和测试。我们推荐函数的复杂度不要超过以下阈值:

  • 圈复杂度不超过 10
  • 认知复杂度不超过 15

2.7.12【推荐】函数的参数不应过多

如果函数的参数过多,将不利于函数的维护和调用。这时你需要考虑是否函数做了太多的事情,是否有必要对其进行拆分。

如果必须使用过多的参数,可以考虑用对象代替参数列表:

// 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.8 类

2.8.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.8.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.8.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.8.4【强制】正确地使用 super 方法。eslint: constructor-super,no-this-before-super

  • 子类的 construtor 中必须使用 super(),且必须在 thissuper 关键词前调用
  • 非子类的 construtor 中不能使用 super()
// bad - 非子类不能使用 super
class Parent {
  constructor() {
    super();
    this.name = 'parent';
  }
}

// good
class Parent {
  constructor() {
    this.name = 'parent';
  }
}

// bad - 子类必须使用 super
class Child extends Parent {
  constructor() {
    this.name = 'child';
  }
}

// bad - this 必须在调用 super 后使用
class Child extends Parent {
  constructor() {
    this.name = 'foo';
    super();
  }
}

// good
class Child extends Parent {
  constructor (value) {
    super(value);
    this.name = 'foo';
  }
}

2.8.5【强制】避免重复的类成员命名。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.8.6【参考】如类的方法不需要特别的返回值,可以返回 this 以方便链式调用

class Box {
  constructor() {
    this.width = 0;
    this.height = 0;
  }

  setWidth(width) {
    this.width = width;
  }

  setHeight(height) {
    this.height = height;
  }
}

const box = new Box();
box.setWidth(5);
box.setHeight(10);

// good
class A {
  constructor() {
    this.width = 0;
    this.height = 0;
  }

  setWidth(width) {
    this.width = width;
    return this;
  }

  setHeight(height) {
    this.height = height;
    return this;
  }
}

const box = new Box();
box.setWidth(5).setHeight(10);

2.9 模块

2.9.1【推荐】使用 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.9.2【强制】不要用多个 import 引入同一模块。eslint: no-duplicate-imports

多条 import 语句引入了同一模块会降低可维护性,你需要将它们合成一条语句。

// bad
import React from 'react';
import { Component }  from 'react';

// good
import React, { Component } from 'react';

2.9.3【强制】import 语句需要放到模块的最上方

由于 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.9.4【推荐】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.9.5【推荐】不要在 import 时直接 export

虽然一行代码更简洁,但不利于代码的可读性和一致性。

// bad
export { Com as Component } from 'react';

// good
import { Component } from 'react';

export default Component;

2.9.6【推荐】不要 export 可变的内容

// bad
let foo = 1;
export default foo;

// good
const foo = 1;
export default foo;

2.9.7【推荐】当模块内只有一个 export 时,使用 default export

我们也建议文件内只包含一个 export,这有利于代码的可维护性。

// bad
export function foo() {}

// good
export default function foo() {}

2.10 操作符

2.10.1【推荐】使用严格相等运算符。eslint: eqeqeq

非严格相等运算符(==!=)会在比较前将被比较值转换为相同类型,对于不熟悉 JS 语言特性的人来说,这可能造成不小的隐患。了解更多

因此,一般情况下我们应该使用严格比较运算符( ===!==)进行比较。如果要比较的两个值类型不相同,应该显性地将其转换成相同类型再进行严格比较,而不是依赖于 ==!= 的隐形类型转换。

const id = '83949';

// bad - 为了兼容 id 可能是字符串的情况,而有意使用 == 与数字比较
if (id == 83949) { // => false
  // do something
}

// good - 如果 id 可能是字符串,应该先将其进行类型转换,再使用 === 进行比较
if (Number(id) === 83949) { // => true
  // do something
}

2.10.2【强制】不要使用一元自增自减运算符。eslint: no-plusplus

不要使用一元自增自减运算符(++--),除非在 for 循环条件中。

++-- 会带来值是否会提前变化带来的理解成本,也可能因为自动添加分号机制导致一些错误,因此我们推荐使用 num += 1 来代替 num++。但出于习惯,在 for 循环的条件中依然可以使用自增自减运算符。

let num = 1;

// bad
num++;
--num;

// good
num += 1;
num -= 1;

2.10.3【强制】不要使用 , 操作符,除非在 for 循环条件中。eslint: no-sequences

, 操作符包含多个表达式,它会从左到右执行这些表达式并返回最后一个表达式的值,使用它的容易导致错误。但出于习惯,在 for 循环的条件中仍然可以使用逗号操作符。

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

// good
doSomething();
if (!!test) {
}

2.10.4【强制】不要使用 void 运算符。eslint: no-void

在很老版本的 JS 中,undefined 值是可变的,因此使用 void 语句一般是用来得到一个 undefined 值。而在新版本的 JS 中,上面的问题已不复存在。因此出于程序可读性的考虑,禁止使用 void 运算符。

// bad
const foo = void 0;

// good
const foo = undefined;

2.10.5【强制】避免嵌套的三元表达式。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.10.6【强制】避免不必要的三元表达式。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.10.7【强制】混合使用多种操作符时,用小括号包裹分组。除了四则运算符(+, -, *, /),因为大多数人熟知它们的优先级。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.11 控制语句

2.11.1【强制】switch 语句中的 case 需要以 break 结尾。eslint: no-fallthrough

// 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.11.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.11.3【参考】switch 语句应包含至少 3 个条件分支,否则应使用 if 语句。eslint: default-case

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.11.4【强制】控制语句的嵌套层级不要过深。eslint: max-depth

控制语句的嵌套层级不要超过 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.11.5【强制】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.11.6【推荐】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.11.7【参考】如果一个 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.11.8【参考】条件表达式的值

条件表达式(例如 if 语句)的值通过抽象方法 ToBoolean 进行强制转换,计算结果遵守下面的规则:

  • 对象数组 被计算为 true
  • Undefined 被计算为 false
  • Null 被计算为 false
  • 布尔值 被计算为 布尔的值
  • 数字 如果是 +0、-0 或 NaN 被计算为 false,否则为 true
  • 字符串 如果是空字符串 '' 被计算为 false,否则为 true
if ({}) { // => true
}

if ([]) { // => true
}

if (0) { // => false
}

if ('0') { // => true
}

if ('') { // => false
}

2.12 其他

2.12.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.12.2【强制】禁止使用 debugger。eslint: no-debugger

debugger 语句会让程序暂停,并在当前位置开启调试器,这通常也是在程序调试阶段使用的,不应发布到线上。

// bad
function isTruthy(x) {
  debugger;
  return Boolean(x);
}

2.12.3【强制】禁止使用 alert。eslint: no-alert

alert 语句会使浏览器弹出原生警告框,这可能让人感觉你的程序出错了。如果需要对用户弹出警告信息,好的做法是使用第三方的弹窗组件或自己定义警告框样式。同理,confirmprompt 语句也不应被使用。

// bad
alert('Oops!');

// good - 使用自定义的 Alert 组件
Alert('Oops!');

2.12.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.12.5【强制】generator 函数内必须有 yield 语句。eslint: require-yield

如果一个 generator 中没有 yield 语句,那么这个 generator 就不是必须的。

// bad
function* foo() {
  return 10;
}

// good
function* foo() {
  yield 5;
  return 10;
}

2.12.6【强制】禁止对原生对象或只读的全局对象进行赋值。eslint: no-global-assign

JS 执行环境中会包含一些全局变量和原生对象,如浏览器环境中的 window,node 环境中的 globalprocessObjectundefined 等。除了像 window 这样的众所周知的对象,JS 还提供了数百个内置全局对象,你可能在定义全局变量时无意对它们进行了重新赋值,因此最好的做法是不要定义全局变量。

// bad
window = {};
Object = null;
undefined = 1;

2.12.7【推荐】需要迭代运算时,优先使用 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);

3 注释

注释的目的:提高代码的可读性,从而提高代码的可维护性

注释的原则:如无必要,勿增注释;如有必要,尽量详尽

3.1【推荐】单行注释使用 //

注释应单独一行写在被注释对象的上方,不要追加在某条语句的后面:

// bad
const active = true;  // is current tab

// good
// is current tab
const active = true;


注释行上方需要有一个空行(除非注释行上方是一个块的顶部),以增加可读性:

// 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【推荐】多行注释使用 /** ... */,而不是多行的 //

// 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【推荐】合理使用特殊注释标记

有时我们发现某个可能的 bug,但因为一些原因还没法修复;或者某个地方还有一些待完成的功能,这时我们需要使用相应的特殊标记注释来告知未来的自己或合作者。最常用的特殊标记有两种:

  • // FIXME: 说明问题是什么
  • // TODO: 说明还要做什么或者问题的解决方案

`FIXME``TODO````javascript
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](http://usejsdoc.org/) 规范。
<br />例如:
```javascript
/**
 * 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;
  }
};

4 命名

4.1【推荐】命名需要有语义,不要使用单个字符命名。eslint: id-length

// bad
function q() {
  // ...
}

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

4.2【推荐】使用小驼峰(camelCase)命名对象、函数、实例。eslint: camelcase

// bad
const this_is_my_object = {};
function this_is_my_function() {}

// good
const thisIsMyObject = {};
function thisIsMyFunction() {}

4.3【强制】使用大驼峰(PascalCase)命名类和构造函数。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.4【推荐】export 相关的命名规范

使用小驼峰(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【推荐】全大写字母、单词间使用下划线分割的命名模式(UPPERCASE_VARIABLES),仅用于命名常量,且该常量需同时满足如下条件:

  • 使用 const 关键字声明
  • 用于 export,而不是本文件内

`const``const````javascript
// 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````javascript
// bad - unnecessarily uppercases key while adding no semantic value
export const AN_OBJECT = {
  KEY: 'value',
};

// good
export const AN_OBJECT = {
  key: 'value',
};

4.6【参考】命名时不要用以下划线 _ 开头或结尾。eslint: no-underscore-dangle

JS 没有私有属性或私有方法的概念,这样的命名可能会让人误解。

// bad
this.__firstName__ = 'Panda';
this.firstName_ = 'Panda';
this._firstName = 'Panda';

// good
this.firstName = 'Panda';

4.7【参考】首字母缩略词的大小写需保持一致,即全部大写或全部小写,以保证可读性

// bad
import SmsContainer from './containers/SmsContainer';

// good
import SMSContainer from './containers/SMSContainer';

// bad
const HttpRequests = [
  // ...
];

// good
const HTTPRequests = [
  // ...
];

// good
const httpRequests = [
  // ...
];

4.8【推荐】不要保存 this 的引用,使用箭头函数或函数的 bind 方法

// bad
function foo() {
  const self = this;
  return function () {
    console.log(self);
  };
}

// bad
function foo() {
  const that = this;
  return function () {
    console.log(that);
  };
}

// good
function foo() {
  return () => {
    console.log(this);
  };
}

5 关于 ES5

这个章节是为还在使用 ES5 及之前版本 JS 的同学准备。因为本规约以 ES6 编写,你可以通过阅读本章节来了解 ES5 中有哪些需要额外注意的地方。

5.1【推荐】ES5 中的变量声明

使用 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 声明的变量会被提升到其作用域顶部:

// 变量声明会被提升到函数顶部,但赋值不会被提升
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.2【强制】对于用逗号分隔的多行结构,不使用最后一个行末逗号。eslint: comma-dangle

这样做会在 IE6/7 和 IE9 怪异模式下引起问题。另外,多余的逗号在某些 ES3 的实现里会增加数组的长度。

// bad
var hero = {
  firstName: 'Kevin',
  lastName: 'Flynn',
};

// good
var hero = {
  firstName: 'Kevin',
  lastName: 'Flynn'
};

5.3【推荐】使用 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();

5.4【推荐】使用 Array#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 配套工具

7 参考资料


React 编码规约

基本

1.1

每个文件只写一个组件,但是多个无状态组件可以放在单个文件中。

组件

2.1

【强制】有内部状态,方法或者是要对外暴露ref的组件,使用ES6 Class写法。

// bad
const Listing = React.createClass({
  // ...
  render() {
    return <div>{this.state.hello}</div>;
  }
});

// good
class Listing extends React.Component {
  // ...
  render() {
    return <div>{this.state.hello}</div>;
  }
}

2.2

没有内部状态,方法或者是无需对外暴露ref的组件,使用函数写法。

// bad
class Listing extends React.Component {
  render() {
    return <div>{this.props.hello}</div>;
  }
}

// good
const Listing = ({ hello }) => (
  <div>{hello}</div>
);

PropTypes/DefaultProps

3.1

有内部状态,方法或者是要对外暴露ref的组件,使用ES7类静态属性提案写法。

class Button extends Component {
  static propTypes = {
    size: React.PropTypes.oneOf(['large', 'normal', 'small']),
    shape: React.PropTypes.oneOf(['default', 'primary', 'ghost'])
    disabled: React.PropTypes.bool
  };

  static defaultProps = {
    size: 'normal',
    shape: 'default',
    disabled: false
  };

  render() {
    // ....
  }
}

3.2

没有内部状态,方法或者无需对外暴露ref的组件,使用类静态属性写法。

const HelloMessage = ({ name }) => {
  return <div>Hello {name}</div>;
};

HelloMessage.propTypes = {
  name: React.PropTypes.string
};

HelloMessage.defaultProps = {
  name: 'vic'
};

3.3

PropTypes必须。

State

4.1

使用ES7实例属性提案声明写法或者构造函数声明写法,后者适合需要进行一定计算后才能初始化state的情况。

class Some extends Component {
  state = {
    foo: 'bar'
  };

  // ....
}

class Some extends Component {
  constructor(props) {
    super(props);
      this.state = {
        foo: 'bar'
      };
  }

  // ....
}

4.2

【建议】不建议对this.state进行赋值。

// bad
this.state.name = this.props.name.toUpperCase();

// good
this.setState({
  name: this.props.name.toUpperCase();
});

DisplayName

【建议】为了调试方便,建议在组件最上面写displayName。

// good
class Some extends Component {
  static displayName = 'Some';

  // ....
}

命名

5.1

【强制】扩展名: React组件文件使用.jsx扩展名。

5.2

文件名: 文件名使用驼峰式命名,首字母大写,如ReservationCard.jsx

5.3

引用命名: React组件名使用驼峰式命名,首字母大写,实例名也使用驼峰式命名,但首字母小写。

// bad
import reservationCard from './ReservationCard';

// good
import ReservationCard from './ReservationCard';

// bad
const ReservationItem = <ReservationCard />;

// good
const reservationItem = <ReservationCard />;

引号

6.1

对于JSX属性值总是使用双引号", 其他均使用单引号'

// bad
<Foo bar='bar' />

// good
<Foo bar="bar" />

// bad
<Foo style={{ left: "20px" }} />

// good
<Foo style={{ left: '20px' }} />

空格

7.1

【建议】总是在自闭合的标签/>前加一个空格。

// bad
<Foo/>

// very bad
<Foo                 />

// good
<Foo />

7.2

【建议】不要在JSX{}引用括号里两边加空格。

// bad
<Foo bar={ baz } />

// good
<Foo bar={baz} />

7.3

【建议】不要在JSX props属性=两边加空格。

// bad
<Hello name = {firstname} />;

// good
<Hello name={firstname} />;

属性

8.1

【强制】JSX属性名总是使用驼峰式风格。

// bad
<Foo UserName="hello" phone_number={12345678} />

// good
<Foo userName="hello" phoneNumber={12345678} />

8.2

【建议】如果属性值为true, 可以直接省略。

// bad
<Foo hidden={true} />

// good
<Foo hidden />

8.3

【强制】数组中或者遍历中输出相同的React组件,属性key必需。

// bad
[<Hello />, <Hello />, <Hello />];

data.map(x => <Hello>x</Hello>);

// good
[<Hello key="first" />, <Hello key="second" />, <Hello key="third" />];

data.map((x, i) => <Hello key={i}>x</Hello>);

8.4

【强制】class以及for等关键字不允许作为属性名。

// bad
<div class="hello">Hello World</div>;

// good
<div className="hello">Hello World</div>;

8.5

【强制】属性名不允许重复声明。

// bad
<Hello name="John" name="John" />;

// good
<Hello firstname="John" lastname="Doe" />;

Refs

9.1

【强制】总是在Refs里使用回调函数。

// bad
<Foo
  ref="myRef"
/>

// good
<Foo
  ref={ref => { this.myRef = ref; }}
/>

括号

10.1

【建议】将多行的JSX标签写在()里,单行可以省略()

// bad
render() {
  return <MyComponent className="long body" foo="bar">
     <MyChild />
  </MyComponent>;
}

// good
render() {
  return (
    <MyComponent className="long body" foo="bar">
      <MyChild />
    </MyComponent>
  );
}

// good
render() {
  const body = <div>hello</div>;
  return <MyComponent>{body}</MyComponent>;
}

标签

11.1

对于没有子元素的标签来说总是闭合的。

// bad
<Foo className="stuff"></Foo>

// good
<Foo className="stuff" />

方法

12.1

render方法必须有值返回。

// bad
render() {
  (<div />);
}

// good
render() {
  return (<div />);
}

12.2

【建议】按照以下顺序排序内部方法。

1. static methods and properties
2. lifecycle methods: displayName, propTypes, contextTypes, childContextTypes, mixins, statics,defaultProps, constructor, getDefaultProps, getInitialState, state, getChildContext, componentWillMount, componentDidMount, componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate, componentDidUpdate, componentWillUnmount (in this order).
3. custom methods
4. render method`

12.3

【建议】不要在componentDidMount以及componentDidUpdate中调用setState,除非是在绑定的回调函数中设置State。

// bad
class Hello extends Component {
  componentDidMount() {
    this.setState({
      name: this.props.name.toUpperCase()
    });
  }
  render() {
    return <div>Hello {this.state.name}</div>;
  }
}

// good
class Hello extends Component {
  componentDidMount() {
    this.onMount(newName => {
      this.setState({
        name: newName
      });
    });
  }
  render() {
    return <div>Hello {this.state.name}</div>;
  }
}

12.4

【建议】使用箭头函数来获取本地变量。

function ItemList(props) {
  return (
    <ul>
      {props.items.map((item, index) => (
        <Item
          key={item.key}
          onClick={() => doSomethingWith(item.name, index)}
        />
      ))}
    </ul>
  );
}

12.5

【建议】当在render()里使用事件处理方法时,提前在构造函数里把this绑定上去。

解释:为什么?在每次render过程中, 再调用bind都会新建一个新的函数,浪费资源。

// bad
class extends React.Component {
  onClickDiv() {
    // do stuff
  }

  render() {
    return <div onClick={this.onClickDiv.bind(this)} />
  }
}

// good
class extends React.Component {
  constructor(props) {
    super(props);

    this.onClickDiv = this.onClickDiv.bind(this);
  }

  onClickDiv() {
    // do stuff
  }

  render() {
    return <div onClick={this.onClickDiv} />
  }
}

12.6

【建议】在React模块中,不要给所谓的私有函数添加_前缀,本质上它并不是私有的。

解释:_下划线前缀在某些语言中通常被用来表示私有变量或者函数。但是不像其他的一些语言,在JS中没有原生支持所谓的私有变量,所有的变量函数都是共有的。尽管你的意图是使它私有化,在之前加上下划线并不会使这些变量私有化,并且所有的属性(包括有下划线前缀及没有前缀的)都应该被视为是共有的。了解更多详情请查看Issue #1024,和#490

// bad
React.createClass({
  _onClickSubmit() {
    // do stuff
  },

  // other stuff
});

// good
class extends React.Component {
  onClickSubmit() {
    // do stuff
  }

  // other stuff
}

参考资料


发布了35 篇原创文章 · 获赞 1 · 访问量 6718

猜你喜欢

转载自blog.csdn.net/qq_36162529/article/details/91954933