# 前端规范系列

从零开始制定规范

# 命名规范

1 避免单字母的名字。用你的命名来描述功能(最好使用动词命名)。

function query() {
  // ...
}

2 在命名对象、函数和实例时使用驼峰命名法(camelCase)。

const thisIsMyObject = {};
function thisIsMyFunction() {}

3 只有在命名构造器或者类的时候才用帕斯卡拼命名法。(PascalCase 每个单词的第一个字母都大写)

class User {
  constructor(options) {
    this.name = options.name;
  }
}
const good = new User({
  name: 'yup',
});

4 不要使用前置或者后置下划线。

5 文件名应该和默认导出的名称完全匹配。

class CheckBox {
  // ...
}
export default CheckBox;
export default function fortyTwo() { return 42; }
import CheckBox from './CheckBox'; // PascalCase export/import/filename
import fortyTwo from './fortyTwo'; // camelCase export/import/filename

8 当你导出一个构造器 / 类 / 单例 / 函数库 / 暴露的对象时应该使用帕斯卡命名法。

const AirbnbStyleGuide = {
  es6: {
  },
};
export default AirbnbStyleGuide;

9 缩略词和缩写都必须是全部大写或者全部小写。

import SMSContainer from './containers/SMSContainer';
const HTTPRequests = [
  // ...
];
const httpRequests = [
  // ...
];

# 变量

1 使用 const 或者 let 来定义变量。避免污染全局命名空间。(最好使用名词命名) 把 const 声明的放在一起,把 let 声明的放在一起。. 这在后边如果需要根据前边的赋值变量指定一个变量时很有用。

const superPower = new SuperPower();
const goSportsTeam = true;
let dragonball;
let i;

2 在你需要的使用定义变量,但是要把它们放在一个合理的地方。 let 和 const 是块级作用域而不是函数作用域。

function checkName(hasName) {
  if (hasName === 'test') {
    return false;
  }
  const name = getName();
  if (name === 'test') {
    this.setName('');
    return false;
  }
  return name;
}

3 不要链式变量赋值。 链式变量赋值会创建隐式全局变量。

(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
// 对于 `const` 也一样

# 注释

1 使用 /** ... */ 来进行多行注释。

/**
* make() returns a new element
* based on the passed-in tag name
*/
function make(tag) {
  // ...
  return element;
}

2 使用 // 进行单行注释。 将单行注释放在需要注释的行的上方新行。 在注释之前放一个空行,除非它在块的第一行。

// 注释
function getType() {
  console.log('fetching type...');

  // set the default type to 'no type'
  const type = this.type || 'no type';
  return type;
}

# 空格

1 使用 tabs (空格字符) 设置为 2 个空格。

function baz() {
∙∙let name;
}

2 在主体前放置一个空格。

function test() {
  console.log('test');
}
dog.set('attr', {
  age: '1 year',
  breed: 'Bernese Mountain Dog',
});

3 在控制语句(if, while 等)开始括号之前放置一个空格。 在函数调用和是声明中,在参数列表和函数名之间没有空格。

if (isJedi) {
  fight();
}
function fight() {
  console.log('Swooosh!');
}

4 用空格分离操作符。

const x = y + 5;

5 在块和下一个语句之前留下一空白行。

const arr = [
  function foo() {
  },

  function bar() {
  },
];

return arr;

6 不要在括号内添加空格。

if (foo) {
  console.log(foo);
}

7 不要在中括号中添加空格。

const foo = [1, 2, 3];
console.log(foo[0]);

8 在花括号内添加空格。 const foo = { clark: 'kent' };

9 要求打开的块标志和同一行上的标志拥有一致的间距。此规则还会在同一行关闭的块标记和前边的标记强 制实施一致的间距。

function foo() { return true; }
if (foo) { bar = 0; }

10 逗号之前避免使用空格,逗号之后需要使用空格。

var foo = 1, bar = 2;
var arr = [1, 2];

11 在行的末尾避免使用空格。

# 分号

每一行结束加分号结尾

const luke = {};
const leia = {};
[luke, leia].forEach((jedi) => {
  jedi.father = 'vader';
});

# 方法

1 使用命名的函数表达式代替函数声明。 函数声明是挂起的,这意味着在它在文件中定义之前,很容易引用函数。这会损害可读性和可维护性。 // 从变量引用调用中区分的词汇名称

const short = function longUniqueMoreDescriptiveLexicalFoo() {
  // ...
};

2 Wrap立即调用函数表达式。 立即调用的函数表达式是单个单元 - 包装, 并且拥有括号调用, 在括号内, 清晰的表达式。

(function () {
  console.log('Welcome to the Internet. Please follow me.');
}());

3 切记不要在非功能块中声明函数 (if, while, 等)。 将函数赋值给变量。

4 避免使用默认参数的副作用。 他们很容易混淆。

var b = 1;
count();  // 1
count();  // 2
count(3); // 3
count();  // 3

5 总是把默认参数放在最后。

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

6 函数签名中的间距。 一致性很好,在删除或添加名称时不需要添加或删除空格。

const x = function () {};
const y = function a() {};

7 优先使用扩展运算符 ... 来调用可变参数函数 它更加干净,你不需要提供上下文。

const x = [1, 2, 3, 4, 5];
console.log(...x);
new Date(...[2016, 8, 5]);

# 箭头函数

1 当你必须使用匿名函数时 (当传递内联函数时), 使用箭头函数。 它创建了一个在 this 上下文中执行的函数版本,它通常是你想要的,并且是一个更简洁的语法。 如果你有一个相当复杂的函数,你可以把这个逻辑转移到它自己的命名函数表达式中。

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

2 如果表达式跨越多个行,用括号将其括起来,以获得更好的可读性。 它清楚地显示了函数的起点和终点。

['get', 'post', 'put'].map(httpMethod => (
  Object.prototype.hasOwnProperty.call(
    httpMagicObjectWithAVeryLongName,
    httpMethod,
  )
));

3 如果你的函数接收一个参数,则可以不用括号,省略括号。 否则,为了保证清晰和一致性,需要在参数周围加上括号。 注意:总是使用括号是可以接受的,在这种情况下,我们使用 “always” option 来配置减少视觉上的混乱。

[1, 2, 3].map(x => x * x);
[1, 2, 3].map(number => (
  `A long string with the ${number}. It’s so long that we don’t want it to take up space on the .map line!`
));
[1, 2, 3].map((x) => {
  const y = x + 1;
  return x * y;
});

4 避免箭头函数符号 (=>) 和比较运算符 (<=, >=) 的混淆。

const itemHeight = item => (item.height > 256 ? item.largeSize : item.smallSize);
const itemHeight = (item) => {
  const { height, largeSize, smallSize } = item;
  return height > 256 ? largeSize : smallSize;
};

5 注意带有隐式返回的箭头函数函数体的位置。

(foo) => bar;
(foo) => (bar);
(foo) => (
  bar
)

# 类和构造器

1 尽量使用 class. 避免直接操作 prototype . class 语法更简洁,更容易推理。

class Queue {
  constructor(contents = []) {
    this.queue = [...contents];
  }
  pop() {
    const value = this.queue[0];
    this.queue.splice(0, 1);
    return value;
  }
}

2 使用 extends 来扩展继承。 它是一个内置的方法,可以在不破坏 instanceof 的情况下继承原型功能。

class PeekableQueue extends Queue {
  peek() {
    return this.queue[0];
  }
}

3 方法返回了 this 来供其内部方法调用。

class Jedi {
  jump() {
    this.jumping = true;
    return this;
  }
  setHeight(height) {
    this.height = height;
    return this;
  }
}
const luke = new Jedi();
luke.jump()
.setHeight(20);

4 如果没有指定类,则类具有默认的构造器。 一个空的构造器或是一个代表父类的函数是没有必要的。

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

6 避免定义重复的类成员。 重复的类成员声明将会默认倾向于最后一个 - 具有重复的类成员可以说是一个错误。

class Foo {
  bar() { return 1; }
}
class Foo {
  bar() { return 2; }
}

##模块

1 你可能经常使用模块 (import/export) 在一些非标准模块的系统上。 你也可以在你喜欢的模块系统上相互转换。 模块是未来的趋势,让我们拥抱未来。

import AirbnbStyleGuide from './AirbnbStyleGuide';
export default AirbnbStyleGuide.es6;
import { es6 } from './AirbnbStyleGuide';
export default es6;

2 不要使用通配符导入。 这确定你有一个单独的默认导出。

import AirbnbStyleGuide from './AirbnbStyleGuide';

3 不要直接从导入导出。 虽然写在一行很简洁,但是有一个明确的导入和一个明确的导出能够保证一致性。

// filename es6.js
import { es6 } from './AirbnbStyleGuide';
export default es6;

4 只从一个路径导入所有需要的东西。 从同一个路径导入多个行,使代码更难以维护。

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

5 不要导出可变的引用。 在一般情况下,应该避免发生突变,但是在导出可变引用时及其容易发生突变。虽然在某些特殊情况下,可能需要这样,但是一般情况下只需要导出常量引用。

const foo = 3;
export { foo };

6 在单个导出的模块中,选择默认模块而不是指定的导出。 为了鼓励更多的文件只导出一件东西,这样可读性和可维护性更好。

export default function foo() {}

7 将所有的 imports 语句放在所有非导入语句的上边。 由于所有的 imports 都被提前,保持他们在顶部是为了防止意外发生。

import foo from 'foo';
import bar from 'bar';
foo.init();

8 多行导入应该像多行数组和对象一样缩进。 花括号和其他规范一样,遵循相同的缩进规则,后边的都好一样。

import {
  longNameA,
  longNameB,
  longNameC,
  longNameD,
  longNameE,
} from 'path';

9 在模块导入语句中禁止使用 Webpack 加载器语法。 因为在导入语句中使用 webpack 语法,将代码和模块绑定在一起。应该在 webpack.config.js 中使用加载器语法。

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

##属性

1 访问属性时使用点符号。 const luke = { jedi: true, age: 28, }; const isJedi = luke.jedi;

2 使用变量访问属性时,使用 []表示法。 const luke = { jedi: true, age: 28, }; function getProp(prop) { return luke[prop]; } const isJedi = getProp('jedi');

3 计算指数时,可以使用 ** 运算符。

const binary = 2 ** 10;

# 比较运算符和等号

1 使用 === 和 !== 而不是 == 和 !=。

2 条件语句,例如 if 语句使用 ToBoolean 的抽象方法来计算表达式的结果,并始终遵循以下简单的规则: Objects 的取值为: true Undefined 的取值为: false Null 的取值为: false Booleans 的取值为: 布尔值的取值 Numbers 的取值为:如果为 +0, -0, or NaN 值为 false 否则为 true Strings 的取值为: 如果是一个空字符串 '' 值为 false 否则为 true

if ([0] && []) {
  // true
  // 一个数组(既是是空的)是一个对象,对象的取值为 true
}

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

if (isValid) {
  // ...
}
if (collection.length > 0) {
  // ...
}

4 获取更多信息请查看 Angus Croll 的 Truth Equality and JavaScript 。

5 在 case 和 default 的子句中,如果存在声明 (例如. let, const, function, 和 class),使用大括号来创建块 。

switch (foo) {
  case 1: {
    let x = 1;
    break;
  }
  case 2: {
    const y = 2;
    break;
  }
  case 3: {
    function f() {
      // ...
    }
    break;
  }
}

6 三目表达式不应该嵌套,通常是单行表达式。

// 分离为两个三目表达式
const maybeNull = value1 > value2 ? 'baz' : null;
// better
const foo = maybe1 > maybe2
  ? 'bar'
  : maybeNull;

// best
const foo = maybe1 > maybe2 ? 'bar' : maybeNull;

7 避免不必要的三目表达式。

const foo = a || b;
const bar = !!c;
const baz = !c;

8 使用该混合运算符时,使用括号括起来。 唯一例外的是标准算数运算符 (+, -, *, & /) 因为他们的优先级被广泛理解。 这能提高可读性并且表明开发人员的意图。

const foo = (a && b < 0) || c > 0 || (d + 1 === 0);
const bar = (a ** b) - (5 % d);
if (a || (b && c)) {
  return d;
}
const bar = a + b / c * d;

#

1 当有多行代码块的时候,使用大括号包裹。

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

2 如果你使用的是 if 和 else 的多行代码块,则将 else 语句放在 if 块闭括号同一行的位置。

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

3 如果一个 if 块总是执行一个 return 语句,那么接下来的 else 块就没有必要了。 如果一个包含 return 语句的 else if 块,在一个包含了 return 语句的 if 块之后,那么可以拆成多个 if 块。

function cats() {
  if (x) {
    return x;
  }
  if (y) {
    return y;
  }
}

function dogs(x) {
  if (x) {
    if (z) {
      return y;
    }
  } else {
    return z;
  }
}

# ESlint

# ESlint基础知识

  • ESLint中规则第一个值是错误级别,可以使下面的值之一:
    • "off" or 0 - 关闭规则
    • "warn" or 1 - 将规则视为一个警告(不会影响退出码)
    • "error" or 2 - 将规则视为一个错误 (退出码为1)
  • ESLint启用推进规则
"extends": "eslint:recommended"
  • ESLint自动修复功能
  eslint --fix
  • 常见.eslintrc配置文件解析
module.exports = {

  //此项是用来告诉eslint找当前配置文件不能往父级查找
  root: true,

  //此项是用来指定eslint解析器的,解析器必须符合规则,babel-eslint解析器是对babel解析器的包装使其与ESLint解析
  parser: 'babel-eslint',

  //此项是用来指定javaScript语言类型和风格,sourceType用来指定js导入的方式,默认是script,此处设置为module,指某块导入方式
  parserOptions: {
    // 设置 script(默认) 或 module,如果代码是在ECMASCRIPT中的模块
    sourceType: 'module',
    "ecmaVersion": 6,
    "ecmaFeatures": {
      "jsx": true
    }
  },

  // 此项指定环境的全局变量,下面的配置指定为浏览器环境
  env: {
    "browser": true,
    "node": true,
    "commonjs": true,
    "es6": true,
    "amd": true
  },
  
  // 此项是用来配置标准的js风格,就是说写代码的时候要规范的写,如果你使用vs-code我觉得应该可以避免出错
  extends: 'vue',
  // 此项是用来提供插件的,插件名称省略了eslint-plugin-,下面这个配置是用来规范html的
  plugins: [
    'html',
    "flow-vars", 
    "react"
  ],

  /* 
  下面这些rules是用来设置从插件来的规范代码的规则,使用必须去掉前缀eslint-plugin-
    主要有如下的设置规则,可以设置字符串也可以设置数字,两者效果一致
    "off" -> 0 关闭规则
    "warn" -> 1 开启警告规则
    "error" -> 2 开启错误规则
  */
  rules: {
    // 警告
    "no-extra-parens": 1, // 非必要的括号
    "no-empty": 1, // 块语句中的内容不能为空
    "no-use-before-define": [1, "nofunc"], // 未定义前不能使用
    "no-unused-vars": 1, // 不能有声明后未被使用的变量或参数
    "no-undef": 1, // 不可以 有未定义的变量
    // vue
    "flow-vars/define-flow-type": 1,
    "flow-vars/use-flow-type": 1,

    // react
    "react/jsx-uses-react": 2,
    "react/jsx-uses-vars": 2,

    // 代码风格
    "no-multi-spaces": 1, // 不能用多余的空格
    "key-spacing": [1, {  // 对象字面量中冒号的前后空格
      "beforeColon": false,
      "afterColon": true
    }],
    "block-scoped-var": 2, // 块语句中使用var
    "consistent-return": 2, // return 后面是否允许省略
    "accessor-pairs": 2, // 在对象中使用getter/setter
    "no-return-assign": [2, "always"], // return 语句中不能有赋值表达式
    "no-redeclare": [2, {   // 禁止重复声明变量
      "builtinGlobals": true
    }],
    "space-infix-ops": 2, // 中缀操作符周围要不要有空格
    "curly": 1, // 必须使用 if(){} 中的{}

    // common js
    "no-duplicate-imports": 1
  }
}

# 常用ESlint ruler

    "no-caller": 1,//禁止使用arguments.caller或arguments.callee
    "no-dupe-keys": 2,//在创建对象字面量时不允许键重复 {a:1,a:1}
    "no-empty": 2,//块语句中的内容不能为空
    "no-eq-null": 2,//禁止对null使用==或!=运算符
    "no-eval": 1,//禁止使用eval
    "no-extra-parens": 2,//禁止非必要的括号
    "no-extra-semi": 2,//禁止多余的冒号
    "no-func-assign": 2,//禁止重复的函数声明
    "no-inline-comments": 0,//禁止行内备注
    "no-label-var": 2,//label名不能与var声明的变量名相同
    "no-mixed-spaces-and-tabs": [2, false],//禁止混用tab和空格
    "linebreak-style": [0, "windows"],//换行风格
    "no-multi-spaces": 1,//不能用多余的空格
    "no-multiple-empty-lines": [1, {"max": 2}],//空行最多不能超过2行
    "no-redeclare": 2,//禁止重复声明变量
    "no-return-assign": 1,//return 语句中不能有赋值表达式
    "no-self-compare": 2,//不能比较自身
    "no-sparse-arrays": 2,//禁止稀疏数组, [1,,2]
    "no-trailing-spaces": 1,//一行结束后面不要有空格
    "no-this-before-super": 0,//在调用super()之前不能使用this或super
    "no-undef": 1,//不能有未定义的变量
    "no-underscore-dangle": 1,//标识符不能以_开头或结尾
    "no-unused-vars": [2, {"vars": "all", "args": "after-used"}],//不能有声明后未被使用的变量或参数
    "no-use-before-define": 2,//未定义前不能使用
    "no-var": 0,//禁用var,用let和const代替

    "array-bracket-spacing": [2, "never"],//是否允许非空数组里面有多余的空格
    "arrow-parens": 0,//箭头函数用小括号括起来
    "arrow-spacing": 0,//=>的前/后括号
    "accessor-pairs": 0,//在对象中使用getter/setter
    "camelcase": 2,//强制驼峰法命名
    "consistent-this": [2, "that"],//this别名
    "default-case": 2,//switch语句最后必须有default
    "eqeqeq": 2,//必须使用全等
    "func-style": [0, "declaration"],//函数风格,规定只能使用函数声明/函数表达式
    "indent": [2, 4],//缩进风格

# 目录规范

├── build                      // 构建相关  
├── config                     // 配置相关  
├── src                        // 源代码  
│   ├── api                    // 所有请求  
│   ├── assets                 // 主题 字体等静态资源  
│   ├── components             // 全局公用组件  
│   ├── directive              // 全局指令  
│   ├── filters                // 全局 filter  
│   ├── icons                  // 项目所有 svg icons  
│   ├── lang                   // 国际化 language  
│   ├── mock                   // 项目mock 模拟数据  
│   ├── router                 // 路由  
│   ├── store                  // 全局 store管理  
│   ├── styles                 // 全局样式  
│   ├── utils                  // 全局公用方法  
│   ├── vendor                 // 公用vendor  
│   ├── views                  // views 所有页面  
│   ├── App.vue                // 入口页面  
│   ├── main.js                // 入口文件 加载组件 初始化等  
│   └── permission.js          // 权限管理  
├── static                     // 第三方不打包资源  
│   └── Tinymce                // 富文本  
├── .babelrc                   // babel-loader 配置  
├── .eslintrc.js               // eslint 配置项  
├── .gitignore                 // git 忽略项  
├── .travis.yml                // 自动化CI配置  
├── favicon.ico                // favicon图标  
├── index.html                 // html模板  
└── package.json               // package.json  

# 数据流向规范

数据流向

# Git Flow

采用"上游优先"(upsteam first)原则,即只存在一个主分支master,它是所有其他分支的"上游"。只有上游分支采纳的代码变化,才能应用到其他分支。

  • 分支约定

    • 远程分支
      • 主分支:master分支
      • 预发分支:pre-production
      • 生产分支: production
    • 本地分支
      • 每个开发者基于master拉取一个分支,合并和同步代码
  • 合并流向

    • 功能发布:功能开发完毕,经过自测和测试合并到master分支,通过预发环境测试,合并到生产环境分支
    • bug修复:生产环境出现bug,新建功能分支,先合并到master,再cherry-pick到pre-production,最后合并到master分支
  • 版本发布
    每一个稳定版本,都要从master分支拉出一个分支,比如1.0.0、1.1.1等等。

上次更新: 2020/4/19下午4:53:50