前端编码最佳实践-基本篇

333 阅读6分钟

本文是在阿里巴巴前端规约的基础上重点突出我们日常需要注意的问题或不遵循可能导致生产事故的问题。

阅读对象:入职3年内前端新手。

1. eslint-disable 规范

建议 disable 的同时也要写明理由。

单行禁用请注明禁用的 rule

no-abusive-eslint-disable

var _devtools; /* eslint-disable-line no-var 使用的理由 */

禁用下一行

/* eslint-disable-next-line: no-alert 使用的理由 */
alert('foo');

// 禁用多个规则时用逗号分隔
// eslint-disable-line no-alert, quotes, semi

多行禁用一定要在禁用部分末尾重新 enable

/* eslint-disable no-var 使用的理由 */
var _Store = __Store;
var _devtools;
/* eslint-enable no-var */

技巧:可以将各种 eslint 禁用指令做成 snippet:

"eslint-disable-next-line": {
  "prefix": "/* eslint-disable-next-line: 规则名 禁止的理由 */",
  "body": "/* eslint-disable-next-line 规则名 禁止的理由 */",
  "description": "/* eslint-disable-next-line 规则名 禁止的理由 */"
},

2. JavaScript 规范

浅拷贝优先用对象展开操作符替代 Object.assign

eslint: prefer-destructuring

访问和使用多个对象属性时,请使用解构

eslint: prefer-destructuring

Bad
function getFullName(user) {
  const firstName = user.firstName;
  const lastName = user.lastName;

  return `${firstName} ${lastName}`;
}
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}`;
}

尽量避免使用 prototype

99% 的情况我们可以有其他更安全、更优雅和更简洁的替代方案

Bad
// 判断是否数组
Object.prototype.toString.call(value) === '[object Array]'
Good
Array.isArray(value)

函数

永远不要把参数命名为 arguments。这将会覆盖原来函数作用域内的 arguments 对象

Bad
function foo(name, options, arguments) {
}
Good
function foo(name, options, args) {
}

不要使用 arguments。可以选择 rest 语法 ... 替代

[eslint: prefer-rest-params]

使用 ... 能明确你要传入的参数。另外 rest参数是一个真正的数组, 而 arguments 是一个类数组。

Bad
function concatenateAll() {
  const args = Array.prototype.slice.call(arguments);
  
  return args.join('');
}
Good
function concatenateAll(...args) {
  return args.join('');
}

默认参数语法

// good
function handleThings(opts = {}) {
}

始终将默认参数放在最后

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

推荐参数解构,对于多个参数的函数,也推荐使用对象传参的形式

Bad
function getPerson(data){
  const hand = data.hand;
  const head = data.head;
  const body = data.body;
}
Good
function getPerson({ hand, head, body }){
    
}

禁止使用 Function 构造函数来创建新函数

[eslint: no-new-func]

Bad
var add = new Function('a', 'b', 'return a + b');

不要改变入参

[eslint: no-param-reassign]

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

参数不要重新赋值

[eslint: no-param-reassign]

Bad
function f1(a) {
  a = a || 1;
}
Good
function f3(_a) {
  const a = _a || 1;
}

优先使用展开运算符 ... 来调用可变参数函数

[eslint: prefer-spread]

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
// 无副作用,可省略大括号
[1, 2, 3].map(number => `A string containing the ${number}.`);
Good
let bool = false;
// ❌
foo(() => bool = true);

// ✅ 对bool产生了副作用
foo(() => {
  bool = true;
});

总是使用 class。避免直接操作 prototype 。

方法可以返回 this 来帮助链式调用。

class Jedi {
  jump() {
    this.jumping = true;
    
    return this;
  }

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

const luke = new Jedi();

luke.jump().setHeight(20);

模块

总是使用ES的模块 (import/export),不要用require等其他的方式

不要从 import中直接 export

分类:代码风格一致性

原因:虽然一行代码很简练,但是保持清晰的导入导出语句让代码风格更一致。

来自 github.com/airbnb/java…

// ❌
export { es6 as default } from './AirbnbStyleGuide';

// ✅
import { es6 } from './AirbnbStyleGuide';

export default es6;

只在一个路径中 import

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

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

不要 export 可变绑定

[eslint: import/no-mutable-exports]

// ❌
export let foo = {};

// ✅
export const foo = {};

在只有单个导出的模块中,默认 export 优于命名 export

该规则已废弃,任何情况下都优先具名导出

[eslint: import/prefer-default-export]

// ❌
export function foo() {}

// ✅
export default function foo() {}

禁止匿名默认导出

详见 优先命名导出而非默认匿名导出

// ❌
export function () {}

export default (props) => {
  return </>
}

// ✅
export function foo() {}
export default function CascaderRegion(props) {
  ...
}

迭代器

请使用高阶函数替代迭代器,尽量保持原数组的不变性(无副作用)

[eslint: no-iterator no-restricted-syntax]

使用 map() / every() / filter() / find() / findIndex() / reduce() / some() / ... 遍历数组。

const numbers = [1, 2, 3, 4, 5];

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

// NOT BAD
let sum = 0;
numbers.forEach((num) => {
  sum += num;
});

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

不要使用 for of 或 generators (生成器)

for of看似简洁实际可能编译成 ES5 代码后及其复杂generators 不能很好的转译成 ES5。

变量

建议类型变量在声明时就赋予相应类型的默认值

  • number 0
  • string ''
  • boolean false
  • 对象 {}null 看情况而定

其次

  1. 永远不要将一个变量初始化为undefined,这是没有意义的
  2. 如果是一个object类型,显式地初始化为null是一个好习惯,会便于类型推导
Bad
let sum

sum += 1;
Good
let sum = 0

sum += 1;
let str = ''

str += 1;

const 和 let 分组

// ❌
let i;
const items = getItems();
let dragonball;
const goSportsTeam = true;
let len;

// ✅
const goSportsTeam = true;
const items = getItems();
// 空行隔离分界更清晰
let dragonball;
let i;
let length;

变量不要链式赋值

[eslint: no-multi-assign]

原因:链接变量赋值会创建隐式全局变量。

let a = b = c = 1;

✅
let a = 1;
let b = a;
let c = a;

避免单独使用一元递增和递减运算符

❌
num++;

✅
num += 1

比较运算符

使用 === 和 !== 优先于 == 和 !=

[eslint: eqeqeq]

对于布尔值使用简写,但对于字符串和数字使用显式比较

// ❌
if (isValid === true) {
}
// ✅
if (isValid) {
}

// ❌
if (name) {
}
// ✅
if (name !== '') {
}

// ❌
if (collection.length) {
}
// ✅
if (collection.length > 0) {
}

三元表达式不应该嵌套,通常写成单行表达式

[eslint: no-nested-ternary]

// ❌
const foo = maybe1 > maybe2 ? "bar" : value1 > value2 ? "baz" : null;

// ✅ 拆分成两个三元表达式
const maybeNull = value1 > value2 ? 'baz' : null;
const foo = maybe1 > maybe2 ? 'bar' : maybeNull;

当运算符混合在一个语句中时,请将其放在括号内

[eslint: no-mixed-operators]

// ❌
const foo = a && b < 0 || c > 0 || d + 1 === 0;
// ✅
const foo = (a && b < 0) || c > 0 || (d + 1 === 0);

代码块

总是使用大括号包裹所有代码块。

[eslint: nonblock-statement-body-position]

Bad
if (test)
  return false;

if (test) return false;
Good
if (test) {
  return false;
}

2. 如果通过 if 和 else 使用多行代码块,把else放在if代码块闭合括号的同一行

[eslint: brace-style]

// ❌
if (test) {
  thing1();
  thing2();
}
else {
  thing3();
}

// ✅
if (test) {
  thing1();
  thing2();
} else {
  thing3();
}

3. 如果if总是会返回一个return语句,则esle不用写

[eslint: no-else-return]

// ❌
function foo() {
  if (x) {
    return x;
  } else {
    return y;
  }
}

// ✅
function foo() {
  if (x) {
    return x;
  }

  return y;
}

控制语句

如果您的控制语句(if, while 的)太长或超过最大行长度,那么每个(分组)条件可以放单独一行。逻辑运算符应该放在每行起始处。

Bad
// ❌
if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) {
  thing1();
}
Good
// ✅
if (
  (foo === 123 || bar === 'abc') 
  && doesItLookGoodWhenItBecomesThatLong() 
  && isThisReallyHappening()
) {
  thing1();
}

更多阅读