前言:团队基于ES6和Eslint规则规定代码规范,本文的目的是梳理和总结团队现在实行的规范。
作者:郑灵华,点餐秒付终端团队成员
目录
一、Eslint检测ES6规范配置
- 编码格式规范
- 声明唯一性
- 初始化定义规范
- 代码编写注意事项
- 派生类相关
二、Airbnb规范节选
- 箭头函数
- 构造器
- 迭代遍历
- 属性定义
- 解构
- 函数
三、参考资料
一、Eslint检测ES6规范配置
1. 编码格式规范
a.规定箭头函数强制大括号
b.规定箭头函数参数是否要使用括号
c.规定箭头函数的箭头前后的空格规范
d.generator 函数中 号周围的空格
e.规定rest参数和扩展运算符与他们的参数之间的空格
f.禁止模板字面量中的花括号出现括号
g.强制在 yield 表达式中 * 后面使用空格
a.规定箭头函数强制大括号
//Eslint文件配置项
'arrow-body-style': ['error', 'as-needed', {
requireReturnForObjectLiteral: false,
}]复制代码
参数 | 参数说明 | 备注 |
---|---|---|
as-needed | 当大括号是可省略的,强制不使用 | |
requireReturnForObjectLiteral | 不需要显式返回对象字面量 | 必须与as-needed 搭配使用 |
//项目中正确使用事例
//可以省略大括号
let foo = () => 0;
//不用显式返回对象字面量
let foo = () => ({ bar: 0 });
//错误对比:
let foo = () => {
return ({bar: 0 });
};复制代码
b.规定箭头函数参数是否要使用括号
//Eslint文件配置项
'arrow-parens': ['error', 'as-needed', {
requireForBlockBody: true,
}]复制代码
参数 | 参数说明 | 备注 |
---|---|---|
as-needed | 当只有一个参数时允许省略圆括号 | |
requireForBlockBody | 当函数体在一个指令块中,参数必须用圆括号包含 | 作为as-needed补充。以函数体是否被 { } 包括快速判断 |
//项目实践正确例子
// 只有一个参数允许省略
a.map(x => {
return x * x;
});
// requireForBlockBody 参数作为补充,上述代码修改成
a.map((x) => {
return x * x;
});复制代码
c.箭头函数的箭头前后的空格规范
'arrow-spacing': ['error', {
before: true,
after: true
}]复制代码
参数 | 参数说明 | 备注 |
---|---|---|
before | 箭头前面有空格 | |
after | 箭头后面有空格 |
//项目应用
a => a;复制代码
d.generator 函数中 * 号周围的空格
'generator-star-spacing': ['error', {
before: false,
after: true
}]复制代码
参数 | 参数说明 | 备注 |
---|---|---|
before | *前面没有空格 | |
after | *后面有空格 |
//项目正确使用示例
function* generator() {}复制代码
e.不允许rest参数和扩展运算符与他们的参数之间有空格
'rest-spread-spacing': ['error', 'never']复制代码
参数 | 参数说明 | 备注 |
---|---|---|
never | 符号和参数之间不能有空格 |
//rest参数
let [a, b, ...arr] = [1, 2, 3, 4, 5]
//扩展运算符
function fn(){}
fn(...args)复制代码
f.禁止模板字面量中的花括号出现括号
'template-curly-spacing': 'error'复制代码
//花括号里面没有括号
`hello, ${people.name}!`复制代码
g.强制在 yield 表达式中 后面使用空格
'yield-star-spacing': ['error', 'after']复制代码
function* generator() {
yield* other();
}复制代码
2. 声明唯一性
a.不能修改类声明的变量
b.禁止修改const声明的变量
c.不允许类成员里有重复的名称
d.不要重复引入一个模块
e.禁止在import,export,解构赋值中重命名和原有名字相同
a.不能修改类声明的变量
'no-class-assign': 'error'复制代码
// 简而言之,如果以class Name{}形object-shorthand式出现,那么Name不能做任何更改和赋值
// 下面例子是正确的。因为A至始至终只是变量
let A = class {
b() {
A = 0;
console.log(A);
}
}
console.log(A); //class
let Foo = new A();
Foo.b(); //0
console.log(A); //0复制代码
b.禁止修改const声明的变量
'no-const-assign': 'error'复制代码
c.不允许类成员里有重复的名称
'no-dupe-class-members': 'error'复制代码
d.不要重复引入一个模块
'no-duplicate-imports': 'off'复制代码
//同一个模块引入两个变量应该写在一个大括号里面
import { merge, find } from 'module';复制代码
e.禁止在import,export,解构赋值中重命名和原有名字相同
'no-useless-rename': ['error', {
ignoreDestructuring: false,
ignoreImport: false,
ignoreExport: false,
}]复制代码
//形如{ foo as foo }和{ bar: bar }并没有起到重命名的作用,所以应该禁止这种冗余书写
import { foo as bar } from "baz";
export { foo as bar } from "foo";
let { [foo]: foo } = bar;复制代码
3. 初始化定义规范
a.Symbol类型不能用new关键字
b.Symbol定义的时候增加描述语言,便于debug
c.generator函数里面一定要有yield
d.使用 let 或 const 而不是 var
e.禁止在字面量声明无用的计算属性
f.若变量不会再次赋值,使用const声明
a.Symbol类型不能用new关键字
'no-new-symbol': 'error'复制代码
//symbol应该以函数形式调用
var foo = Symbol('foo');复制代码
b.Symbol定义的时候增加描述语言,便于debug
'symbol-description': 'error'复制代码
let foo = Symbol("some description")复制代码
c.generator函数里面一定要有yield
'require-yield': 'error'复制代码
d.使用 let 或 const 而不是 var
'no-var': 'error'复制代码
e.禁止在字面量声明无用的计算属性
'no-useless-computed-key': 'error'复制代码
//无用的["a"]计算属性
var foo = {['0+1,234']: "b"};
//改写成
var foo = { '0+1,234': 0 };复制代码
f.若变量不会再次赋值,使用const声明
'prefer-const': ['error', {
destructuring: 'any',
ignoreReadBeforeAssign: true,
}]复制代码
参数 | 参数说明 | 备注 |
---|---|---|
destructuring | 解构赋值时,所有变量的类型都应该保持一致 | |
ignoreReadBeforeAssign | 忽略声明和第一次赋值之间的变量 | 也就是不能先定义后赋值 |
//解构赋值时,值要么都是const要么都是let
// a0是确定的,b没有被赋值
const {a: a0, b} = obj;
const a = a0 + 1;
// a,b都是变量,所以解构赋值定义用let
let {a, b} = obj;
a = a + 1;
b = b + 1;
//错误例子,在ignoreReadBeforeAssign=true时,timer的声明和赋值之间的initialize()函数声明会被省略。
//从而会在setInterval处报错函数undefined
let timer;
function initialize() {
if (foo()) {
clearInterval(timer);
}
}
timer = setInterval(initialize, 100);
//正确例子,只要声明就要赋值!
const timer = setInterval(initialize, 100);
function initialize() {
if (foo()) {
clearInterval(timer);
}
}复制代码
4.代码编写注意事项
a. 避免箭头函数和比较式混淆
b. 使用模板字面量而不是字符串拼接
c. 使用扩展运算符(...)而非.apply()调用可变参数
d. 用rest参数(...变量名)替换arguments
e. 不允许使用parseInt()转化2,8,16进制
f. 要求使用箭头函数进行回调
g. 对象字面量语法简写
a.避免箭头函数和比较式混淆
'no-confusing-arrow': ['error', {
allowParens: true,
}]复制代码
参数 | 参数说明 | 备注 |
---|---|---|
allowParens | 放宽标准,允许箭头函数使用括号 | 不强制必须用return返回 |
//如果是严格模式,会发现即使用圆括号包含,也要用return区分箭头函数和三元运算比较
var x = a => { return 1 ? 2 : 3; };
var x = (a) => { return 1 ? 2 : 3; };
//如果allowParens=true,则放宽标准
var x = a => (1 ? 2 : 3);
var x = (a) => (1 ? 2 : 3);复制代码
b.使用模板字面量而不是字符串拼接
'prefer-template': 'error'复制代码
let str = `Hello, ${name}!`复制代码
c.使用扩展运算符(...)而非.apply()调用可变参数
'prefer-spread': 'error'复制代码
//求出一个数组最大元素
Math.max.apply(null, [14, 3, 77])
//等效于
Math.max(...[14, 3, 77])
//等同
Math.max(14, 3, 77)复制代码
d.用rest参数(...变量名)替换arguments
'prefer-rest-params': 'error'复制代码
//rest运算符可以提供一个真正的数组,能显式表示参数
//而arguments是一个对象,操作中要通过call等手段调用数组方法
function foo(...args) {
console.log(args);
}复制代码
e. 不允许使用parseInt()转化2,8,16进制
'prefer-numeric-literals': 'error'复制代码
//只针对2,8,16进制使用
//数字转化成其他进制或者是变量转化还是用parseInt
0b111110111 === 503;
0o767 === 503;
0x1F7 === 503;
parseInt(1, 3);
parseInt(foo, 2);复制代码
f. 要求使用箭头函数进行回调
'prefer-arrow-callback': ['error', {
allowNamedFunctions: false,
allowUnboundThis: true,
}]复制代码
参数 | 参数说明 | 备注 |
---|---|---|
allowNamedFunctions | 如果回调函数里面是命名函数则报错 | |
allowUnboundThis | 不使用bind()指定this,规则会动态标记this的使用 |
//直接使用this,而不是bind(this)
//回调函数里是匿名函数
foo(function() { return this.a; });复制代码
g.对象字面量语法简写
'object-shorthand': ['error', 'always', {
ignoreConstructors: false,
avoidQuotes: true,
}]复制代码
参数 | 参数说明 | 备注 |
---|---|---|
always | 能简写就简写 | |
ignoreConstructors | 构造函数不能省略 | 必须指定第一个参数 |
avoidQuotes | 对象键是字符串时,用长格式 | 必须指定第一个参数 |
// 因为foo对象两个键都是string,所以后面不能省略
var foo = {
"bar-baz": function() {},
"qux": qux
}复制代码
5.派生类相关
a. 构造函数必须调用 super
b. 禁止不必要的构造函数
c. 派生类函数构造器禁止在super()之前使用this
a. 构造函数必须调用 super
'constructor-super': 'error'复制代码
//派生类中构造函数必须调用,非派生类的构造函数不能调用super()
class A {
constructor() { }
}
class A extends B {
constructor() {
super();
}
}复制代码
b. 禁止不必要的构造函数
'no-useless-constructor': 'error'复制代码
//简单讲便是构造器里面一定要有执行的逻辑代码
class A {
constructor () {
doSomething();
}
}
//如果没有特别的逻辑,则类返回空即可
class A { }复制代码
c. 派生类函数构造器禁止在super()之前使用this
'no-this-before-super': 'error复制代码
//否则会返回引用错误(reference error)
class A extends B {
constructor() {
super();
this.a = 0; // OK, this is after `super()`.
}
}复制代码
二、Airbnb规范节选
1. 箭头函数
- 当函数特别简单,并且只有一个参数,可省略花括号、圆括号、return
- 提高链式调用可读性,不断传递this
- 但是结果需要回传一个对象时,不能省略return
2. 构造器
- 用class替代prototype
//用class更为简洁
class Foo {
constructor(){}
getVal(){}
}
//等价于
Foo.prototype = {
getVal(){}
}复制代码
- 用extends实现继承,不直接操作prototype
- class能实现模块开发,并且能通过返回this实现链式调用
- 类的数据类型就是函数,类本身就指向构造函数
class Foo {}
typeof Foo; // Function
Foo === Foo.prototype.constructor; //true复制代码
3. 迭代遍历
- ES6提供的遍历器Iterator,适合于部署了Symbol.iterator属性的数据结构,可以用for...of遍历成员
- for...in遍历object所有可枚举对象,包含当前对象和原型上
- for...of遍历collection对象,并不适用所有object,视乎其是否有[Symbol.iterator]属性。且只遍历当前对象
- Airbnb推荐使用高阶函数例如 map() 和 reduce() 替代 for-of,便于处理函数回调
4. 属性定义
- 如果对象具有动态属性名,在一开始使用可计算的属性名称,保证所有属性定义在一起
const obj = {
id: 5,
name: 'San Francisco',
[getKey('enabled')]: true,
}复制代码
5. 解构
- 推荐使用解构赋值,减少临时变量的引入
- 需要回传多个值时,使用对象解构,而不是数组解构,方便调用时可以忽略顺序
// 数组解构=>调用时回调数据的顺序是一一对应的
function processInput(input) {
// then a miracle occurs
return [left, right, top, bottom];
}
const [left, __, top] = processInput(input);
// 对象解构=>不考虑顺序,只要保证变量必须与属性同名
function processInput(input) {
// then a miracle occurs
return { left, right, top, bottom };
}
const { left, right } = processInput(input);复制代码
- 对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz; // "aaa"
foo; // error: foo is not defined复制代码
6. 函数
- 使用函数声明代替函数表达式,便于在调用栈识别,避免整个函数被提升。同时,箭头函数可以取代函数表达式
- 不要把参数命名为 arguments。这将取代原来函数作用域内的 arguments 对象
- 不要使用 arguments。用 rest 语法 ... 替代
- 推荐: 函数参数应该指定默认值
//bad code,如果是Boolean,传进去甚至会改变opts的类型
function handleThings(opts) {
opts = opts || {};
}
//Airbnb 推荐写法,初始化参数
function handleThings(opts = {}) {}复制代码
三、参考资料
Airbnb JavaScript规范
阮一峰-ES6入门教程
小问ES6理解-进阶版
ES6 In Depth (深入浅出ES6)