前言
与独立开发不同,在多人协作的开发团队中,任何人都有可能参与到你或他人编写的模块中去,倘若每个人的编程风格都大不一致,那么将给其他人带来阅读、协作与维护造成不小的困扰,因此统一团队的编程规范是必要且关键的。
除此之外,养成自己良好灵活的编程风格习惯,对于团队变化带来的规范改动,也将是如鱼得水而非束手束脚。
保持个人规范仅仅是良好的习惯,但建议以社区大规范为主,灵活调整具体细则配合ESLint做代码检查,最终服务于团队项目规范
#
对于 ESLint Rules 及相关代码风格,以 semi
规则为例,结尾是否添加分号一直是大家喜欢诟病的规则。
一般来讲,添加分号是为了规避以往构建工具或工具库带来的问题。现如今环境下,添加分号的必要性显得没那么重要,以 尤大的讨论 及 vue 源码为例。
对于习惯与项目遗留问题,不建议强行进行改动。而更应该注重于实际开发团队而选择对应的规则,良好的编程规范是个人编程素养,而具体规范落实,则应该是团队协作的基本。舍小我服务于团队才是正确的选择,而无需进行无意义的争论。
HTML
在HTML5的标准下,以及网站SEO优化、无障碍阅读需求,对于HTML标签的书写应当是追求标签语义化
CSS
参考文献:
百度前端规范、
css命名规范-BEM
JavaScript
作为前端工程师最重要的语言工具(包括Node.js),js规范显得尤为重要,也是不同水平的编程者风格差异最大的一部分。
不论你使用那种js语法框架,基本js编程风格都是一个合格的前端开发者应该掌握的。
命名规范
- 基本的变量与函数命名 ```javascript // 变量及函数应始终小驼峰命名为主 const nickname = ‘nickname’; let userNum = 4;
function utils() { / to do / }; function formatText() { / to do / };
2. 常量命名
```javascript
// 常量应使用全部大写字母命名,并以下划线分割单词
// 建议:模块/功能_功能/作用_变量
const PROMISE_STATUS_PENDING = 'pending';
const PROMISE_STATUS_RESOLVE = 'fulfilled';
const PROMISE_STATUS_REJECT = 'rejected';
// ESM 是值的引用, 此处使用Object.freeze()做枚举避免外部不恰当的改动
const PROMISE_STATUS = Object.freeze({
PENDING:'pending',
RESOLVE:'fulfilled',
REJECT:'rejected'
})
- 类 & 非ts下的私有方法/变量命名
// 类名必须为大驼峰命名
class Person {
// 类中或模块化的私有方法/属性应加上下划线前缀
constructor() { this._name = '示例' }
_getName() {
return this._name;
}
// 公有方法/属性仍遵守基本规范
setName(name) {
this._name = name;
}
}
引入 TypeScript
在合适的项目/工具库中引入TypeScript可以降低后期维护成本
class Person {
private name: string;
constructor() { this._name = '示例' }
private getName(): string {
return this._name;
}
public setName(name: string): void {
this._name = name;
}
}
// 常量枚举
enum PROMISE_STATUS = {
PENDING = 'pending',
RESOLVE = 'fulfilled',
REJECT = 'rejected'
}
- 更好的命名
除了基本的命名风格,单词的书写也应更贴近单词
对于boolean类型的属性与方法,应根据场景合理使用is、has等表示判断意义的单词作为前缀
// good
const nickname = '命名';
let isUser = true;
let hadLogin = true;
function checkIsUser() { return true };
function isUser() { return false };
// bad
const nickName = '命名';
let ifUser = false;
let login = false;
function ifUser() { return false }
语法规范
- 一般情况下使用ES6新语法 ```javascript // good const demo = ‘示例’; let num = 1;
const { a, b: 1 } = obj;
// bad var demo = ‘示例’; var num = 1;
var a = obj.a; var b = obj.b || 1;
2. 单行 if 语句也应保留括号, switch 语句应处理default情况
```javascript
// bad
if (condition) // to do
// good
if (condition) {
// to do
}
switch (key) {
case value:
// to do
break;
default:
// to do
}
合理使用try…catch处理error,避免在catch中对err进行重新赋值
// 对于接口报错应与后端配合进行统一处理,且避免在catch中对err进行重新赋值
try {
throw new Error('报错了');
} catch (err) {
// bad
// err.errMsg = 'err';
throw(err) // // or toast(err.errMsg)
}
在合适的地方销毁定时器/监听器 ```javascript // 此处使用vuejs示例 mounted() { this.timer = setInterval(() => { / to do / }, interval); } destroyed() { this.timer && clearInterval(this.timer) }
// 或者 mounted() { this.timer = setInterval(() => { / to do / }, interval); this.$once(‘hook:destroyed’, () => { this.timer && clearInterval(this.timer) }) }
5. 根据实际场景合理使用 for 迭代器
```javascript
const list = [1, 2, 3]
// good
list.forEach(item => {
console.log(item);
})
// map虽然与forEach相似,但map还有生成并返回新数组的特性
const newList = map(item => item * 2); // [2, 4, 6]
// 而array.reduce()则要注意对数组进行判空处理
if (Array.isArray(list) && list.length > 0) {
return list.reduce((total, val) => total + val); // 6
}
// bad
list.map(item => {
console.log(item);
})
- 合理使用async…await配合try…catch 代替 Promise.then() 避免回调地狱 ```javascript function onErrorHandle(err) { throw(err) // // or toast(err.errMsg) }
// good async function _ajax() { try { const res1 = await new Promise1(); const res2 = await new Promise2(); // to do … } catch (err) { onErrorHandle(err) } } // OR async function _ajax() { const res1 = await new Promise1().catch(err1 => …); const res2 = await new Promise2().catch(err2 => …); // to do … }
// bad function _ajax() { new Promise1().then((res1) => { new Promise2().then(res2 => { res2() }).catch(err2 => { onErrorHandle(err) }) }).catch(err1 => { onErrorHandle(err) }) }
<a name="kqXwo"></a>
## ESLint
<a name="gcmff"></a>
### .eslintrc.js
```javascript
// 个人 ESLint 配置示例
module.exports = {
// 指定文件为eslint配置根目录,而不再查找使用父级的eslint配置
root: true,
// 指定脚本的运行环境
env: {
brower: true,
node: true
},
// 按需引入所需eslint插件
extends: ['plugin:vue/essential', '@vue/prettier'],
parserOptions: {
/**
* 自定义语法规则或解析配置
*/
},
rules: {
// 规避与prettier的规则冲突
'prettier/prettier': 'off',
// es6规范
'no-unused-vars': 'error',
'prefer-const': ['error', { destructuring: 'all', ignoreReadBeforeAssign: true }],
'no-undef': 'error',
'no-debugger': 'error',
'no-return-assign': 'off',
// 禁止重复声明变量
'no-redeclare': ['error', { builtinGlobals: true }],
// == 运算符可能会带来一些非预期问题
eqeqeq: 'error',
// 省略单参数的箭头函数的括号
'arrow-parens': ['warn', 'as-needed'],
// 减少不必要的空白或填充
'no-trailing-spaces': 'off',
'padded-blocks': 'off',
// 避免直接操作原型对象, 改用call/apply指定调用对象
'no-prototype-builtins': 'error',
// 在case中声明
'no-case-declarations': 'off',
// 虽然编译时会自动识别添加分号, 但良好的习惯可以减少;开头的脚本文件之类的产生
semi: ['error', 'always'],
// if 语句的括号有有必要的
curly: ['error', 'all'],
// 优先使用单引号
quotes: ['error', 'single', { avoidEscape: true }],
// 命名风格应始终以大小驼峰为主
camelcase: [
'error',
{
properties: 'never' // 对于对象中的属性根据实际情况决定
}
],
// 最后的属性/方法没必要尾随逗号
'comma-dangle': ['error'],
// 方法名不应该有空格或换行,避免误解
'func-call-spacing': 'error',
// others
'no-return-await': 'off',
'no-async-promise-executor': 'warn',
/**
* vue相关的eslint规则
* https://eslint.vuejs.org/rules/
*/
'vue/max-attributes-per-line': 'off',
'vue/return-in-computed-property': 'error',
'vue/html-self-closing': [
'error',
{
html: {
void: 'any',
normal: 'never',
component: 'any'
},
svg: 'always',
math: 'always'
}
]
}
};
按需禁用ESLint
官方文档 ```bash
ESLint禁用
禁用下一行
// eslint-disable-next-line
禁用当前行
/ eslint-disable-line / // eslint-disable-line
禁用整个文件
/ eslint-disable /
只禁用指定规则 只需在上述命令后加上 eslint-rule
/ eslint-disable no-alert /
添加注释 只需在上述命令后加上—-comment
/ eslint-disable no-alert —-demo /
在项目根目录添加.eslintignore文件并声明禁用文件
/bin/bash
echo “*.json” >> .eslintignore
<a name="DdYGd"></a>
### 在vscode中配置
- 进入插件界面搜索并安装插件`Prettier`、`Eslint`<br />vscode --> Perference --> Extensions<br />快捷键:[mac]`command + shift + x` / [windows]`ctrl + shift + x`
- vscode --> Perference --> Setting --> 右上角Open Setting(json) 添加以下配置
```json
// 保存时自动格式化
"editor.formatOnSave": true
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
// 如果需要指明npm工具
"eslint.packageManager": "yarn",
// 如果需要指明检验文件类型
"eslint.validate": [
"ts",
"javascript",
"javascriptreact",
"vue",
"typescript",
"typescriptreact",
"typescript"
],
// 如果需要将eslint作为格式化工具
"eslint.format.enable": true
- 为方便修改覆盖Prettier默认格式化规则,可以在项目根目录配置.prettierrc文件
{
"printWidth": 120,
"singleQuote": true,
"trailingComma": "none",
"semi": true
}
- 于项目根目录创建.eslintrc.js配置文件
目录结构参考
合理的目录结构使项目模块更为清晰,便于维护
# 示例为vue2.x项目src文件夹
├── api 外部数据的请求以及转换
├── assets 静态文件或者全局css样式
├── components 全局组件
├── core 复杂逻辑或者核心代码
├── docs 文档
├── lib 工具类, utils/tools
├── views 页面组件
├──── page.vue
├── router 路由
├──── index.js
├── store 数据流、复杂业务状态
├──── modules store 模块化 + namespaced
├──── index.js
├── App.vue
├── main.js app入口