前端开发规范

1、代码规范

类型

  • 1.1 基本类型
    • 基本类型赋值时,应该直接使用类型的值 
      • string
      • number
      • boolean
      • null
      • undefined
      • symbol
const foo = 1;
let bar = foo;

bar = 9;

console.log(foo, bar); // => 1,9
    • Symbols 不能被完全 polyfill, 所以不应该在目标浏览器/环境不支持它们的情况下使用它们。
    • 复杂类型
      • object
      • array
      • function
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-constno-const-assign
    • 尽量确保你的代码中的状态是可控范围内的,重复引用会出现难以理解的 bug 和代码。
// bad
var a = 1;
var b = 2;
// good
const a = 1;
const b = 2;

 

  • 2.2 如果你一定要对参数重新赋值,那就用let,而不是var. eslint: no-var
    • let是块级作用域,var是函数级作用域,同样是为了减少代码的不可控,减少 “意外”
// bad
var count = 1;
if (true) {
  count += 1;
}
// good, use the let.
let count = 1;
if (true) {
  count += 1;
}

 

  • 2.3 letconst都是块级作用域
 // const 和 let 都只存在于它定义的那个块级作用域
{
  let a = 1;
  const b = 1;
}
console.log(a); // ReferenceError,引用错误
console.log(b); // ReferenceError,引用错误


对象 Objects

 // bad
const item = new Object();
// good
const item = {};

 

 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

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

 

// 模板字符串更具可读性、语法简洁、字符串插入参数。
// 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

反斜线可读性差,只在必要时使用

// 反斜线可读性差,只在必要时使用
// 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) {
  // ...
}

 

 // 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 = {}) {
  // ...
}

 

// bad
const f = function(){};
const g = function (){};
const h = function() {};
// good
const x = function () {};
const y = function a() {};

 

// 特别注意引用类型的操作,保证数据的不可变性
// bad
function f1(obj) {
  obj.key = 1;
};
// good
function f2(obj) {
  const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
};

 

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

// 它创建了一个在上下文中执行的函数,这通常是您想要的,并且是一种更简洁的语法。
// 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-parensarrow-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();

 

// 尽量减少状态,保证数据的不可变性。虽然在某些场景下可能需要这种技术,但总的来说应该导出常量。
// bad
let foo = 3;
export { foo }
// good
const foo = 3;
export { foo }

 

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

 

 

 

 

 // bad
const binary = Math.pow(2, 10);
// good
const binary = 2 ** 10;

 

 

 

变量 Variables

  • 12.1 始终用 const 或 let 声明变量。如果你不想遇到一对变量提升、全局变量的 bug 的话。 eslint: no-undef prefer-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;
}

 

 

 

 // 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, 那就用()把这个值包起来再换行。 eslint operator-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;

 

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

// 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() {
  // ...
}

 

// 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.xxxres.length,就会报错,导致程序阻塞。

 一般有两种手法来处理这种情况:一种是判空处理,如(res || []).length(res || {}).name等;另外一种是全部按理想情况写,但是外层用try{...}catch(){...}包裹,一有异常就抛出去,通过过滤错误来确保try后面的代码仍能正常执行。

数据检验

最常出现在表单输入时,用户可能输入各种各样奇怪的东西,比如有前后空格、负数、小数、很长很长的数,重复的数等等,一般我们可以设置一些规则来限制用户操作,比如设置最大输入长度,不能输入负数、小数等等,还有就是表单提交时对不合理的行为做出提示,阻止其进行下一步操作。

能应对怪异的用户行为

我们很难去规范用户使用系统或浏览页面的行为,尤其是行为顺序,可能用户会完全不按照正常的顺序去做一些操作,比如按照相反的顺序来,或交叉顺序操作系统,或疯狂的点击按钮。这不是普遍的情况,但最好能保证你的程序在这种情况下仍然可用, 一般应对疯狂的用户行为,可以用防抖、节流、最大次数限制来做规范,而对于不按套路操作的用户,要么限制其行为,告诉他“请先选择xxxx,再进行xxxx”,或者在程序方面做好兼容。

注意JS浮点数运算的坑

这是一个历史遗留问题,0.1 + 0.2 !== 0.3是一个大家都知道的梗,所以如果涉及到前端运算的页面,一定要注意浮点数的问题, 常见的手法有用+‘xxx’parseIntparseFloat来做字符串转数字,用toFixed来限定小数点的位数等。

  • 更安全地访问对象

不要相信接口数据:

// 比如某个api/xxx/list的接口,按照文档的约定
{
    code: 0,
    msg: "",
    data: [
     // ... 具体数据
    ],
};
// 前端代码可能就会写成
const {code, msg, data} = await fetchList()
data.forEach(()=>{})

因为我们假设了后台返回的data是一个数组,所以直接使用了data.forEach,如果在联调的时候遗漏了一些异常情况

    • 预期在没有数据时data会返回[]空数组,但后台的实现却是不返回data字段
    • 后续接口更新,data从数组变成了一个对象,跟前端同步不及时

这些时候,使用data.forEach时就会报错。(Uncaught TypeError: data.forEach is not a function)

 所以在这些直接使用后台接口返回值的地方,最好添加类型检测:

 Array.isArray(data) && data.forEach(()=>{})

 

空值合并运算符

由于JavaScript动态特性,我们在查询对象某个属性时如x.y.z,最好检测一下xy是否存在:let z = x && x.y && x.y.z

 经常这么写就显得十分麻烦,可选链操作符 安全访问对象属性就简单得多:var z = a?.y?.z;

posted @ 2022-10-13 16:21  郭磊—lily  阅读(113)  评论(0编辑  收藏  举报