前端开发规范总结
目录
一、前言
良好的代码具有很好的可读性,后续维护起来也会令人愉悦,还能降低重构的概率,通过最佳实践减少不必要的选择和差异。统一团队的JS语法风格和书写习惯,减少程序出错的概率,其中也包含了ES6的语法规范和最佳实践
二、 HTML开发规范
三、 CSS开发规范
本章节一部分参考MIUI团队的前端开发文档
3.1 文件
采用 UTF-8
编码,在 CSS 代码头部使用:
@charset "utf-8";
注意,必须要定义在 CSS 文件所有字符的前面(包括编码注释),@charset
才会生效。
3.2 属性
- 使用css提供的简写功能
/* 推荐 */
border-top: 0;
font: 100%/1.6 palatino, georgia, serif;
padding: 0 1em 2em;
- 按照首字母大小写组织属性
background: fuchsia;
border: 1px solid;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
color: black;
text-align: center;
text-indent: 2em;
- 每行属性采用分号结尾
/* 推荐 */
.test {
display: block;
height: 100px;
}
- 属性复用
提高代码复用,设计好代码解构,如下:
.container,
.sidebar{
background-color: #ffffff;
border-radius: 0.5rem;
}
3.3 单位
- 值为 0 时不用添加单位
margin: 0;
padding: 0;
- 值在0和1之间不用添加0
font-size: .8em;
- 使用相对单位
使用相对单位,最大程度的支持各种终端设备
尽量避免使用px,当你打算使用它之前,先思考一下相对布局是否会带来更好的兼容性
- 相对单位举例
- vw:基于视窗的宽度计算,1vw 等于视窗宽度的百分之一
- vh:基于视窗的高度计算,1vh 等于视窗高度的百分之一
- vmin:基于vw和vh中的最小值来计算,1vmin 等于最小值的百分之一
- vmax:基于vw和vh中的最大值来计算,1vmax 等于最大值的百分之一
- em:相对于父元素
- rem:相对于根元素
- 单位运算
.box {
height: calc(50vh - 20px); /* 50% 的视窗高度减掉20px */
width: calc(100% / 3); /* 三分之一的父容器宽度 */
background: red;
}
四、JavaScript开发规范
基于ES6,参考Airbnb JavaScript规范
4.1 变量
- 对所有引用使用const,不要使用var (可以确保你无法重新分配引用,以避免出现错误和难以理解的代码) eslint:prefer-const , no-const-assign
- 如果引用是可变动的,使用
let
代替var
,eslint: no-var
- 使用字面量值创建对象 ,eslint: no-new-object
// bad
const a = new Object{}
// good
const a = {}
- 对象具有动态属性名时,使用对象属性名进行创建
function getKey(k) {
return `a key named ${k}`
}
// bad
const obj = {
id: 5,
name: 'San Francisco'
};
obj[getKey('enabled')] = true
// good
const obj = {
id: 5,
name: 'San Francisco',
[getKey('enabled')]: true
};
- 请使用对象方法的简写方式,eslint: object-shorthand
// bad
const item = {
value: 1,
addValue: function (val) {
return item.value + val
}
}
// good
const item = {
value: 1,
addValue (val) {
return item.value + val
}
}
- 请使用对象属性值的简写方式,eslint: object-shorthand
const job = 'FrontEnd'
// bad
const item = {
job: job
}
// good
const item = {
job
}
- 只对非法标识符的属性使用引号,eslint: quote-props
// bad
const bad = {
'foo': 3,
'bar': 4,
'data-blah': 5
}
// good
const good = {
foo: 3,
bar: 4,
'data-blah': 5
}
- 不要直接使用
Object.prototype
的方法, 例如hasOwnProperty
,propertyIsEnumerable
和isPrototypeOf
方法,eslint: no-prototype-builtins
// bad
console.log(object.hasOwnProperty(key))
// good
console.log(Object.prototype.hasOwnProperty.call(object, key))
// best
const has = Object.prototype.hasOwnProperty // cache the lookup once, in module scope.
console.log(has.call(object, key))
/* or */
import has from 'has' // https://www.npmjs.com/package/has
console.log(has(object, key))
4.2 数组
- 请使用字面量值创建数组,eslint: no-array-constructor
// bad
const items = new Array()
// good
const items = []
- 向数组中添加元素,请使用push方法
const items = []
// bad
items[items.length] = 'test'
// good
items.push('test')
- 使用展开运算符 ... 复制数组
// bad
const items = []
const itemsCopy = []
const len = items.length
let i
for (i = 0; i < len; i++) {
itemsCopy[i] = items[i]
}
// good
itemsCopy = [...items]
4.3 解构赋值
- 当需要使用对象的多个属性时,请使用解构赋值,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}`
}
// better
function getFullName ({ firstName, lastName }) {
return `${firstName} ${lastName}`
}
- 当需要使用数组的多个值时,请同样使用解构赋值,eslint: prefer-destructuring
const arr = [1, 2, 3, 4]
// bad
const first = arr[0]
const second = arr[1]
// good
const [first, second] = arr
- 当函数需要回传多个值的时候,请使用对象的解构,而不是数组的解构
// bad
function doSomething () {
return [top, right, bottom, left]
}
// 如果是数组解构,那么在调用时就需要考虑数据的顺序
const [top, xx, xxx, left] = doSomething()
// good
function doSomething () {
return { top, right, bottom, left }
}
// 此时不需要考虑数据的顺序
const { top, left } = doSomething()
4.4 字符串
- 字符串统一使用单引号的格式 ' ', eslint: quotes
- 字符串太长时,不要使用 \ 进行换行,而是使用 + 进行拼接
- 程序化生成字符串时,请使用模板字符串,eslint: prefer-template template-curly-spacing
const test = 'test'
// bad
const str = ['a', 'b', test].join()
// bad
const str = 'a' + 'b' + test
// good
const str = `ab${test}`
- 不要对字符串使用eval(),会导致太多漏洞, eslint: no-eval
- 不要在字符串中使用不必要的转义字符, eslint: no-useless-escape
4.5 函数
- 不要使用Function构造函数创建函数, eslint: no-new-func
- 在函数签名中使用空格,eslint: space-before-function-paren space-before-blocks
const y = function a () {}
- 使用具名函数表达式而非函数声明,eslint: func-style
- 不要在非函数代码块(
if
,while
等)中声明函数,eslint:no-loop-func
// bad
if (isUse) {
function test () {
// do something
}
}
// good
let test
if (isUse) {
test = () => {
// do something
}
}
- 不要更改参数,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
}
4.6 箭头函数
- 当你必须使用函数表达式(传递匿名函数)时,使用箭头函数标记. eslint: prefer-arrow-callback, arrow-spacing
它将创建在
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
})
- 如果函数体只包含一条没有副作用的返回表达式的语句,可以省略花括号并使用隐式的
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
}))
// No implicit return with side effects
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
})
4.7 禁止使用with(){}
4.8 不使用分号
我们遵循 Standard
的规范,不使用分号。
五、框架开发规范
5.1 Vue
本规范提供了一种统一的编码规范来编写 Vue.js 代码。这使得代码具有如下的特性:
- 其它开发者或是团队成员更容易阅读和理解。
- IDEs 更容易理解代码,从而提供高亮、格式化等辅助功能
- 更容易使用现有的工具
- 更容易实现缓存以及代码包的分拆
5.1.1 Vue组件
5.1.1.1 概要
Components Should Be Focused, Independent, Reusable, Small & Testable (FIRST)
采用组件化开发是vue最基本的命题,本章节将总结Vue组件开发的规范,主要关注以下方面
- 基于模块开发
- vue 组件命名
- 组件表达式简单化
- 组件 props 原子化
- 验证组件的 props
- 组件结构化
- 组件事件命名
- 避免
this.$parent
- 谨慎使用
this.$refs
- 使用组件名作为样式作用域空间
- 提供组件 API 文档
- 对组件文件进行代码校验
- 只在需要时创建组件
5.1.1.2 细则
-
基于模块开发
根据面对对象设计的单一职责(SRP)原则,我们的模块应该只做一件事情
每一个 Vue 组件(等同于模块)首先必须专注于解决一个单一的问题,独立的、可复用的、微小的 和 可测试的。
-
vue 组件命名
组件的命名需遵从以下原则:
- 有意义的: 不过于具体,也不过于抽象
- 简短: 2 到 3 个单词
- 具有可读性: 以便于沟通交流
-
组件表达式简单化
避免复杂的行内表达式,可以使用methods或者computed属性来代替
<!-- 推荐 -->
<template>
<h1>
{{ `${year}-${month}` }}
</h1>
</template>
<script type="text/javascript">
export default {
computed: {
month() {
return this.twoDigits((new Date()).getUTCMonth() + 1);
},
year() {
return (new Date()).getUTCFullYear();
}
},
methods: {
twoDigits(num) {
return ('0' + num).slice(-2);
}
},
};
</script>
<!-- 避免 -->
<template>
<h1>
{{ `${(new Date()).getUTCFullYear()}-${('0' + ((new Date()).getUTCMonth()+1)).slice(-2)}` }}
</h1>
</template>
-
组件 props 原子化
虽然 Vue.js 支持传递复杂的 JavaScript 对象通过 props 属性,但是你应该尽可能的使用原始类型的数据。尽量只使用 JavaScript 原始类型(字符串、数字、布尔值)和函数。尽量避免复杂的对象。
原因:
-
使得组件 API 清晰直观。
-
只使用原始类型和函数作为 props 使得组件的 API 更接近于 HTML(5) 原生元素。
-
其它开发者更好的理解每一个 prop 的含义、作用。
-
传递过于复杂的对象使得我们不能够清楚的知道哪些属性或方法被自定义组件使用,这使得代码难以重构和维护。
-
验证组件的 props
编写组件props的时候必须提供以下验证:
- 提供默认值。
- 使用
type
属性校验类型。
- 使用 props 之前先检查该 prop 是否存在。
验证组件 props 可以保证你的组件永远是可用的(防御性编程)。即使其他开发者并未按照你预想的方法使用时也不会出错。
<template>
<input type="range" v-model="value" :max="max" :min="min">
</template>
<script type="text/javascript">
export default {
props: {
max: {
type: Number, // 这里添加了数字类型的校验
default() { return 10; },
},
min: {
type: Number,
default() { return 0; },
},
value: {
type: Number,
default() { return 4; },
},
},
};
</script>
-
组件结构化
- 按首字母排序 properties、data、computed、watches 和 methods 使得这些对象内的属性便于查找。
- 合理组织,使得组件易于阅读。(name; extends; props, data 和 computed; components; watch 和 methods; lifecycle methods 等)。
- 使用
name
属性。借助于 vue devtools 可以让你更方便的测试。
- 合理的 CSS 结构,如 BEM
- 使用单文件 .vue 文件格式来组件代码。
示例:
<template lang="html">
<div class="Ranger__Wrapper">
<!-- ... -->
</div>
</template>
<script type="text/javascript">
export default {
// 不要忘记了 name 属性
name: 'RangeSlider',
// 使用组件 mixins 共享通用功能
mixins: [],
// 组成新的组件
extends: {},
// 组件属性、变量
props: {
bar: {}, // 按字母顺序
foo: {},
fooBar: {},
},
// 变量
data() {},
computed: {},
// 使用其它组件
components: {},
// 方法
watch: {},
methods: {},
// 生命周期函数
beforeCreate() {},
mounted() {},
};
</script>
<style scoped>
.Ranger__Wrapper { /* ... */ }
</style>
-
组件事件命名
- 事件名也使用连字符命名。
- 一个事件的名字对应组件外的一组意义操作,如:upload-success、upload-error 以及 dropzone-upload-success、dropzone-upload-error (如果需要前缀的话)。
- 事件命名应该以动词(如 client-api-load) 或是 名词(如 drive-upload-success)结尾。
-
避免
this.$parent
- Vue.js 支持组件嵌套,并且子组件可访问父组件的上下文。访问组件之外的上下文违反了基于模块开发的原则,影响组件的复用,因此你应该尽量避免使用
this.$parent
。 - 正确的做法:
- Vue.js 支持组件嵌套,并且子组件可访问父组件的上下文。访问组件之外的上下文违反了基于模块开发的原则,影响组件的复用,因此你应该尽量避免使用
- 通过 props 将值传递给子组件。
- 通过 props 传递回调函数给子组件来达到调用父组件方法的目的。
- 通过在子组件触发事件来通知父组件。
-
谨慎使用
this.$refs
在大多数情况下,通过 this.$refs
来访问其它组件的上下文是可以避免的。在使用的的时候你需要注意避免调用了不恰当的组件 API,所以应该尽量避免使用 this.$refs
。
原因:
-
组件必须是保持独立的,如果一个组件的 API 不能够提供所需的功能,那么这个组件在设计、实现上是有问题的。
-
组件的属性和事件必须足够的给大多数的组件使用。
可以做到减少使用refs的良好编码习惯
- 提供良好的组件 API。
- 总是关注于组件本身的目的。
- 拒绝定制代码。如果你在一个通用的组件内部编写特定需求的代码,那么代表这个组件的 API 不够通用,或者你可能需要一个新的组件来应对该需求。
- 检查所有的 props 是否有缺失的,如果有提一个 issue 或是完善这个组件。
- 检查所有的事件。子组件向父组件通信一般是通过事件来实现的,但是大多数的开发者更多的关注于 props 从忽视了这点。
- Props向下传递,事件向上传递! 。以此为目标升级你的组件,提供良好的 API 和 独立性。
- 当遇到 props 和 events 难以实现的功能时,通过
this.$refs
来实现。
- 当需要操作 DOM 无法通过指令来做的时候可使用
this.$ref
而不是JQuery
、document.getElement*
、document.queryElement
。
- 使用组件名作为样式作用域空间
使用组件名作为样式命名的前缀,可基于 BEM 或 OOCSS 范式。同时给 style 标签加上 scoped 属性。加上 scoped 属性编译后会给组件的 class 自动加上唯一的前缀从而避免样式的冲突。
<style scoped>
/* 推荐 */
.MyExample { }
.MyExample li { }
.MyExample__item { }
/* 避免 */
.My-Example { } /* 没有用组件名或模块名限制作用域, 不符合 BEM 规范 */
</style>
-
提供组件 API 文档
为每个组件提供README.md,当你的组件构建完毕,你的组件项目目录应该类似于下:
range-slider/
├── range-slider.vue
├── range-slider.less
└── README.md
一个良好的API文档应该如下:
-
对组件文件进行代码校验
为了校验工具能够校验 *.vue
文件,你需要将代码编写在 <script>
标签中,并使组件表达式简单化,因为校验工具无法理解行内表达式,配置校验工具可以访问全局变量 vue
和组件的 props
。
(1)ESLint
ESLint 需要通过 ESLint HTML 插件来抽取组件中的代码。
通过 .eslintrc
文件来配置 ESlint,这样 IDE 可以更好的理解校验配置项:
{
"extends": "eslint:recommended",
"plugins": ["html"],
"env": {
"browser": true
},
"globals": {
"opts": true,
"vue": true
}
}
运行 ESLint
eslint src/**/*.vue
(2)JSHint
JSHint 可以解析 HTML(使用 --extra-ext
命令参数)和抽取代码(使用 --extract=auto
命令参数)。
通过 .jshintrc
文件来配置 ESlint,这样 IDE 可以更好的理解校验配置项。
{
"browser": true,
"predef": ["opts", "vue"]
}
运行 JSHint
jshint --config modules/.jshintrc --extra-ext=html --extract=auto modules/
注:JSHint 不接受 vue
扩展名的文件,只支持 html
。
-
只在需要时创建组件
(1) Vue.js 是一个基于组件的框架。如果你不知道何时创建组件可能会导致以下问题:
- 如果组件太大, 可能很难重用和维护;
- 如果组件太小,你的项目就会(因为深层次的嵌套而)被淹没,也更难使组件间通信;
(2) 创建组件时应该遵循的规则:
- 首先,尽可能早地尝试构建出诸如模态框、提示框、工具条、菜单、头部等这些明显的(通用型)组件。总之,你知道的这些组件以后一定会在当前页面或者是全局范围内需要。
- 第二,在每一个新的开发项目中,对于一整个页面或者其中的一部分,在进行开发前先尝试思考一下。如果你认为它有一部分应该是一个组件,那么就创建它吧。
- 最后,如果你不确定,那就不要。避免那些“以后可能会有用”的组件污染你的项目。它们可能会永远的只是(静静地)待在那里,这一点也不聪明。注意,一旦你意识到应该这么做,最好是就把它打破,以避免与项目的其他部分构成兼容性和复杂性。
5.1.2 架构
5.1.2.1 概要
站在软件体系结构的角度上规范我们的工程,降低冗余代码,提高模块的复用能力,健壮性.使得软件项目符合软件工程的开发规范
5.1.2.2 细则
- 使用mixins减少重复代码
Mixins 封装可重用的代码,避免了重复。如果两个组件共享有相同的功能,则可以使用 mixin。通过 mixin,你可以专注于单个组件的任务和抽象的通用代码。这有助于更好地维护你的应用程序。
举例:
假设你有一个移动端和桌面端的菜单组件,它们共享一些功能。我们可以抽象出这两个组件的核心功能到一个 mixin 中,例如:
const MenuMixin = {
data () {
return {
language: 'EN'
}
},
methods: {
changeLanguage () {
if (this.language === 'DE') this.$set(this, 'language', 'EN')
if (this.language === 'EN') this.$set(this, 'language', 'DE')
}
}
}
export default MenuMixin
要使用 mixin,只需将其导入到两个组件中(我只展示移动组件)。
<template>
<ul class="mobile">
<li @click="changeLanguage">Change language</li>
</ul>
</template>
<script>
import MenuMixin from './MenuMixin'
export default {
mixins: [MenuMixin]
}
</script>
彩蛋(点个赞吧亲!):