前端 React 代码规范

1,002 阅读15分钟

内容引用自:

github.com/cnsnake11/r…

guide.aotu.io/docs/js/cod…

代码千万行,安全第一行;前端不规范,同事两行泪。

代码是给人看的,其次才是给机器执行。

引用网络资源整合,如果错误欢迎更正。

代码要做到

  1. 注释清楚
  1. 间隔整齐
  1. 可读性高
  1. 可维护性好
  1. 可扩展性好

代码注释

  1. 公共组件使用说明
  1. 各组件中重要函数或者类说明
  1. 复杂的业务逻辑处理说明
  1. 特殊情况的代码处理说明,对于代码中特殊用途的变量、存在临界值、函数中使用的 hack、使用了某种算法或思路等需要进行注释描述
  1. 多重 if 判断语句
  1. 注释块必须以/**(至少两个星号)开头**/
  1. 单行注释使用 //

单行注释 // 双斜杠后面与注释内容必须有一个空格,行尾注释必须与代码间隔一个空格

  // bad
  
  var name = 'abc';//姓名
 
  // good
 
  // 姓名
  var name = 'abc';
  var name = 'abc'; // 姓名

多行注释

页面的常量或全局的常量并使用多行注释(大写加下划线的方式命名)
/** 页面的常量  */
const PAGE_CONSTANT = { a: 1, b: 2 };

组件使用说明,和调用说明
/**
* 组件名称
* @module 组件存放位置
* @desc 组件描述
* @author 组件作者
* @date 2017120517:22:43
* @param {Object} [title]    - 参数说明
* @param {String} [columns] - 参数说明
* @example 调用示例
*  <hbTable :title="title" :columns="columns" :tableData="tableData"></hbTable>
**/


方法注释
/**
* @desc 方法描述
* @param {Array} arr 这是一个数组表示XXX
* @param {Object} obj 这是一个对象表示XXX
* @param {boolean} bool XXX是否为。。。
* @returns {Array} 返回XXX的处理结果
*/
function foo (arr, obj, bool)  {
  const tempArr = [...arr]; // 不建议直接对参数进行操作
  tempArr .push(obj);
  return bool ? arr : [];
}
  • JSX注释
return () {
  // 部分逻辑
  return ( // 要有括号
    {/** 内容XXX开始 */}
    <Component>
      <View>一些内容</View>
    </Component>
    {/** 内容XXX结束 */}
  )
}

method 方法命名命名规范

  • 驼峰式命名,统一使用动词或者动词+名词形式
//bad
go、nextPage、show、open、login

// good
jumpPage、openCarInfoDialog
  • 请求数据方法,以 data 结尾
//bad
takeData、confirmData、getList、postForm

// good
getListData、postFormData
  • init、refresh 单词除外
  • 尽量使用常用单词开头(set、get、go、can、has、is)
  • 函数方法常用动词
get 获取/set 设置,
add 增加/remove 删除
create 创建/destory 移除
start 启动/stop 停止
open 打开/close 关闭,
read 读取/write 写入
load 载入/save 保存,
create 创建/destroy 销毁
begin 开始/end 结束,
backup 备份/restore 恢复
import 导入/export 导出,
split 分割/merge 合并
inject 注入/extract 提取,
attach 附着/detach 脱离
bind 绑定/separate 分离,
view 查看/browse 浏览
edit 编辑/modify 修改,
select 选取/mark 标记
copy 复制/paste 粘贴,
undo 撤销/redo 重做
insert 插入/delete 移除,
add 加入/append 添加
clean 清理/clear 清除,
index 索引/sort 排序
find 查找/search 搜索,
increase 增加/decrease 减少
play 播放/pause 暂停,
launch 启动/run 运行
compile 编译/execute 执行,
debug 调试/trace 跟踪
observe 观察/listen 监听,
build 构建/publish 发布
input 输入/output 输出,
encode 编码/decode 解码
encrypt 加密/decrypt 解密,
compress 压缩/decompress 解压缩
pack 打包/unpack 解包,
parse 解析/emit 生成
connect 连接/disconnect 断开,
send 发送/receive 接收
download 下载/upload 上传,
refresh 刷新/synchronize 同步
update 更新/revert 复原,
lock 锁定/unlock 解锁
check out 签出/check in 签入,
submit 提交/commit 交付
push 推/pull 拉,
expand 展开/collapse 折叠
begin 起始/end 结束,
start 开始/finish 完成
enter 进入/exit 退出,
abort 放弃/quit 离开
obsolete 废弃/depreciate 废旧,
collect 收集/aggregate 聚集

单行代码块

在单行代码块中使用空格

// 不推荐
function foo () {return true}
if (foo) {bar = 0}

// 推荐
function foo () { return true }
if (foo) { bar = 0 }

大括号风格

在编程过程中,大括号风格与缩进风格紧密联系,用来描述大括号相对代码块位置的方法有很多。在 JavaScript 中,主要有三种风格,如下:

  • One True Brace Style
if (foo) {
  bar()
} else {
  baz()
}
  • Stroustrup
if (foo) {
  bar()
}
else {
  baz()
}
  • Allman
if (foo)
{
  bar()
}
else
{
  baz()
}

我们团队约定使用 One True Brace Style 风格

变量命名

当命名变量时,主流分为驼峰式命名(constiableName)和下划线命名(constiable_name)两大阵营。

团队约定使用驼峰式命名

拖尾逗号

在 ECMAScript5 里面,对象字面量中的拖尾逗号是合法的,但在 IE8(非 IE8 文档模式)下,当出现拖尾逗号,则会抛出错误,所以不使用拖尾逗号

const foo = {
  name: 'foo',
  age: '22'
}

const foo = { name: 'foo', age: '22' }

逗号空格

逗号前后的空格可以提高代码的可读性,团队约定在逗号后面使用空格,逗号前面不加空格。

// 不推荐
const foo = 1,bar = 2;
const foo = 1 , bar = 2;
const foo = 1 ,bar = 2;

// 推荐
const foo = 1, bar = 2;

逗号风格

逗号分隔列表时,在 JavaScript 中主要有两种逗号风格:

  • 标准风格,逗号放置在当前行的末尾
  • 逗号前置风格,逗号放置在下一行的开始位置

团队约定使用标准风格

// 不推荐
const foo = 1
,
bar = 2;

const foo = 1
, bar = 2;

const foo = ['name'
          , 'age'];
          
// 推荐
const foo = 1,
      bar = 2;

const foo = ['name', 'age'];

计算属性的空格

团队约定在对象的计算属性内,禁止使用空格

// 不推荐
obj['foo' ];
obj[ 'foo'];
obj[ 'foo' ];

// 推荐
obj['foo'];

拖尾换行

在非空文件中,存在拖尾换行是一个常见的 UNIX 风格,它的好处是可以方便在串联和追加文件时不会打断 Shell 的提示。在日常的项目中,保留拖尾换行的好处是,可以减少版本控制时的代码冲突。

// 不推荐
function func () {
  // do something
}

// 推荐
function func () {
  // do something
}

可以通过 .editorconfig 添加 EOL

函数调用

为了避免语法错误,团队约定在函数调用时,禁止使用空格

// 不推荐
fn ()
fn
()


// 推荐
fn();

缩进

代码保持一致的缩进,是作为工程师的职业素养。但缩进用两个空格,还是四个空格,是用 Tab 还是空格呢?这样的争论太多了,也得不出答案。本规范结合了市面上优秀的开源项目,姑且约定使用 空格 来缩进,而且缩进使用两个空格。

那是不是不能使用 Tab 进行缩进了?我们可以通过配置 .editorconfig ,将 Tab 自动转换为空格。

对象字面量的键值缩进

团队约定对象字面量的键和值之间不能存在空格,且要求对象字面量的冒号和值之间存在一个空格

// 不推荐
const obj = { 'foo' : 'haha' };

// 推荐
const obj = { 'foo': 'haha' };

构造函数首字母大写

在 JavaScript 中 new 操作符用来创建某个特定类型的对象的一个实例,该类型的对象是由一个构造函数表示的。由于构造函数只是常规函数,唯一区别是使用 new 来调用。所以我们团队约定构造函数的首字母要大小,以此来区分构造函数和普通函数。

// 不推荐
const fooItem = new foo();

// 推荐
const fooItem = new Foo();

构造函数的参数

在 JavaScript 中,通过 new 调用构造函数时,如果不带参数,可以省略后面的圆括号。但这样会造成与整体的代码风格不一致,所以团队约定使用圆括号

// bad
const person = new Person;

// good
const person = new Person();

空行

空白行对于分离代码逻辑有帮助,但过多的空行会占据屏幕的空间,影响可读性。团队约定最大连续空行数为 2

// bad
function demo1 () {
  // do somthing
}



function demo2 () {
  // do somthing
}

// good
function demo1 () {
  // do somthing
}


function demo2 () {
  // do somthing
}

链式赋值

链式赋值容易造成代码的可读性差,所以团队约定禁止使用链式赋值

// bad
const a = b = c = 1;

// good
const a = 1;
const b = 1;
const c = 1;

变量声明

JavaScript 允许在一个声明中,声明多个变量。团队约定在声明变量时,一个声明只能有一个变量

// bad
const a, b, c;

// good
const a;
const b;
const c;

分号

JavaScript 在所有类 C 语言中是比较独特的,它不需要在每个语句的末尾有分号。在很多情况下,JavaScript 引擎可以确定一个分号应该在什么位置然后自动添加它。此特征被称为 自动分号插入 (ASI),被认为是 JavaScript 中较为有争议的特征。

本规范推荐在每个语句的末尾添加分号

// bad
function foo (){
  let a = 1
  a++
}

// good
function foo (){
  let a = 1;
  a++;
}

相关参考 :semi

代码块空格

一致性是任何风格指南的重要组成部分。虽然在哪里放置块的开括号纯属个人偏好,但在整个项目中应该保持一致。不一致的风格将会分散读者阅读代码的注意力。

团队约定代码块前要添加空格

行尾和空行任何情况下禁止存在空格

// bad
if (a){
  b()    
}
        
function a (){}

// good
if (a) {
  b()
}

function a () {}

函数声明的空格

当格式化一个函数,函数名或 function 关键字与左括号之间允许有空白。命名函数要求函数名和 function 关键字之间有空格,但是匿名函数要求不加空格。

团队约定函数括号前要加空格

// bad
function func(x) {
  // ...
}

function () {
  // ...
}

// good
function func (x) {
  // ...
}

function() {
  // ...
}

操作符的空格

团队约定操作符前后都需要添加空格

// bad
const sum = 1+2;

// good
const sum = 1 + 2;

\

语言规范

JavaScript 是一种客户端脚本语言,这里列出了编写 JavaScript 时需要遵守的规则。

类型

  • 原始类型: 存取原始类型直接作用于值本身

    • 布尔类型
    • Null 类型
    • Undefined 类型
    • 数字类型
    • BigInt 类型
    • 字符串类型
    • 符号类型 Symbol
const foo = 1;
let bar = foo;

bar = 9;

console.log(foo, bar); // 1, 9
  • 复杂类型: 访问复杂类型作用于值的引用

    • object
    • array
    • function
const foo = [1, 2, 3];
const bar = foo;

bar[0] = 9;

console.log(foo[0], bar[0]); // 9, 9

引用

  • 请记得 constlet 都是块级作用域,var 是函数级作用域
// const and let only exist in the blocks they are defined in.
{
  let a = 1;
  const b = 1;
}
console.log(a); // ReferenceError
console.log(b); // ReferenceError

原因:这样做可以确保你无法重新分配引用,以避免出现错误和难以理解的代码

// bad
var a = 1;
var b = 2;

// good
const a = 1;
const b = 2;
  • 如果引用是可变动的,使用 let 代替 var,eslint: no-var

原因:let 是块级作用域的,而不像 var 属于函数级作用域

// bad
var count = 1;
if (count < 10) {
  count += 1;
}

// good
let count = 1;
if (count < 10) {
  count += 1;
}

对象

// bad
const a = new Object{};

// good
const a = {};
  • 别使用保留字作为对象的键值,这样在 IE8 下不会运行
// bad
const a = {
  default: {},  // default 是保留字
  common: {}
}

// good
const a = {
  defaults: {},
  common: {}
}
  • 当使用动态属性名创建对象时,请使用对象计算属性名来进行创建

原因:因为这样做就可以让你在一个地方定义所有的对象属性

function getKey(k) {
  return `a key named ${k}`;
}

// bad
const obj = {
  id: 5,
  name: 'San Francisco'
};
obj[getKey('enabled')] = true;

// good
const obj = {
  id: 5,
  name: 'San Francisco',
  [getKey('enabled')]: true
};
// bad
const item = {
  value: 1,

  addValue: function (val) {
    return item.value + val
  }
}

// good
const item = {
  value: 1,

  addValue (val) {
    return item.value + val
  }
}
  • 原因:这样更简短且描述更清楚
const job = 'FrontEnd';

// bad
const item = {
  job: job
}

// good
const item = {
  job
}
  • 将简写的对象属性分组后统一放到对象声明的开头

原因:这样更容易区分哪些属性用了简写的方式

const job = 'FrontEnd';
const department = 'JDC';

// bad
const item = {
  sex: 'male',
  job,
  age: 25,
  department
}

// good
const item = {
  job,
  department,
  sex: 'male',
  age: 25
}
  • 只对非法标识符的属性使用引号,eslint: quote-props

原因:因为通常来说我们认为这样主观上会更容易阅读,这样会带来代码高亮上的提升,同时也更容易被主流 JS 引擎优化

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

// good
const good = {
  foo: 3,
  bar: 4,
  'data-blah': 5
}
  • 不要直接使用 Object.prototype 的方法, 例如 hasOwnProperty, propertyIsEnumerableisPrototypeOf 方法,eslint: no-prototype-builtins
  • 原因:这些方法可能会被对象自身的同名属性覆盖 - 比如 { hasOwnProperty: false } 或者对象可能是一个 null 对象(Object.create(null))

// bad
console.log(object.hasOwnProperty(key));

// good
console.log(Object.prototype.hasOwnProperty.call(object, key));

// best
const has = Object.prototype.hasOwnProperty // cache the lookup once, in module scope.
console.log(has.call(object, key));
/* or */
import has from 'has' // https://www.npmjs.com/package/has
console.log(has(object, key));
  • 优先使用对象展开运算符 ... 来做对象浅拷贝而不是使用 Object.assign,使用对象剩余操作符来获得一个包含确定的剩余属性的新对象
// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 }); // this mutates `original` ಠ_ಠ
delete copy.a; // so does this

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

const { a, ...noA } = copy; // noA => { b: 2, c: 3 }

数组

// bad
const items = new Array();

// good
const items = [];
  • 向数组中添加元素时,请使用 push 方法
const items = [];

// bad
items[items.length] = 'test'

// good
items.push('test');
  • 使用展开运算符 ... 复制数组
// bad
const items = [];
const itemsCopy = [];
const len = items.length;
let i;

// bad
for (i = 0; i < len; i++) {
  itemsCopy[i] = items[i];
}

// good
itemsCopy = [...items];
  • 把一个可迭代的对象转换为数组时,使用展开运算符 ... 而不是 Array.from
const foo = document.querySelectorAll('.foo');

// good
const nodes = Array.from(foo);

// best
const nodes = [...foo];
  • 使用 Array.from 来将一个类数组对象转换为数组
const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 };

// bad
const arr = Array.prototype.slice.call(arrLike);

// good
const arr = Array.from(arrLike);
  • 遍历迭代器进行映射时使用 Array.from 代替扩展运算符 ..., 因为这可以避免创建中间数组
// bad
const baz = [...foo].map(bar);

// good
const baz = Array.from(foo, bar);
  • 使用数组的 map 等方法时,请使用 return 声明,如果是单一声明语句的情况,可省略 return
// good
[1, 2, 3].map(x => {
  const y = x + 1
  return x * y;
})

// good
[1, 2, 3].map(x => x + 1);

// bad
const flat = {}
[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
  const flatten = memo.concat(item);
  flat[index] = flatten;
})

// good
const flat = {}
[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
  const flatten = memo.concat(item);
  flat[index] = flatten;
  return flatten;
})

// bad
inbox.filter((msg) => {
  const { subject, author } = msg;
  if (subject === 'Mockingbird') {
    return author === 'Harper Lee';
  } else {
    return false;
  }
})

// good
inbox.filter((msg) => {
  const { subject, author } = msg;
  if (subject === 'Mockingbird') {
    return author === 'Harper Lee';
  }

  return false;
})
  • 如果一个数组有多行则要在数组的开括号后和闭括号前使用新行
// bad
const arr = [
  [0, 1], [2, 3], [4, 5]
]

const objectInArray = [{
  id: 1
}, {
  id: 2
}]

const numberInArray = [
  1, 2
]

// good
const arr = [[0, 1], [2, 3], [4, 5]];

const objectInArray = [
  {
    id: 1
  },
  {
    id: 2
  }
]

const numberInArray = [
  1,
  2
]

解构赋值

愿意:解构可以避免创建属性的临时引用

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

// better
function getFullName ({ firstName, lastName }) {
  return `${firstName} ${lastName}`;
}
const arr = [1, 2, 3, 4];

// bad
const first = arr[0];
const second = arr[1];

// good
const [first, second] = arr;
  • 函数需要回传多个值时,请使用对象的解构,而不是数组的解构

原因:可以非破坏性地随时增加或者改变属性顺序

// bad
function doSomething () {
  return [top, right, bottom, left];
}

// 如果是数组解构,那么在调用时就需要考虑数据的顺序
const [top, xx, xxx, left] = doSomething();

// good
function doSomething () {
  return { top, right, bottom, left };
}

// 此时不需要考虑数据的顺序
const { top, left } = doSomething();

字符串

  • 字符串统一使用单引号的形式 '',eslint: quotes
// bad
const department = "JDC";

// good
const department = 'JDC';
  • 字符串太长的时候,请不要使用字符串连接符换行 ``,而是使用 +
const str = '凹凸实验室 凹凸实验室 凹凸实验室' +
  '凹凸实验室 凹凸实验室 凹凸实验室' +
  '凹凸实验室 凹凸实验室';
const test = 'test';

// bad
const str = ['a', 'b', test].join();

// bad
const str = 'a' + 'b' + test;

// good
const str = `ab${test}`;
  • 不要对字符串使用eval(),会导致太多漏洞, eslint: no-eval
// bad
const foo = ''this' \i\s "quoted"';

// good
const foo = ''this' is "quoted"';
const foo = `my name is '${name}'`;

函数

  • 函数单行不应该超过
  • 为了避免语法错误,团队约定在函数调用时,禁止使用空格
// 不推荐
fn ()
fn
()


// 推荐
fn();
  • 不要使用Function构造函数创建函数, eslint: no-new-func

原因:此方式创建函数和对字符串使用 eval() 一样会产生漏洞

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

// still bad
const subtract = Function('a', 'b', 'return a - b');
const f = function(){};
const g = function (){};
const h = function() {};

// good
const x = function b () {};
const y = function a () {};
  • 使用具名函数表达式而非函数声明,eslint: func-style

原因:这样做会导致函数声明被提升,这意味着很容易在文件中定义此函数之前引用它,不利于可读性和可维护性。如果你发现函数定义既庞大又复杂以至于不能理解文件的其他部分,或许你应该将它拆分成模块!别忘记要显式命名表达式,而不用管名字是否是从包含的变量(通常出现在现代浏览器中或者使用 Babel 编译器的时候)中推断的。这样会消除错误调用堆栈中的任何假设。 (讨论)

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

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

// good
// lexical name distinguished from the variable-referenced invocation(s)
const short = function longUniqueMoreDescriptiveLexicalFoo () {
  // ...
}
  • 用圆括号包裹自执行匿名函数,eslint:wrap-iife

原因:一个立即执行匿名函数表达式是一个单一的单元,将其及其调用括号包装在括号中,能够清楚地表达这一点。注意,在到处都是模块的世界中几乎不需要 IIFE。

// immediately-invoked function expression (IIFE)
(function () {
  console.log('Welcome to the Internet. Please follow me.');
}())
  • 不要在非函数代码块(if , while 等)中声明函数,eslint:no-loop-func
// bad
if (isUse) {
  function test () {
    // do something
  }
}

// good
let test;
if (isUse) {
  test = () => {
    // do something
  }
}
  • 不要将参数命名为 arguments,会导致该参数的优先级高于每个函数作用域内原先存在的 arguments 对象
// bad
function foo (name, options, arguments) {
  // ...
}

// good
function foo (name, options, args) {
  // ...
}
  • 不要使用 arguments,使用 剩余运算符 ...
  • arguments 只是一个类数组,而 ... 是一个真正的数组
// bad
function test () {
  const args = Array.prototype.slice.call(arguments)
  return args.join('')
}

// good
function test (...args) {
  return args.join('')
}
  • 使用参数默认值语法而不是修改函数参数
// really bad
function handleThings (opts) {
  // No! We shouldn't mutate function arguments.
  // Double bad: if opts is falsy it'll be set to an object which may
  // be what you want but it can introduce subtle bugs.
  opts = opts || {}
  // ...
}

// still bad
function handleThings (opts) {
  if (opts === void 0) {
    opts = {}
  }
  // ...
}

// good
function handleThings (opts = { }) {
  // ...
}
  • 避免参数默认值的副作用
let b = 1;
// bad
function count (a = b++) {
  console.log(a);
}
count();  // 1
count(); // 2
count(3); // 3
count();  // 3
  • 将参数默认值放在最后
// bad
function handleThings (opts = {}, name) {
  // ...
}

// good
function handleThings (name, opts = {}) {
  // ...
}

原因:操作作为参数传入的对象可能在原始调用中造成意想不到的变量副作用

// bad
function f1 (obj) {
  obj.key = 1;
}

// good
function f2 (obj) {
  const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
}

原因:参数重新赋值可能会导致无法预期的行为,尤其是当操作 arguments 对象时,也可能导致优化问题,尤其是在 V8 引擎中

// bad
function f1 (a) {
  a = 1;
}

function f2 (a) {
  if (!a) { a = 1 }
}

// good
function f3 (a) {
  const b = a || 1;
}

function f4 (a = 1) {
}
  • 调用可变参数函数时建议使用展开运算符 ..., eslint: prefer-spread

原因:显然你无需使用上下文,很难结合 newapply

// bad
const x = [1, 2, 3, 4, 5];
console.log.apply(console, x);

// good
const x = [1, 2, 3, 4, 5];
console.log(...x);

// bad
new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]));

// good
new Date(...[2016, 8, 5]);

箭头函数

原因:它将创建在 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;
})
  • 如果函数体只包含一条没有副作用的返回表达式的语句,可以省略花括号并使用隐式的 return, 否则保留花括号并使用 return 语句,eslint: arrow-parens, arrow-body-style
// bad
[1, 2, 3].map(number => {
  const nextNumber = number + 1;
  `A string containing the ${nextNumber}.`;
})

// good
[1, 2, 3].map(number => `A string containing the ${number}.`);

// good
[1, 2, 3].map((number) => {
  const nextNumber = number + 1;
  return `A string containing the ${nextNumber}.`;
})

// good
[1, 2, 3].map((number, index) => ({
  index: number;
}))

// No implicit return with side effects
function foo(callback) {
  const val = callback();
  if (val === true) {
    // Do something if callback returns true
  }
}

let bool = false;

// bad
foo(() => bool = true);

// good
foo(() => {
  bool = true;
})
  • 一旦表达式跨多行,使用圆括号包裹以便更好阅读
// bad
['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call(
    httpMagicObjectWithAVeryLongName,
    httpMethod
  )
)

// good
['get', 'post', 'put'].map(httpMethod => (
  Object.prototype.hasOwnProperty.call(
    httpMagicObjectWithAVeryLongName,
    httpMethod
  )
))
  • 函数如果只接收一个参数并且没使用用花括号,则省略圆括号,否则为了清晰明确则使用圆括号包裹参数,注意:总是使用圆括号也是可以接受的,eslint 中的 "always" 选项,eslint: arrow-parens
// bad
[1, 2, 3].map((x) => x * x);

// good
[1, 2, 3].map(x => x * x);

// good
[1, 2, 3].map(number => (
  `A long string with the ${number}. It’s so long that we’ve broken it ` +
  'over multiple lines!'
))

// bad
[1, 2, 3].map(x => {
  const y = x + 1;
  return x * y;
})

// good
[1, 2, 3].map((x) => {
  const y = x + 1;
  return x * y;
})

类&构造函数

  • 使用 class,避免直接操作 prototype
// bad
function Queue (contents = []) {
  this._queue = [..contents];
}
Queue.prototype.pop = function () {
  const value = this._queue[0];
  this._queue.splice(0, 1);
  return value;
}

// good
class Queue {
  constructor (contents = []) {
    this._queue = [...contents];
  }

  pop () {
    const value = this._queue[0];
    this._queue.splice(0, 1);
    return value;
  }
}
  • 使用 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];
  }
}
  • 如果未声明构造函数,则类会有一个默认的构造函数,没必要用空的构造函数或者将其委托给父类,eslint: no-useless-constructor
// bad
class Jedi {
  constructor () {}

  getName() {
    return this.name;
  }
}

// bad
class Rey extends Jedi {
  constructor (...args) {
    super(...args);
  }
}

// good
class Rey extends Jedi {
  constructor (...args) {
    super(...args)
    this.name = 'Rey'
  }
}

原因:重复的类成员声明会默认使用最后声明的,通常会导致 bug

// bad
class Foo {
  bar () { return 1 }
  bar () { return 2 }
}

// good
class Foo {
  bar () { return 1 }
}

// good
class Foo {
  bar () { return 2 }
}

模块

  • 使用标准的 ES6 模块语法 importexport

原因:模块是未来,让我们现在开始使用未来的特性

// bad
const util = require('./util');
module.exports = util;

// good
import Util from './util';
export default Util;

// better
import { Util } from './util';
export default Util;
  • 不要使用 import 的通配符 *,这样可以确保你只有一个默认的 export
// bad
import * as Util from './util';

// good
import Util from './util';
  • 同个文件每个模块只允许 import 一次,有多个 import 请书写在一起,eslint: no-duplicate-imports

原因:这样可以让代码更易于维护

// bad
import foo from 'foo';
// … some other imports … //
import { named1, named2 } from 'foo';

// good
import foo, { named1, named2 } from 'foo';

// good
import foo, {
  named1,
  named2
} from 'foo';
// bad
import foo from 'foo';
foo.init();

import bar from 'bar';

// good
import foo from 'foo';
import bar from 'bar';

foo.init();
  • 多行导入应该像多行数组和对象文字一样缩进
// bad
import { longNameA, longNameB, longNameC, longNameD, longNameE } from 'path';

// good
import {
  longNameA,
  longNameB,
  longNameC,
  longNameD,
  longNameE
} from 'path';
// bad
import fooSass from 'css!sass!foo.scss';
import barCss from 'style!css!bar.css';

// good
import fooSass from 'foo.scss';
import barCss from 'bar.css';

迭代器

  • 不要使用 iterators,建议使用 JS 更高优先级的函数代替 for-in 或 for-of 循环,除非迫不得已,eslint: no-iterator no-restricted-syntax
const numbers = [1, 2, 3, 4, 5];

// bad
let sum = 0;
for (let num of numbers) {
  sum += num;
}

// good
let sum = 0
numbers.forEach(num => sum += num);

// better
const sum = numbers.reduce((total, num) => total + num, 0);

生成器

  • 非必要请不要使用生成器 generator,使用async/awiat

原因:因为不能很好地翻译成 ES5 代码

对象属性

  • 使用 . 来访问对象属性
const joke = {
  name: 'haha',
  age: 28
}

// bad
const name = joke['name'];

// good
const name = joke.name;
  • 当访问的属性是变量时使用 []
const luke = {
  jedi: true,
  age: 28,
}

function getProp (prop) {
  return luke[prop];
}

const isJedi = getProp('jedi');

变量声明

  • 声明变量时,请使用 constlet 关键字,如果没有写关键字,变量就会暴露在全局上下文中,这样很可能会和现有变量冲突,另外,也很难明确该变量的作用域是什么。这里推荐使用 const 来声明变量,我们需要避免全局命名空间的污染。eslint: no-undef prefer-const
// bad
demo = new Demo();

// good
const demo = new Demo();
  • 将所有的 constlet 分组
// bad
let a;
const b;
let c;
const d;
let e;

// good
const b;
const d;
let a;
let c;
let e;;
  • 变量不要进行链式赋值

原因:变量链式赋值会创建隐藏的全局变量

// bad
(function example() {
  // JavaScript interprets this as
  // let a = ( b = ( c = 1 ) );
  // The let keyword only applies to variable a; variables b and c become
  // global variables.
  let a = b = c = 1;
}())

console.log(a); // throws ReferenceError
console.log(b); // 1
console.log(c); // 1

// good
(function example() {
  let a = 1;
  let b = a;
  let c = a;
}())

console.log(a); // throws ReferenceError
console.log(b); // throws ReferenceError
console.log(c); // throws ReferenceError

// the same applies for `const`

原因:声明但未被使用的变量通常是不完全重构犯下的错误.这种变量在代码里浪费空间并会给读者造成困扰

// bad

var some_unused_var = 42;

// Write-only variables are not considered as used.
var y = 10;
y = 5;

// A read for a modification of itself is not considered as used.
var z = 0;
z = z + 1;

// Unused function arguments.
function getX (x, y) {
  return x;
}

// good

function getXPlusY (x, y) {
  return x + y;
}

const x = 1;
const y = a + 2;

alert(getXPlusY(x, y));

// 'type' is ignored even if unused because it has a rest property sibling.
// This is a form of extracting an object that omits the specified keys.
const { type, ...coords } = data;
// 'coords' is now the 'data' object without its 'type' property.

Hoisting

  • var 存在变量提升的情况,即 var 声明会被提升至该作用域的顶部,但是他们的赋值并不会。而 constlet 并不存在这种情况,他们被赋予了 Temporal Dead Zones, TDZ, 了解 typeof 不再安全很重要
function example () {
  console.log(notDefined);   // => throws a ReferenceError
}

function example () {
  console.log(declareButNotAssigned);  // => undefined
  var declaredButNotAssigned = true;
}

function example () {
  let declaredButNotAssigned;
  console.log(declaredButNotAssigned);   // => undefined
  declaredButNotAssigned = true;
}

function example () {
  console.log(declaredButNotAssigned);   // => throws a ReferenceError
  console.log(typeof declaredButNotAssigned);  // => throws a ReferenceError
  const declaredButNotAssigned = true;
}
  • 匿名函数的变量名会提升,但函数内容不会
function example () {
  console.log(anonymous); // => undefined

  anonymous();

  var anonymous = function () {
    console.log('test');
  }
}
  • 命名的函数表达式的变量名会被提升,但函数名和函数函数内容并不会
function example() {
  console.log(named)  // => undefined

  named()   // => TypeError named is not a function

  superPower()  // => ReferenceError superPower is not defined

  var named = function superPower () {
    console.log('Flying')
  }
}

function example() {
  console.log(named)  // => undefined

  named()   // => TypeError named is not a function

  var named = function named () {
    console.log('named')
  }
}

比较运算符&相等

  • 使用 ===!== 而非 ==!=,eslint: eqeqeq
  • 条件声明例如 if 会用 ToBoolean 这个抽象方法将表达式转成布尔值并遵循如下规则

    • Objects 等于 true
    • Undefined 等于 false
    • Null 等于 false
    • Booleans 等于 布尔值
    • Numbers+0, -0, 或者 NaN 的情况下等于 false, 其他情况是 true
    • Strings'' 时等于 false, 否则是 true
    • if ([0] && []) {
      // true
      // 数组(即使是空数组)也是对象,对象等于true
      }
      
      [] == 0; // true  请注意!
      

标准特性

为了代码的可移植性和兼容性,我们应该最大化的使用标准方法,例如优先使用 string.charAt(3) 而不是 string[3]

with() {}

由于 with 方法会产生神奇的作用域,所以我们也是禁止使用该方法的

修改内置对象的原型

不要修改内置对象,如 ObjectArray

React&JSX 书写规范

本规范基本基于标准的 JavaScript 语法规范

基本规则

  • 每个文件只包含一个 React 类组件

  • 一般使用 JSX 语法
  • 除非是在非 JSX 文件中初始化应用,否则不要使用 React.createElement

命名规范

  • 组件文件扩展名

如果使用 JavaScript,则文件扩展名为 .js;如果使用 TypeScript,则文件扩展名为 .tsx

  • 组件文件名

如果是组件文件,则使用 PascalCase,如 MyComponent.js

如果组件是一个目录,则组件主入口命名为 index,如 index.js

  • 引用命名

React 组件使用 PascalCase,组件实例使用 CamelCase,eslint: react/jsx-pascal-case

// bad
import reservationCard from './ReservationCard'

// good
import ReservationCard from './ReservationCard'

// bad
const ReservationItem = <ReservationCard />

// good
const reservationItem = <ReservationCard />
  • 组件命名

使用文件名作为组件名字,例如, ReservationCard.js 应该包含名为 ReservationCard 的引用,然而对于文件夹中的根组件, 使用 index.js 作为文件名,使用文件夹的名字作为组件的名字

// bad
import Footer from './Footer/Footer'

// bad
import Footer from './Footer/index'

// good
import Footer from './Footer'
  • 组件属性名

React DOM 使用小驼峰式命名法来定义属性的名称,而不使用 HTML 属性名称的命名约定,例如

<div onClick={this.handler} />

Class Component VS Functional Component

只允许使用 Class ComponentFunctional Component 两种形态来书写组件,条件语序的情况下建议尽量使用函数式组件配合 Hooks 来进行开发

对齐

遵循以下JSX语法的对齐风格,eslint: react/jsx-closing-bracket-location

// bad
<Foo superLongParam='bar'
     anotherSuperLongParam='baz' />

// good
<Foo
  superLongParam='bar'
  anotherSuperLongParam='baz'
/>

// if props fit in one line then keep it on the same line
<Foo bar='bar' />

// children get indented normally
<Foo
  superLongParam='bar'
  anotherSuperLongParam='baz'
>
  <Quux />
</Foo>

// bad
{showButton &&
  <Button />
}

// bad
{
  showButton &&
    <Button />
}

// good
{showButton && (
  <Button />
)}

// good
{showButton && <Button />}

空格

// bad
<Foo/>

// very bad
<Foo                 />

// bad
<Foo
 />

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

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

引号

JSX 属性要使用单引号,与其他普通 JS 保持一致

// bad
<Foo bar="bar" />

// good
<Foo bar='bar' />

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

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

属性

  • 属性名使用 CamelCase
// bad
<Foo
  UserName='hello'
  phone_number={12345678}
/>

// good
<Foo
  userName='hello'
  phoneNumber={12345678}
/>
// bad
<Foo
  hidden={true}
/>

// good
<Foo
  hidden
/>

// good
<Foo hidden />

原因:不使用稳定的 ID 会对性能产生副作用并且组件状态会出问题,是一种反模式

// bad
{todos.map((todo, index) =>
  <Todo
    {...todo}
    key={index}
  />
)}

// good
{todos.map(todo => (
  <Todo
    {...todo}
    key={todo.id}
  />
))}
  • 为所有的非必需属性定义使用 defaultProps 明确的默认值
// bad
function SFC ({ foo, bar, children }) {
  return <div>{foo}{bar}{children}</div>
}
SFC.propTypes = {
  foo: PropTypes.number.isRequired,
  bar: PropTypes.string,
  children: PropTypes.node
}

// good
function SFC ({ foo, bar, children }) {
  return <div>{foo}{bar}{children}</div>
}
SFC.propTypes = {
  foo: PropTypes.number.isRequired,
  bar: PropTypes.string,
  children: PropTypes.node
}
SFC.defaultProps = {
  bar: '',
  children: null
}

Refs

避免使用字符串引用,请使用回调函数作为引用,eslint: react/no-string-refs

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

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

圆括号

当 JSX 标签超过一行时使用圆括号包裹, eslint: react/wrap-multilines


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

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

// good, when single line
render () {
  const body = <div>hello</div>
  return <MyComponent>{body}</MyComponent>
}

标签

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

// good
<Foo className='stuff' />
// bad
<Foo
  bar='bar'
  baz='baz' />

// good
<Foo
  bar='bar'
  baz='baz'
/>

方法

  • 使用箭头函数包裹本地变量
function ItemList (props) {
  return (
    <ul>
      {props.items.map((item, index) => (
        <Item
          key={item.key}
          onClick={() => doSomethingWith(item.name, index)}
        />
      ))}
    </ul>
  )
}
  • 类组件的内部方法不要使用下划线前缀
// bad
class extends React.Component {
  _onClickSubmit () {
    // do stuff
  }

  // other stuff
}

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

  // other stuff
}
  // bad
  render () {
    (<div />)
  }

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

Hooks 书写规范

  • Hooks 只能应用于函数式组件中
  • 只在 React 函数最顶层使用 Hooks

不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们

// bad
function a () {
  const [count, setCount] = useState(0)
  useEffect(function persistForm() {
    localStorage.setItem('formData', accountName)
  })
  const x = function () {}
  const [timer, setTimer] = useState(0)

  // main logic
}

// bad
function a () {
  const [count, setCount] = useState(0)
  useEffect(function persistForm() {
    localStorage.setItem('formData', accountName)
  })
  const [timer, setTimer] = useState(0)
  const x = function () {}
  // main logic
}

\

文件与组件命名

  • 扩展名: 使用.js作为js文件的扩展名。如果同一个文件夹下有同名而不同作用的js文件,则通过中缀(小写)进一步区分,例如:HomeView.component.js,HomeView.style.js,HomeView.action.js等。
  • 文件名: 使用驼峰命名法且首字母大写,如HomeView.js
  • 组件命名: 与文件名(除中缀外)完全一致。如果组件单独放置在目录中,则目录名也一致。
// bad
import Footer from './Component/Footer/FooterView'

// good
import Footer from './Component/Footer/Footer'

// good
import Footer from './Footer'

组件声明

  • 使用class与extends关键字。不使用React.createClass方法。需要导出的组件直接在class关键字前使用export default。
// bad
export default React.createClass({
});

// good
export default class HomeView extends React.Component {
}

对齐

  • 按下面的案例对齐:
// bad
<Foo superLongParam="bar"
     anotherSuperLongParam="baz" />

// good
<Foo
  superLongParam="bar"
  anotherSuperLongParam="baz"
/>

// 如果一行能摆下props,那就摆在一行
<Foo bar="bar" />

// 子组件照常缩进
<Foo
  superLongParam="bar"
  anotherSuperLongParam="baz"
>
  <Spazz />
</Foo>

引号

  • 对于JSX的字符串属性使用双引号("),其他情况下使用单引号。
// bad
<Foo bar='bar' />

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

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

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

空格

  • 在自闭合的标签中包含一个空格。
// bad
<Foo/>

// very bad
<Foo                 />

// bad
<Foo
 />

// good
<Foo />

state/props

  • 对于多个单词组成的pros,使用驼峰命名法。不使用下划线或连接线。
// bad
<Foo
  UserName="hello"
  phone_number={12345678}
/>

// good
<Foo
  userName="hello"
  phoneNumber={12345678}
/>
  • 读取state和props时,使用const与解构,必要时可使用let。不使用var。
// bad
var userName = this.props.userName;
let checked = this.state.checked;

// good
const { userName, age, sex } = this.props;
const { checked } = this.state;

括号

  • 当JSX标签超过一行时,使用括号包裹。
/// bad
render() {
  return <MyComponent className="long body" foo="bar">
           <MyChild />
         </MyComponent>;
}

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

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

标签

  • 对于没有子组件的JSX标签,始终自闭合。
// bad
<Foo className="stuff"></Foo>

// good
<Foo className="stuff" />
  • 如果组件有多行属性,则另起一行进行自闭合。
// bad
<Foo
  bar="bar"
  baz="baz" />

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

方法

  • 为方法命名时,不使用下划线开头(哪怕是想用作私有方法)。
// bad
React.createClass({
  _onClickSubmit() {
    // do stuff
  }

  // other stuff
});

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

  // other stuff
});

方法声明的顺序

  • 原则上按如下顺序排列React组件的各个方法(生命周期):
  1. constructor
  1. 静态方法(static methods)
  1. getChildContext
  1. componentWillMount
  1. componentDidMount
  1. componentWillReceiveProps
  1. shouldComponentUpdate
  1. componentWillUpdate
  1. componentDidUpdate
  1. componentWillUnmount
  1. 点击处理或事件处理函数,比如onClickSubmit()、onChangeDescription()
  1. 用在render中的getter方法,比如getSelectReason()、getFooterContent()
  1. 可选的render方法,比如renderNavigation()、renderProfilePicture()
  1. render
  • 按如下方式定义propTypes, defaultProps, contextTypes等
import React, { Component, PropTypes } from 'react';

const propTypes = {
  id: PropTypes.number.isRequired,
  url: PropTypes.string.isRequired,
  text: PropTypes.string,
};

const defaultProps = {
  text: 'Hello World',
};

class Link extends Component {
  static methodsAreOk() {
    return true;
  }

  render() {
    return <a href={this.props.url} data-id={this.props.id}>{this.props.text}</a>
  }
}

Link.propTypes = propTypes;
Link.defaultProps = defaultProps;

export default Link;

⬆ 回到目录

ES6代码规范

变量与常量声明

  • 1.1 变量

尽量使用let来代替var

对于全局变量声明,采用global.xxx = xxx,但应避免声明过多全局变量污染环境

  • 1.2 常量

对于常量应使用const进行声明,命名采用驼峰写法

对于使用 immutable 数据应用const进行声明

// 不好
let someNum = 123;
const AnotherStr = '不变的字符串';
let arr = ['不', '变', '数', '组'];
var ANOTHER_OBJ = {
  '不变对象': true
};


// 好
const someNum = 123;
const anotherStr = '不变的字符串';
const arr = ['不', '变', '数', '组'];
const anotherObj = {
  '不变对象': true
};

字符串

  • 2.1 处理多行字符串,使用模板字符串

以反引号( ` )标示

可读性更强,代码更易编写

注意排版引起空格的问题,使用场景为声明HTML模板字符串

// 不好
const tmpl = '<div class="content"> \n' +
              '<h1>这是换行了。</h1> \n' +
            '</div>';


// 好
const tmpl = `
<div class="content">
  <h1>这是换行了。</h1>
</div>`;
  • 2.2 处理字符串拼接变量时,使用模板字符串
  // 不好
  function sayHi(name) {
    return 'How are you, ' + name + '?';
  }


  // 好
  function sayHi(name) {
    return `How are you, ${name}?`;
  }

解构

  • 3.1 解构语句中不使用圆括号
// 不好
[(a)] = [11]; // a未定义
let { a: (b) } = {}; // 解析出错


// 好
let [a, b] = [11, 22];
  • 3.2 对象解构

对象解构 元素与顺序无关

对象指定默认值时仅对恒等于undefined ( !== null ) 的情况生效

  • 3.2.1 若函数形参为对象时,使用对象解构赋值
// 不好
function someFun(opt) {
  let opt1 = opt.opt1;
  let opt2 = opt.opt2;
  console.log(op1);
}


// 好
function someFun(opt) {
  let { opt1, opt2 } = opt;
  console.log(`$(opt1) 加上 $(opt2)`);
}

function someFun({ opt1, opt2 }) {
  console.log(opt1);
}
  • 3.2.2 若函数有多个返回值时,使用对象解构,不使用数组解构,避免添加顺序的问题
// 不好
function anotherFun() {
  const one = 1, two = 2, three = 3;
  return [one, two, three];
}
const [one, three, two] = anotherFun(); // 顺序乱了
// one = 1, two = 3, three = 2


// 好
function anotherFun() {
  const one = 1, two = 2, three = 3;
  return { one, two, three };
}
const { one, three, two } = anotherFun(); // 不用管顺序
// one = 1, two = 2, three = 3
  • 3.2.3 已声明的变量不能用于解构赋值(语法错误)
// 语法错误
let a;
{ a } = { b: 123};
  • 3.3 数组解构

数组元素与顺序相关

  • 3.3.1 交换变量的值
let x = 1;
let y = 2;

// 不好
let temp;
temp = x;
x = y;
y = temp;


// 好
[x, y] = [y, x]; // 交换变量
  • 3.3.2 将数组成员赋值给变量时,使用数组解构
const arr = [1, 2, 3, 4, 5];

// 不好
const one = arr[0];
const two = arr[1];


// 好
const [one, two] = arr;

数组

  • 4.1 将类数组(array-like)对象与可遍历对象(如Set, Map)转为真正数组

采用Array.from进行转换

// 不好
function foo() {
  let args = Array.prototype.slice.call(arguments);
}


// 好
function foo() {
  let args = Array.from(arguments);
  // let args = [...arguments]
}
  • 4.2 数组去重

结合Set结构与Array.from

使用indexOf,HashTable等形式,不够简洁清晰

// 好
function deduplication(arr) {
  return Array.from(new Set(arr));
}
// 简写
const arr = [1, 2, 2, 4]
const newArr = [...new Set(arr)];
  • 4.3 数组拷贝(浅拷贝)

采用数组扩展...形式

const items = [1, 2, 3];

// 不好
const len = items.length;
let copyTemp = [];
for (let i = 0; i < len; i++) {
  copyTemp[i] = items[i];
}


// 好
let copyTemp = [...items];
  • 4.4 将一组数值转为数组

采用Array.of进行转换

// 不好
let arr1 = new Array(2); // [undefined x 2]
let arr2 = new Array(1, 2, 3); // [1, 2, 3]


// 好
let arr1 = Array.of(2);  // [2]
let arr2 = Array.of(1, 2, 3); // [1, 2, 3]

函数

  • 5.1 当要用函数表达式或匿名函数时,使用箭头函数(Arrow Functions)

箭头函数更加简洁,并且绑定了this

// 不好
const foo = function(x) {
  console.log(foo.name); // 返回'' ,函数表达式默认省略name属性
};

[1, 2, 3].map(function(x) {
  return x + 1;
});

var testObj = {
  name: 'testObj',
  init() {
    var _this = this; // 保存定义时的this引用
    document.addEventListener('click', function() {
      return _this.doSth();
    }, false);
  },
  doSth() {
    console.log(this.name);
  }
};

// 好
const foo = (x) => {
  console.log(foo.name); // 返回'foo'
};

[1, 2, 3].map( (x) => {
  return x + 1;
});

var testObj = {
  name: 'testObj',
  init() {
    // 箭头函数自动绑定定义时所在的对象
    document.addEventListener('click', () => this.doSth(), false);
  },
  doSth() {
    console.log(this.name);
  }
};
  • 5.1.1 箭头函数书写约定

函数体只有单行语句时,允许写在同一行并去除花括号

当函数只有一个参数时,允许去除参数外层的括号

// 好
const foo = x => x + x; // 注意此处会隐性return x + x

const foo = (x) => {
  return x + x; // 若函数体有花括号语句块时须进行显性的return
}; 

[1, 2, 3].map( x => x * x);
  • 5.1.2 用箭头函数返回一个对象,应用括号包裹
// 不好
let test = x => { x: x }; // 花括号会变成语句块,不表示对象


// 好
let test = x => ({ x: x }); // 使用括号可正确return {x:x}
  • 5.2 立即调用函数 IIFE

使用箭头函数

// 不好
(function() {
  console.log('哈');
})();


// 好
(() => {
  console.log('哈');
})();
  • 5.3 函数参数指定默认值

采用函数默认参数赋值语法

// 不好
function foo(opts) {
  opts = opts || {};// 此处有将0,''等假值转换掉为默认值的副作用
}


// 好
function foo(opts = {}) {
  console.log('更加简洁,安全');
}
  • 5.4 对象中的函数方法使用缩写形式

更加简洁

函数方法不要使用箭头函数,避免this指向的混乱

// 不好
const shopObj = {
  des: '对象模块写法',
  foo: function() {
    console.log(this.des);
  }
};

const shopObj = {
  des: '对象模块写法',
  foo: () => {
    console.log(this.des); // 此处会变成undefined.des,因为指向顶层模块的this
  }
};

// 好
const des = '对象模块写法'; // 使用对象属性值简写方式
const shopObj = {
  des,
  foo() {
    console.log(this.des);
  }
};

  • 6.1 类名应使用帕斯卡写法(PascalCased) --大驼峰
// 好
class SomeClass {

}
  • 6.1.1 类名与花括号须保留一个空格间距

类中的方法定义时,括号 ) 也须与花括号 { 保留一个空格间距

// 不好
class Foo{
  constructor(){
    // constructor
  }
  sayHi()    {
    // 仅保留一个空格间距
  }
}


// 好
class Foo {
  constructor() {
    // constructor
  }
  sayHi() {
    // 仅保留一个空格间距
  }
}
  • 6.2 定义类时,方法的顺序如下:

    • constructor
    • public get/set 公用访问器,set只能传一个参数
    • public methods 公用方法,公用相关命名使用小驼峰式写法(lowerCamelCase)
    • private get/set 私有访问器,私有相关命名应加上下划线 _ 为前缀
    • private methods 私有方法
// 好
class SomeClass {
  constructor() {
    // constructor
  }

  get aval() {
    // public getter
  }

  set aval(val) {
    // public setter
  }

  doSth() {
    // 公用方法
  }

  get _aval() {
    // private getter
  }

  set _aval() {
    // private setter
  }

  _doSth() {
    // 私有方法
  }
}
  • 6.3 如果不是class类,不使用new
// 不好
function Foo() {

}
const foo = new Foo();


// 好
class Foo {

}
const foo = new Foo();
  • 6.4 使用真正意思上的类Class写法,不使用prototype进行模拟扩展

Class更加简洁,易维护

// 不好
function Dog(names = []) {
  this._names = [...names];
}
Dog.prototype.bark = function() {
  const currName = this._names[0];
  alert(`one one ${currName}`);
}

// 好
class Dog {
  constructor(names = []) {
    this._names = [...names];
  }
  bark() {
    const currName = this._names[0];
    alert(`one one ${currName}`);
  }
}
  • 6.5 this的注意事项

子类使用super关键字时,this应在调用super之后才能使用

可在方法中return this来实现链式调用写法

静态方法中禁止使用this

class Foo {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

// 不好
class SubFoo extends Foo {
  constructor(x, y, z) {
    this.z = z; // 引用错误
    super(x, y);
  }
}


// 好
class SubFoo extends Foo {
  constructor(x, y, z) {
    super(x, y);
    this.z = z; // this 放在 super 后调用
  }
  setHeight(height) {
    this.height = height;
    return this;
  }
}

模块

  • 7.1 使用import / export来做模块加载导出,不使用非标准模块写法

跟着标准走的人,运气总不会太差

// 不好
const colors  = require('./colors');
module.exports = color.lightRed;


// 好
import { lightRed } from './colors';
export default lightRed;
  • 7.2 import / export 后面采用花括号{ }引入模块的写法时,须在花括号内左右各保留一个空格
// 不好
import {lightRed} from './colors';
import { lightRed} from './colors';

// 好
import { lightRed } from './colors';

\

BOM

Unicode 字节顺序标记 (BOM) 用来指定代码单元是高字节序还是低字节序。也就是说,是高位在前还是低位在前。UTF-8 不需要 BOM 来表明字节顺序,因为单个字节并不影响字节顺序。

相信不少同学遇到过 BOM 的坑,这里不多说了,切记不要使用 windows 的记事本改代码!

ESLint配置

插件端配置eslint