1.1、所有的赋值都用 const
,避免使用 var
。如果你一定要对参数重新赋值,使用 let
,而不是 var
。
因为这个能确保你不会改变你的初始值,重复引用会导致 bug 并且使代码变得难以理解,不这样做会导致全局变量。我们想要避免污染全局命名空间。
// bad
var a = 1;
var b = 2;
// good
const a = 1;
const b = 2;
// bad
var count = 1;
if (true) {
count += 1;
}
// good, use the let.
let count = 1;
if (true) {
count += 1;
}
1.2、为每个变量声明都用一个 const
或 let,
不要使用链式声明变量。把const
和 let
分别放一起。
这种方式很容易去声明新的变量,或者引入一个只有标点的不同的变化
// bad
let i, len, dragonball,
items = getItems(),
goSportsTeam = true;
// bad
let a = b = c = 1;
// 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;
1.3、用属性值缩写。并且将所有缩写放在对象声明的前面。
这样写更简洁,且可读性更高。
const lukeSkywalker = 'Luke Skywalker';
const anakinSkywalker = 'Anakin 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,
};
1.4、不允许有未使用的变量
这样的变量会占用代码中的空间,并可能导致读者混淆。
1.5、对象浅拷贝时,更推荐使用扩展运算符(即 ...
运算符),而不是 Object.assign。获取对象指定的几个属性时,用对象的 rest 解构运算符(即 ...
运算符)更好。
// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 }); // 改了 `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 es6 扩展运算符 ...
const original = { a: 1, b: 2 };
// 浅拷贝
const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }
// rest 解构运算符
const { a, ...noA } = copy; // noA => { b: 2, c: 3 }
1.6、用扩展运算符做数组浅拷贝,类似上面的对象浅拷贝。
// bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i += 1) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
1.7、用 ...
运算符而不是 Array.from 来将一个可迭代的对象转换成数组。
const foo = document.querySelectorAll('.foo');
// good
const nodes = Array.from(foo);
// best
const nodes = [...foo];
1.8、用对象的解构赋值来获取和使用对象某个或多个属性值
解构使您不必为这些属性创建临时引用,并且避免重复引用对象。重复引用对象将造成代码重复、增加阅读次数、提高犯错概率。在一个块级作用域里,解构对象可以在同一个地方给解构字段赋值,而不需要读整个的代码块看它到底用了哪些字段。
// bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
return `${firstName} ${lastName}`;
}
// best
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}
1.9、多个返回值用对象的解构,而不是数组解构。
你可以在后期添加新的属性或者变换变量的顺序而不会破坏原有的引用。
// bad
function processInput(input) {
// 然后就是见证奇迹的时刻
return [left, right, top, bottom];
}
// 调用者需要想一想返回值的顺序
const [left, __, top] = processInput(input);
// good
function processInput(input) {
// oops,奇迹又发生了
return { left, right, top, bottom };
}
// 调用者只需要选择他想用的值就好了
const { left, top } = processInput(input);
1.10、用默认参数语法而不是在函数里对参数重新赋值。把默认参数赋值放在最后
// really bad
function handleThings(opts) {
// 不!我们不该修改 arguments
// 第二:如果 opts 的值为 false, 它会被赋值为 {}
// 虽然你想这么写,但是这个会带来一些微妙的 bug。
opts = opts || {};
// ...
}
// good
function handleThings(opts = {}) {
// ...
}
// bad
function handleThings(opts = {}, name) {
// ...
}
// good
function handleThings(name, opts = {}) {
// ...
}
1.11、不要对参数重新赋值。
为什么?参数重新赋值会导致意外行为,尤其是对
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) {
// ...
}
1.12、当你一定要用函数表达式(在回调函数里)的时候,使用箭头函数
箭头函数中的
this
与定义该函数的上下文中的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;
});
1.13、如果函数体由一个没有副作用的 表达式 语句组成,删除大括号和 return。否则,使用大括号和 return
语句
// good
[1, 2, 3].map((number) => `A string containing the ${number + 1}.`);
// good
[1, 2, 3].map((number, index) => ({
[index]: number,
}));
1.14、用 ===
和 !==
而不是 ==
和 !=
1.15、布尔值要用缩写,而字符串和数字要明确使用比较操作符
// bad
if (isValid === true) {
// ...
}
// good
if (isValid) {
// ...
}
// bad
if (name) {
// ...
}
// good
if (name !== '') {
// ...
}
// bad
if (collection.length) {
// ...
}
// good
if (collection.length > 0) {
// ...
}
1.16、 在 case
和 default
分句里用大括号创建一块包含词法声明的区域
// bad
switch (foo) {
case 1:
let x = 1;
break;
case 2:
const y = 2;
break;
case 3:
function f() {
// ...
}
break;
default:
class C {}
}
// good
switch (foo) {
case 1: {
let x = 1;
break;
}
case 2: {
const y = 2;
break;
}
case 3: {
function f() {
// ...
}
break;
}
case 4:
bar();
break;
default: {
class C {}
}
}
1.17、三元表达式不应该嵌套,通常是单行表达式
// 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;
1.18、避免不必要的三元表达式
// 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;
1.19、用圆括号来组合多种操作符。
// bad
const foo = a && b < 0 || c > 0 || d + 1 === 0;
// bad
const bar = a ** b - 5 % d;
// bad
// 别人会陷入(a || b) && c 的迷惑中
if (a || b && c) {
return d;
}
// bad
const bar = a + b / c * d;
// good
const foo = (a && b < 0) || c > 0 || (d + 1 === 0);
// good
const bar = (a ** b) - (5 % d);
// good
if (a || (b && c)) {
return d;
}
// good
const bar = a + (b / c) * d;
1.20、不要用选择操作符代替控制语句。
// bad
!isRunning && startRunning();
// good
if (!isRunning) {
startRunning();
}
1.21、文件注释
位于文件头部,一般包含概要、作者、版本改动信息以及修改时间等内容
/*
* 简述当前文件功能
*/
1.22、多行注释
// 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;
}
1.23、在你的注释前使用 FIXME
或 TODO
前缀,这有助于其他开发人员快速理解你指出的需要修复的问题, 或者您建议需要实现的问题的解决方案。 这些不同于常规注释,它们是有明确含义的。FIXME:需要修复这个问题
或TODO:需要实现的功能
。
class Calculator extends Abacus {
constructor() {
super();
// FIXME: shouldn't use a global here
total = 0;
}
class Calculator extends Abacus {
constructor() {
super();
// TODO: total should be configurable by an options param
this.total = 0;
}
}
1.24、函数注释
总是保持星号纵向对齐(结束符前留一个空格)
不要在开始符、结束符所在行写注释
尽量使用单行注释
代替多行注释 注释函数时,推荐使用多行注释
/**
* 简述函数功能
* @param {number} a - 参数a
* @param {number} b=1 - 参数b默认值为1
* @returns number
*/
function foo(a, b=1) {
return a + b
}
更多注释细节可访问:JSDoc Guide
1.25、避免一行代码超过100个字符(包含空格)。注意:对于 上面,长字符串不受此规则限制,不应换行。
// bad
const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy;
// bad
$.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.'));
// good
const foo = jsonData
&& jsonData.foo
&& jsonData.foo.bar
&& jsonData.foo.bar.baz
&& jsonData.foo.bar.baz.quux
&& jsonData.foo.bar.baz.quux.xyzzy;
// good
$.ajax({
method: 'POST',
url: 'https://airbnb.com/',
data: { name: 'John' },
})
.done(() => console.log('Congratulations!'))
.fail(() => console.log('You have failed this city.'));
1.26、不要用前置或后置下划线。
// bad
this.__firstName__ = 'Panda';
this.firstName_ = 'Panda';
this._firstName = 'Panda';
// good
this.firstName = 'Panda';
1.27、简称和缩写应该全部大写或全部小写。
// bad
import SmsContainer from './containers/SmsContainer';
// bad
const HttpRequests = [
// ...
];
// good
import SMSContainer from './containers/SMSContainer';
// good
const HTTPRequests = [
// ...
];
// also good
const httpRequests = [
// ...
];
// best
import TextMessageContainer from './containers/TextMessageContainer';
// best
const requests = [
// ...
];
1.28、当你 export 一个结构体/类/单例/函数库/对象 时用大驼峰。
const AirbnbStyleGuide = {
es6: {
}
};
export default AirbnbStyleGuide;
1.29、export default
导出模块A,则这个文件名也叫 A.*
, import
时候的参数也叫 A
。 大小写完全一致。文件命名不要用index.js/vue等,方便搜索。
// file 1 contents
class CheckBox {
// ...
}
export default CheckBox;
// file 2 contents
export default function fortyTwo() { return 42; }
// file 3 contents
export default function insideDirectory() {}
// in some other file
// bad
import CheckBox from './checkBox'; // PascalCase import/export, camelCase filename
import FortyTwo from './FortyTwo'; // PascalCase import/filename, camelCase export
import InsideDirectory from './InsideDirectory'; // PascalCase import/filename, camelCase export
// bad
import CheckBox from './check_box'; // PascalCase import/export, snake_case filename
import forty_two from './forty_two'; // snake_case import/filename, camelCase export
import inside_directory from './inside_directory'; // snake_case import, camelCase export
import index from './inside_directory/index'; // requiring the index file explicitly
import insideDirectory from './insideDirectory/index'; // requiring the index file explicitly
// good
import CheckBox from './CheckBox'; // PascalCase export/import/filename
import fortyTwo from './fortyTwo'; // camelCase export/import/filename
import insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index"
// ^ supports both insideDirectory.js and insideDirectory/index.js
1.30、避免魔法数字
在编程的领域中指莫名其妙出现的数字,其意义需要通过详细的阅读代码才能理解。一般魔法数字都是需要使用枚举变量来替换的。
enum Day {
SUNDAY = 0,
MONDAY = 1,
TUESDAY = 2,
WEDNESDAY = 3,
THURSDAY = 4,
FRIDAY = 5,
SATURDAY = 6
}
Git message 提交规范
好的提交记录,会将每次提交内容的范围,内容,以及涉及到的 bug 都能清晰的展示出来,且格式一致,便于查找找提交记录,对查看代码提交记录或者审核的人更友好,能够更好地了解项目的生命周期以及中间出现的问题。
可以提供更多更有效的历史信息,方便快速预览以及配合cherry-pick快速合并代码
可以用githook钩子来对代码提交坐约束;
module.exports = {
types: [
{ value: 'init', name: 'init: 初始提交' },
{ value: 'feat', name: 'feat: 增加新功能' },
{ value: 'fix', name: 'fix: 修复bug' },
{ value: 'ui', name: 'ui: 更新UI' },
{ value: 'refactor', name: 'refactor: 代码重构' },
{ value: 'release', name: 'release: 发布' },
{ value: 'deploy', name: 'deploy: 部署' },
{ value: 'docs', name: 'docs: 修改文档' },
{ value: 'test', name: 'test: 增删测试' },
{ value: 'chore', name: 'chore: 更改配置文件' },
{ value: 'style', name: 'style: 样式修改不影响逻辑' },
{ value: 'revert', name: 'revert: 版本回退' },
{ value: 'add', name: 'add: 添加依赖' },
{ value: 'del', name: 'del: 删除代码/文件' },
{ value: 'Polishing', name: 'Polishing: 代码打磨(代码格式化,不涉及逻辑调整,使代码更清晰易读等无错修改)' }
]
}
vue风格指南
3.1、组件名应该始终是多个单词的,根组件 App
以及 <transition>
、<component>
之类的 Vue 内置组件除外。
3.2、Prop 定义应该尽量详细。
// bad
// 这样做只有开发原型系统时可以接受
props: ['status']
// good
props: {
status: String
}
3.3、为组件样式设置作用域
对于应用来说,顶级
App
组件和布局组件中的样式可以是全局的,但是其它所有组件都应该是有作用域的。
3.4、单文件组件的文件名应该要么始终是单词大写开头 (PascalCase)
单词大写开头对于代码编辑器的自动补全最为友好
components/
|- MyComponent.vue
3.5、基础组件名,应用特定样式和约定的基础组件 (也就是展示类的、无逻辑的或无状态的组件) 应该全部以一个特定的前缀开头,比如 Base
、App
或 V
。
当你在编辑器中以字母顺序排序时,你的应用的基础组件会全部列在一起,这样更容易识别。
因为组件名应该始终是多个单词,所以这样做可以避免你在包裹简单组件时随意选择前缀 (比如
MyButton
、VueButton
)。
components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue
3.6、和父组件紧密耦合的子组件应该以父组件名作为前缀命名。
如果一个组件只在某个父组件的场景下有意义,这层关系应该体现在其名字上。因为编辑器通常会按字母顺序组织文件,所以这样做可以把相关联的文件排在一起。
components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
3.7、在单文件组件、字符串模板和 JSX 中没有内容的组件应该是自闭合的
<!-- 在单文件组件、字符串模板和 JSX 中 -->
<MyComponent/>
3.8、模板中的组件名大小写,在单文件组件和字符串模板中组件名应该总是 PascalCase,并且不建议使用index来命名,方便查找与搜索。
PascalCase 相比 kebab-case 有一些优势:
编辑器可以在模板里自动补全组件名,因为 PascalCase 同样适用于 JavaScript。
<MyComponent>
视觉上比<my-component>
更能够和单个单词的 HTML 元素区别开来,因为前者的不同之处有两个大写字母,后者只有一个横线。如果你在模板中使用任何非 Vue 的自定义元素,比如一个 Web Component,PascalCase 确保了你的 Vue 组件在视觉上仍然是易识别的。
3.9、Prop 名大小写
在声明 prop 的时候,其命名应该始终使用 camelCase,而在模板和 JSX 中应该始终使用 camelCase。(非官方推荐,方便定位与所搜)
3.10、组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法
复杂表达式会让你的模板变得不那么声明式。我们应该尽量描述应该出现的是什么,而非如何计算那个值。而且计算属性和方法使得代码可以重用。
// bad
{{
fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}}
3.11、指令缩写 (用 :
表示 v-bind:
、用 @
表示 v-on:
和用 #
表示 v-slot:
) 用简写
3.12、组件选项应该有统一的顺序。
模板依赖 (模板内使用的资源)
components
directives
filters
组合 (向选项里合并 property)
extends
mixins
接口 (组件的接口)
inheritAttrs
model
props
/propsData
本地状态 (本地的响应式 property)
data
computed
事件 (通过响应式事件触发的回调)
watch
- 生命周期钩子 (按照它们被调用的顺序)
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
activated
deactivated
beforeDestroy
destroyed
非响应式的 property (不依赖响应系统的实例 property)
methods
渲染 (组件输出的声明式描述)
template
/render
renderError
3.14单文件组件的顶级元素的顺序
单文件组件应该总是让
<script>
、<template>
和<style>
标签的顺序保持一致。且<style>
要放在最后,因为另外两个标签至少要有一个。且script放在开头,方便查看引用。
<!-- ComponentB.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style>
3.15、如果一组 v-if
+ v-else
的元素类型相同,最好使用 key
(比如两个 <div>
元素)。
默认情况下,Vue 会尽可能高效的更新 DOM。这意味着其在相同类型的元素之间切换时,会修补已存在的元素,而不是将旧的元素移除然后在同一位置添加一个新元素。
3.16、隐性的父子组件通信
应该优先通过 prop 和事件进行父子组件之间的通信,而不是
this.$parent
或变更 prop。一个理想的 Vue 应用是 prop 向下传递,事件向上传递的。遵循这一约定会让你的组件更易于理解。然而,在一些边界情况下 prop 的变更或
this.$parent
能够简化两个深度耦合的组件。问题在于,这种做法在很多简单的场景下可能会更方便。不要为了一时方便 (少写代码) 而牺牲数据流向的简洁性 (易于理解)。
3.17、清除定时器或者事件监听
由于项目中有些页面难免会碰到需要定时器或者事件监听。但是在离开当前页面的时候,定时器如果不及时合理地清除,会造成业务逻辑混乱甚至应用卡死的情况,这个时就需要清除定时器事件监听,即在页面卸载(关闭)的生命周期函数里,清除定时器。
beforeDestroy() {
window.removeEventListener('resize', this.resizeFun)
this.clearTimer()
}
3.18、职责单一
任何时候尽量是的一个函数就做一件事情,而不是将各种逻辑全部耦合在一起,提高单个函数的复用性和可读性。
单一性要求一个组件具有高内聚,低耦合的特征,它只负责一件事情,不要耦合一些没必要的逻辑,并且尽量不要和其他组件有过于多的双向交互和互相依赖关系。
3.19、复用性/通用性
在设计组件的时候,一定要考虑组件的复用性或者说是通用性。当组件封装好后,可以在类似的使用场景中直接调用。这要求我们在设计组件的时候,考虑组件功能的通用性,以及考虑组件入参的合理性
3.20、组件的粒度
大多数人的第一反应可能认为拆分的越细越好。但是,组件拆解的过于细致可能导致某些参数从父组件开始一层层向子组件传递,容易漏传,错传,或者其中某层组件忘记判空的时候,单文件组件在大于700行要进行拆分