前端开发规范
1、代码规范
类型
- 1.1 基本类型
- 基本类型赋值时,应该直接使用类型的值
stringnumberbooleannullundefinedsymbol
- 基本类型赋值时,应该直接使用类型的值
const foo = 1; let bar = foo; bar = 9; console.log(foo, bar); // => 1,9 |
-
- Symbols 不能被完全 polyfill, 所以不应该在目标浏览器/环境不支持它们的情况下使用它们。
-
- 复杂类型
objectarrayfunction
- 复杂类型
const foo = [1, 2]; const bar = foo; bar[0] = 9; console.log(foo[0], bar[0]); // => 9, 9 // const 只能阻止引用类型地址的重新赋值 // 并不能保证引用类型的属性等不变 |
引用 Reference
- 2.1 所有的赋值都用
const,避免使用var. eslint:prefer-const,no-const-assign- 尽量确保你的代码中的状态是可控范围内的,重复引用会出现难以理解的 bug 和代码。
// bad var a = 1; var b = 2; // good const a = 1; const b = 2; |
- 2.2 如果你一定要对参数重新赋值,那就用
let,而不是var. eslint:no-varlet是块级作用域,var是函数级作用域,同样是为了减少代码的不可控,减少 “意外”
// bad
var count = 1;
if (true) {
count += 1;
}
// good, use the let.
let count = 1;
if (true) {
count += 1;
}
|
- 2.3
let、const都是块级作用域
// const 和 let 都只存在于它定义的那个块级作用域
{
let a = 1;
const b = 1;
}
console.log(a); // ReferenceError,引用错误
console.log(b); // ReferenceError,引用错误
|
对象 Objects
- 3.1 使用字面值创建对象. eslint:
no-new-object
// bad
const item = new Object();
// good
const item = {};
|
- 3.2 属性值缩写. eslint:
object-shorthand
const lukeSkywalker = 'Luke Skywalker';
// bad
const obj = {
lukeSkywalker: lukeSkywalker,
};
// good
const obj = {
lukeSkywalker
};
|
- 3.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,
};
|
- 3.4 对象浅拷贝时,更推荐使用扩展运算符
...,而不是Object.assign。解构赋值获取对象指定的几个属性时,推荐用 rest 运算符,也是...
// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 });
delete copy.a; // so does this 改变了 original
// 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 }
|
数组 Arrays
- 4.1 用字面量赋值。 eslint:
no-array-constructor
// bad const items = new Array(); // good const items = []; |
- 4.2 用Array#push 向数组中添加一个值而不是直接用下标。
const someStack = [];
// bad
someStack[someStack.length] = 'abracadabra';
// good
someStack.push('abracadabra');
|
- 4.3 用扩展运算符做数组浅拷贝,类似上面的对象浅拷贝
// bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i += 1) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
|
- 4.4 推荐用
...运算符而不是Array.from来将一个类数组转换成数组。
const foo = document.querySelectorAll('.foo');
// good
const nodes = Array.from(foo);
// best
const nodes = [...foo];
|
- 4.5 用
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);
|
解构 Destructuring
- 5.1 用对象的解构赋值来获取和使用对象某个或多个属性值。 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}`;
}
|
- 5.2 数组解构
const arr = [1, 2, 3, 4]; // bad const first = arr[0]; const second = arr[1]; // good const [first, second] = arr; |
字符串 Strings
- 6.1 string 统一用单引号
''。 eslint:quotes
// bad const name = "Capt. Janeway"; // bad - 模板应该包含插入文字或换行 const name = `Capt. Janeway`; // good const name = 'Capt. Janeway'; |
- 6.2 用字符串模板而不是
+来拼接字符串。 eslint:prefer-templatetemplate-curly-spacing
// 模板字符串更具可读性、语法简洁、字符串插入参数。
// bad
function sayHi(name) {
return 'How are you, ' + name + '?';
}
// bad
function sayHi(name) {
return ['How are you, ', name, '?'].join();
}
// good
function sayHi(name) {
return `How are you, ${name}?`;
}
|
- 6.3 永远不要在字符串中用
eval(),漏洞太多。 eslint:no-eval
- 6.4 不要使用不必要的转义字符。eslint:
no-useless-escape
反斜线可读性差,只在必要时使用
// 反斜线可读性差,只在必要时使用
// bad
const foo = '\'this\' \i\s \"quoted\"';
// good
const foo = '\'this\' is "quoted"';
//best
const foo = `my name is '${name}'`;
|
函数 Functions
- 7.1 把立即执行函数包裹在圆括号里。 eslint:
wrap-iife
// immediately-invoked function expression (IIFE)
(function () {
console.log('Welcome to the Internet. Please follow me.');
}());
|
-
7.2 注意: 在ECMA-262中 [块
block] 的定义是: 一系列的语句; 但是函数声明不是一个语句。 函数表达式是一个语句。
// bad
if (currentUser) {
function test() {
console.log('Nope.');
}
}
// good
let test;
if (currentUser) {
test = () => {
console.log('Yup.');
};
}
|
- 7.3 永远不要用
arguments命名参数。它的优先级高于每个函数作用域自带的arguments对象, 所以会导致函数自带的arguments值被覆盖。
// bad
function foo(name, options, arguments) {
// ...
}
// good
function foo(name, options, args) {
// ...
}
|
- 7.4 优先使用rest语法
...,而不是arguments。 eslint:prefer-rest-params
// bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}
// good
function concatenateAll(...args) {
return args.join('');
}
|
- 7.5 使用默认参数语法,而不是在函数里对参数重新赋值。
// really bad
function handleThings(opts) {
// 虽然你想这么写, 但是这个会带来一些细微的bug
// 如果 opts 的值为 false, 它会被赋值为 {}
opts = opts || {};
// ...
}
// still bad
function handleThings(opts) {
if (opts === void 0) {
opts = {};
}
// ...
}
// good
function handleThings(opts = {}) {
// ...
}
|
- 7.6 使用默认参数时,需要避免副作用
var b = 1;
// bad
function count(a = b++) {
console.log(a);
}
count(); // 1
count(); // 2
count(3); // 3
count(); // 3
// 很容易让人懵逼
|
- 7.7 把默认参数赋值放在最后
// bad
function handleThings(opts = {}, name) {
// ...
}
// good
function handleThings(name, opts = {}) {
// ...
}
|
- 7.8 函数签名部分要有空格。eslint:
space-before-function-parenspace-before-blocks
// bad
const f = function(){};
const g = function (){};
const h = function() {};
// good
const x = function () {};
const y = function a() {};
|
- 7.9 永远不要改参数. eslint:
no-param-reassign
// 特别注意引用类型的操作,保证数据的不可变性
// bad
function f1(obj) {
obj.key = 1;
};
// good
function f2(obj) {
const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
};
|
- 7.10 不要对参数重新赋值。 eslint:
no-param-reassign
// 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) {
// ...
}
|
箭头函数 Arrow Functions
- 8.1 如果要用匿名函数做回调,最好使用箭头函数 eslint:
prefer-arrow-callback,arrow-spacing
// 它创建了一个在上下文中执行的函数,这通常是您想要的,并且是一种更简洁的语法。
// 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;
});
|
- 8.2 如果函数体由一个没有副作用的表达式的单个语句组成,去掉大括号和 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
}));
// 表达式有副作用就不要用隐式返回
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;
});
|
- 8.3 如果表达式有多行,首尾放在圆括号里更可读。
// bad
['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call(
httpMagicObjectWithAVeryLongName,
httpMethod
)
);
// good
['get', 'post', 'put'].map(httpMethod => (
Object.prototype.hasOwnProperty.call(
httpMagicObjectWithAVeryLongName,
httpMethod
)
));
|
- 8.4 避免箭头函数语法
=>和比较操作符<=, >=混淆. eslint:no-confusing-arrow
// bad
const itemHeight = item => item.height > 256 ? item.largeSize : item.smallSize;
// bad
const itemHeight = (item) => item.height > 256 ? item.largeSize : item.smallSize;
// good
const itemHeight = item => (item.height > 256 ? item.largeSize : item.smallSize);
// good
const itemHeight = (item) => {
const { height, largeSize, smallSize } = item;
return height > 256 ? largeSize : smallSize;
};
|
类 Classes & 构造函数 Constructors
- 9.1 始终用
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;
}
}
|
- 9.2 如果没有特殊说明,类有默认的构造方法。不用特意写一个空的构造函数或只是代表父类的构造函数。 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';
}
}
|
模块 Modules
- 10.1
import放在其他所有语句之前。 eslint:import/first
// bad import foo from 'foo'; foo.init(); import bar from 'bar'; // good import foo from 'foo'; import bar from 'bar'; foo.init(); |
- 10.2 不要导出可变的绑定 eslint:
import/no-mutable-exports
// 尽量减少状态,保证数据的不可变性。虽然在某些场景下可能需要这种技术,但总的来说应该导出常量。
// bad
let foo = 3;
export { foo }
// good
const foo = 3;
export { foo }
|
- 10.3 在只有一个导出的模块里,用
export default更好。 eslint:import/prefer-default-export
// bad
export function foo() {}
// good
export default function foo() {}
|
属性 Properties
- 11.1 访问属性时使用点符号. eslint:
dot-notation
const luke = {
jedi: true,
age: 28,
};
// bad
const isJedi = luke['jedi'];
// good
const isJedi = luke.jedi;
|
- 11.2 获取的属性是变量时用方括号
[]
const luke = {
jedi: true,
age: 28,
};
function getProp(prop) {
return luke[prop];
}
const isJedi = getProp('jedi');
|
- 11.3 做幂运算时用幂操作符
**。 eslint:no-restricted-properties.
// bad const binary = Math.pow(2, 10); // good const binary = 2 ** 10; |
变量 Variables
- 12.1 始终用
const或let声明变量。如果你不想遇到一对变量提升、全局变量的 bug 的话。 eslint:no-undefprefer-const
// bad superPower = new SuperPower(); // good const superPower = new SuperPower(); |
- 12.2 每个变量单独用一个
const或let。 eslint:one-var
// bad
const items = getItems(),
goSportsTeam = true,
dragonball = 'z';
// bad
// (compare to above, and try to spot the mistake)
const items = getItems(),
goSportsTeam = true;
dragonball = 'z';
// good
const items = getItems();
const goSportsTeam = true;
const dragonball = 'z';
|
- 12.3
const放一起,let放一起
// bad
let i, len, dragonball,
items = getItems(),
goSportsTeam = true;
// bad
let i;
const items = getItems();
let dragonball;
const goSportsTeam = true;
let len;
// good
const goSportsTeam = true;
const items = getItems();
let dragonball;
let i;
let length;
|
- 12.4 变量声明放在合理的位置
// bad - unnecessary function call
function checkName(hasName) {
const name = getName();
if (hasName === 'test') {
return false;
}
if (name === 'test') {
this.setName('');
return false;
}
return name;
}
// good
function checkName(hasName) {
if (hasName === 'test') {
return false;
}
// 在需要的时候分配
const name = getName();
if (name === 'test') {
this.setName('');
return false;
}
return name;
}
|
- 12.5 不允许有未使用的变量。 eslint:
no-unused-vars
// bad
var some_unused_var = 42;
// 定义了没有使用
var y = 10;
y = 5;
// 不会将用于修改自身的读取视为已使用
var z = 0;
z = z + 1;
// 参数定义了但未使用
function getX(x, y) {
return x;
}
// good
function getXPlusY(x, y) {
return x + y;
}
var x = 1;
var y = a + 2;
alert(getXPlusY(x, y));
// 'type' 即使没有使用也可以被忽略, 因为这个有一个 rest 取值的属性。
// 这是从对象中抽取一个忽略特殊字段的对象的一种形式
var { type, ...coords } = data;
// 'coords' 现在就是一个没有 'type' 属性的 'data' 对象
|
-
12.6 避免在
=前/后换行。 如果你的语句超出max-len, 那就用()把这个值包起来再换行。 eslintoperator-linebreak.// bad const foo = superLongLongLongLongLongLongLongLongFunctionName(); // bad const foo = 'superLongLongLongLongLongLongLongLongString'; // good const foo = ( superLongLongLongLongLongLongLongLongFunctionName() ); // good const foo = 'superLongLongLongLongLongLongLongLongString';
比较和相等
-
13.1
if等条件语句使用强制ToBoolean抽象方法来评估它们的表达式,并且始终遵循以下简单规则:
-
-
Objects => true
-
Undefined => false
-
Null => false
-
Booleans => the value of the boolean
-
Numbers
- +0, -0, or NaN => false
- 其他 => true
-
Strings
''=> false- 其他 => true
-
- 13.2 布尔值比较可以省略,但是字符串和数字要显示比较
// bad
if (isValid === true) {
// ...
}
// good
if (isValid) {
// ...
}
// bad
if (name) {
// ...
}
// good
if (name !== '') {
// ...
}
// bad
if (collection.length) {
// ...
}
// good
if (collection.length > 0) {
// ...
}
|
- 13.3 三元表达式不应该嵌套,通常是单行表达式。eslint rules:
no-nested-ternary.
// bad const foo = maybe1 > maybe2 ? "bar" : value1 > value2 ? "baz" : null; // better const maybeNull = value1 > value2 ? 'baz' : null; const foo = maybe1 > maybe2 ? 'bar' : maybeNull; // best const maybeNull = value1 > value2 ? 'baz' : null; const foo = maybe1 > maybe2 ? 'bar' : maybeNull; |
- 13.4 避免不需要的三元表达式 eslint rules:
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; |
块 Blocks
- 14.1 用大括号
{}包裹多行代码块。 eslint:nonblock-statement-body-position
// bad
if (test)
return false;
// good
if (test) return false;
// good
if (test) {
return false;
}
// bad
function foo() { return false; }
// good
function bar() {
return false;
}
|
- 14.2
else和if的大括号保持在一行。 eslint:brace-style
// bad
if (test) {
thing1();
thing2();
}
else {
thing3();
}
// good
if (test) {
thing1();
thing2();
} else {
thing3();
}
|
注释 Comments
- 15.1 多行注释用
/** ... */
// bad
// make() returns a new element
// based on the passed in tag name
//
// @param {String} tag
// @return {Element} element
function make(tag) {
// ...
return element;
}
// good
/**
* make() returns a new element
* based on the passed-in tag name
*/
function make(tag) {
// ...
return element;
}
|
- 15.2 单行注释使用
//。将单行注释放在续注释的语句上方。在注释之前放置一个空行,除非它位于代码块的第一行。
// 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;
}
// also good
function getType() {
// set the default type to 'no type'
const type = this.type || 'no type';
return type;
}
|
命名约定
- 16.1 避免用一个字母命名,让你的命名更加语义化。 eslint:
id-length
// bad
function q() {
// ...
}
// good
function query() {
// ...
}
|
- 16.2 不要用前置或后置下划线。 eslint:
no-underscore-dangle
// bad this.__firstName__ = 'Panda'; this.firstName_ = 'Panda'; this._firstName = 'Panda'; // good this.firstName = 'Panda'; |
- 16.3 不要保存
this的引用,使用箭头函数或硬绑定。
// 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);
};
}
|
- 16.4 默认导出(
export default)一个函数时,函数名、文件名统一。
function makeStyleGuide() {
// ...
}
export default makeStyleGuide;
|
- 16.5 简称和首字母缩写应该全部大写或全部小写。
// bad import SmsContainer from './containers/SmsContainer'; // bad const HttpRequests = [ // ... ]; // good import SMSContainer from './containers/SMSContainer'; // good const HTTPRequests = [ // ... ]; // best import TextMessageContainer from './containers/TextMessageContainer'; // best const Requests = [ // ... ]; |
- 16.6 全大写字母定义用来导出的常量
// allowed but does not supply semantic value
export const apiKey = 'SOMEKEY';
// better in most cases
export const API_KEY = 'SOMEKEY';
// ---
// bad - unnecessarily uppercases key while adding no semantic value
export const MAPPING = {
KEY: 'value'
};
// good
export const MAPPING = {
key: 'value'
};
|
2、接口数据处理
-
增强前端代码的健壮性
异常处理
主要指数据类型。这种情况最常出现在读取后台数据的时候,尤其是需要取数组、对象等引用类型时,可能后台已经保证会传给你固定的数据类型,但因为种种原因或异常,可能出现本来应该是一个对象或数组的字段变成了null,这是非常之普遍的,如果前端直接取res.xxx或res.length,就会报错,导致程序阻塞。
一般有两种手法来处理这种情况:一种是判空处理,如(res || []).length,(res || {}).name等;另外一种是全部按理想情况写,但是外层用try{...}catch(){...}包裹,一有异常就抛出去,通过过滤错误来确保try后面的代码仍能正常执行。
数据检验
最常出现在表单输入时,用户可能输入各种各样奇怪的东西,比如有前后空格、负数、小数、很长很长的数,重复的数等等,一般我们可以设置一些规则来限制用户操作,比如设置最大输入长度,不能输入负数、小数等等,还有就是表单提交时对不合理的行为做出提示,阻止其进行下一步操作。
能应对怪异的用户行为
我们很难去规范用户使用系统或浏览页面的行为,尤其是行为顺序,可能用户会完全不按照正常的顺序去做一些操作,比如按照相反的顺序来,或交叉顺序操作系统,或疯狂的点击按钮。这不是普遍的情况,但最好能保证你的程序在这种情况下仍然可用, 一般应对疯狂的用户行为,可以用防抖、节流、最大次数限制来做规范,而对于不按套路操作的用户,要么限制其行为,告诉他“请先选择xxxx,再进行xxxx”,或者在程序方面做好兼容。
注意JS浮点数运算的坑
这是一个历史遗留问题,0.1 + 0.2 !== 0.3是一个大家都知道的梗,所以如果涉及到前端运算的页面,一定要注意浮点数的问题, 常见的手法有用+‘xxx’或parseInt或parseFloat来做字符串转数字,用toFixed来限定小数点的位数等。
-
更安全地访问对象
不要相信接口数据:
// 比如某个api/xxx/list的接口,按照文档的约定
{
code: 0,
msg: "",
data: [
// ... 具体数据
],
};
// 前端代码可能就会写成
const {code, msg, data} = await fetchList()
data.forEach(()=>{})
|
因为我们假设了后台返回的data是一个数组,所以直接使用了data.forEach,如果在联调的时候遗漏了一些异常情况
-
- 预期在没有数据时data会返回
[]空数组,但后台的实现却是不返回data字段 - 后续接口更新,data从数组变成了一个对象,跟前端同步不及时
- 预期在没有数据时data会返回
这些时候,使用data.forEach时就会报错。(Uncaught TypeError: data.forEach is not a function)
所以在这些直接使用后台接口返回值的地方,最好添加类型检测:
Array.isArray(data) && data.forEach(()=>{})
空值合并运算符
由于JavaScript动态特性,我们在查询对象某个属性时如x.y.z,最好检测一下x和y是否存在:let z = x && x.y && x.y.z
经常这么写就显得十分麻烦,可选链操作符 安全访问对象属性就简单得多:var z = a?.y?.z;

浙公网安备 33010602011771号