开发团队当前正在全面切换到ES6 + Vue/React + Webpack
这套技术栈,小伙伴们在开发过程中对编码规范以及
ES6 使用约定方面存在诸多困惑,所以参考了 Github 以及互联网企业 UED
的相关技术文档,结合团队之前积累的大量开发约定和范式,整理了这篇
code style 并且开源出来,计划在 Team
小伙伴们熟练掌握以后,逐步在项目中推广使用 ESLint 校验代码风格。
本文撰写过程中参考了爱彼迎的 《Airbnb JavaScript Style
Guide》,以及网易 NEC 团队的《CSS
基础库命名约定》等开源规范,结合笔者之前撰写的 《HTML5
语义化标签概览》一文,总体上分为总体原则
、JavaScript/ES6
、CSS/SCSS
、HTML
四个部分,分门别类的从命名约定、选择器使用、DOM
结构组织等纬度进行阐述,以帮助小组成员形成良好的编码习惯。
总体原则
- 驼峰命名(camelCase):用于定义
JavaScript
基本数据类型
、函数方法
。
- 帕斯卡命名(PascalCase):用于 JavaScript
声明
Class类
、Object对象
、Array数组
引用数据类型。
- 短横线命名(kebab-case):用于自定义
HTML视图
、SCSS样式
、Assets资源
,即样式与视图相关的自定义元素都采用该方式命名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <div> <main id="app"> <section class="article-content"> <my-paragraph>kebab-case</my-paragraph> </section> </main> <template> <style> #app { background: url(../assets/banner-logo.png); // kebab-case } .article-content { font-size: 18px; // kebab-case } </style>
<script> let currentDate = "Tue Oct 10 2017 17:52:04 GMT+0800 (CST)";
const changeDate = function () { console.log(new Date()); };
const YourName = { name: "Hank", }; </script> </template> </div>
|
所有代码缩进必须使用 2
个空格,并优先使用单引号'
,除非字符串嵌套需要,否则禁止单-双引号混用。
JavaScript & ES6
本规范基于爱彼迎的 Airbnb JavaScript Style
Guide 以及开发小组当前编码习惯进行制定,适用于使用 Babel 提供 ES6
预编译环境的场景。
命名原则
代码块的花括号{
、流程控制语句的小括号(
前必须放置1
个空格,且每个函数、代码块之前通过换行进行分隔。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Hank { constrocter() { (this.height = "182cm"), (this.weight = "75kg"); }
toString() { if (this.height && this.weight) { console.info("toString"); } } }
|
不要在函数参数列表的前后添加任何空格,但是可以使用1
个空格将 JavaScript
运算符分隔开,并且在每个代码文件末尾保留1 个空行。
1 2 3 4 5
| ((window) => { var self = window; })(window);
|
使用美元符$
作为存储jQuery
对象变量名称的前缀。
1 2 3
| const menu = $("#menu");
const $menu = $("#menu");
|
使用下划线_
作为代码中私有变量的前缀,避免其它小伙伴的操作对该变量造成污染。
1 2 3 4 5 6
| function traverse(array) { let _index = 0; for (_index; array.length < _index; _index++) { console.log(array[_index]); } }
|
严禁在项目中使用单个字母和拼音命名的变量和函数名称。
数据类型
1 2 3 4 5 6
| let myBoolean = true; let myNumber = 32; let myString = "this is a string";
const array = [0, 1, 2, 3, 4, 5]; const object = { a: "a", b: "b", c: "c" };
|
let 和 const 都具有代码块级的作用域,书写时注意将两者进行分组。
对象
使用字面量语法创建对象,而不要使用new
关键字。
1 2 3
| const myObject = new Object();
const myObject = {};
|
对象当中的方法使用简写函数、简写属性进行定义。
1 2 3 4 5 6 7 8 9 10 11
| let username = "admin"; let password = "admin";
vm.login = { username, password, auth(username, password) { }, };
|
当对象中同时存在简写属性和普通属性时,需要将简写属性写到单独的组。
1 2 3 4 5 6 7 8 9 10 11
| vm.group = { username, password, isAuthorized, age: "20", height: 182, weight: 76, homeland: "CHINA", };
|
使用.
运算符访问对象属性,或者使用[]
通过变量访问属性。
1 2 3 4 5 6 7
| const user = { height: 182, weight: 75, };
console.info(user.height); console.info(user["weight"]);
|
ES6
中新出现的class
关键字,实质是构造函数创建对象的一种糖衣语法,目的是更加容易的向对象添加原型方法。因此,建议尽可能使用class
创建类及原型方法,避免在构造函数上使用prototype
关键字。
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Point { constructor(x, y) { this.x = x; this.y = y; }
toString() { return this.x + this.y; } }
|
通过在class
类方法上返回this
对象,可以在该类的实例化对象上进行链式方法调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Hank { constructor(name, age) { this.age = age; this.name = name; }
printName() { console.info(this.name); return this; } printAge() { console.info(this.age); return this; } }
const hank = new Hank("uinika", 18); hank.printName().printAge();
|
代码中尽可能使用extends
实现继承,因为extends
是
ES6
内建的**原型继承方法,不会对instanceof()
的返回结果形成破坏。
1 2 3 4 5 6 7 8 9 10 11
| class Uinika extends Hank { constructor(height, weight) { super("uinika", 18); this.height = height; this.weight = weight; }
print() { console.log(this); } }
|
数组
- 使用字面量语法创建数组,而不要使用
new Array()
关键字。
1 2 3
| const myArray = new Array();
const myArray = [];
|
- 添加数组元素时使用
Arrary.push()
,而不要使用索引赋值。
1 2 3 4 5
| const myArray = [];
myArray[myArray.length] = "uinika";
myArray.push("uinika");
|
- 使用 ES6
的扩展运算符(spread)
...
复制数组。
1 2 3 4 5 6 7 8 9 10
| const target = ["A", "B", "C", "D"]; const copy = [];
for (let index = 0; index < target.length; index++) { copy[i] = target[i]; }
const copy = [...target];
|
- 使用 ES6
提供的
Array.from()
方法将类数组对象(同时具备索引和长度)转换为数组。
1 2 3 4 5 6 7 8
| const myObject = { "0": "A", "1": "B", "2": "C", length: 3, };
let myArray = Array.from(myObject);
|
字符串
为避免频繁按下shift+'
组合键,请在代码中尽可能使用单引号'
声明字符串。
1 2
| const name = "Hank"; const name = "Hank";
|
字符串过长时,必须通过+
或者\
运算符进行换行和缩进处理,让代码更加美观易读。
1 2 3 4 5 6 7 8 9 10
| let bad = "hank is first name and zheng is last name";
let good = "hank is first name \ and zheng is last name";
let best = "hank is first name" + "and zheng is last name";
|
拼接字符串HTML
模板时,请使用模板字符串${}
,而非字符串连接符+
。
1 2 3 4 5 6 7 8 9
| function sayHi(name) { return "How are you, " + name + "?"; }
function sayHi(name) { return `How are you, ${name}?`; }
|
函数
使用函数声明(function
declaration)代替函数表达式(function
expression),因为前者可以进行函数提升(function
hoisting)。
1 2 3 4 5
| const bad = function () {};
function good() {}
|
需要使用函数表达式、匿名函数的场景下,请直接使用箭头函数符号=>
,这样可以在箭头函数内部创建新的this
作用域。
1 2 3 4 5 6 7
| (name, password) => { console.info(name, password); };
(name) => name + "zheng";
|
使用箭头函数来书写立即调用的函数表达式(IIFE,Immediately
Invoked Function Expression)。
1 2 3
| (() => { console.info("Hello Hank!"); })();
|
布尔运算
尽可能使用===
和!==
去同时比较值与数据类型,而非通过==
和!=
仅比较值。
1 2 3
| if (variable1 === "" && variable2 !== 0) { console.info("just a test!"); }
|
各数据类型的布尔运算结果如下,使用if()
等逻辑判断语句时需要注意。
- 对象类型的
object
和array
都计算为true
,无论其是否为空对象{}
或者空数组[]
。
- 空字符串类型
''
计算为false
,但非空字符串'string'
计算为true
。
- 数字类型
NaN
和0
计算为false
。
undefined
和null
都是false
。
利用 JavaScript
逻辑判断语句的强制数据类型转换特性,可以更加简洁的书写布尔判断。
1 2 3 4 5 6 7 8 9
| if (name !== "") { } if (name) { }
if (collection.length > 0) { } if (collection.length) { }
|
如果通过if...else...
语句使用多行代码块,则else
放在if
代码块关闭括号}
同一行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| if (test) { ... } else { ... }
if (test) { ... } else { ... }
|
注释
- 使用
/** */
作为多行注释,用来标注全局功能模块的名称、类型、参数、返回值、描述等信息。
- 使用
/* */
作为单行注释,标注局部代码块的参数、返回值、描述信息。
1 2 3 4 5 6 7 8
|
function trim(input) { if (typeof input === "string" && input) input.replace(/\s/g, ""); }
|
1 2 3
| function trustHtml($sce) { return $sce.trustAsHtml(val); }
|
使用// TODO:
格式注释描述问题本身,使用// FIXME:
格式注释描述问题解决方式。
1 2 3 4 5
| () => { issue(); handle(); return this; };
|
解构赋值
通过解构赋值存取多属性对象,可以减少临时变量声明的数量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function getFullName1(user) { const name = user.name; const password = user.password; return `${name} ${name}`; } function getFullName2(user) { const { name, password } = user; return `${name} ${password}`; } function getFullName3({ name, password }) { return `${name} ${password}`; }
const array = [1, 2, 3, 4]; const array1 = array[0]; const array2 = array[1]; const [array1, array2] = array;
|
函数返回值时,直接返回对象{}
而非数组[]
,避免因为数组索引改变导致不能正确读取相应位置数据。
CSS & SCSS
本规范借鉴网易 NEC 的 CSS
基础库命名约定,结合当前开发团队多种 CSS 样式库混用的场景,在避免
CSS 选择器冲突的原则下制订,适用于通过 Sass 预处理器转译 CSS
的场景。
命名原则
reset.scss
:消除默认样式和浏览器差异,并设置部分标签的初始样式,例如<html>
和<body>
的宽高度的100%
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @import "./base"; @import "./color";
.reset { margin: 0; padding: 0; border: none; outline: none; width: 100%; height: 100%; }
html { @extend .reset; body { @extend .reset; font-size: 16px; font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif; #app { @extend .reset; .router { @extend .reset; } } } }
|
color.scss
:项目中页面的所有取色必须来自这个颜色变量列表,色表内部的变量以color-颜色-深度
格式命名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| $color-primary: $color-blue; $color-success: #13ce66; $color-warning: #f7ba2a; $color-danger: #ff4949;
$color-blue: #20a0ff; $color-blue-light: #58b7ff; $color-blue-dark: #1d8ce0; $color-pink: #ff0097; $color-cyan: #4eb3b9; $color-black: #1f2d3d; $color-black-light: #324057; $color-black-light-extra: #475669; $color-silver: #8492a6; $color-silver-light: #99a9bf; $color-silver-light-extra: #c0ccda; $color-gray: #d3dce6; $color-gray-light: #e5e9f2; $color-gray-light-extra: #eff2f7; $color-white: #ffffff; $color-white-dark: #f9fafc;
|
base.scss
:基础的公用快捷样式,通过 HTML 元素的
class 属性直接使用。
skin.scss
: 全局 UI
插件的样式补丁,如果存在多种皮肤,则以skin-xx.scss
方式进行命名。
grid.scss
:自定义的 CSS 栅格系统,直接通过 HTML
元素及 class 属性使用。
每个 Vue 或者 React
根级组件的样式都独立到单独模块书写,禁止在顶层 ID
选择器之外再定义其它 CSS
样式,避免对全局样式形成污染。私有组件顶层 CSS
选择器使用id
属性定义,公用组件的顶层选择器使用class
属性定义,。
1 2 3 4
| @import "../common/styles/base.scss"; #ID { }
|
为了避免 SASS
中使用多行注释时,将注释内容打包至最终产品代码,因此非公用模块的注释一律使用单行注释//
。
布局选择器
文档 |
doc |
doc |
头部 |
head |
hd |
主体 |
body |
bd |
尾部 |
foot |
ft |
主栏 |
main |
mn |
主栏子容器 |
main-container |
mcc |
侧栏 |
side |
sd |
侧栏子容器 |
side-container |
sdc |
盒容器 |
wrap/box |
wrap/box |
模块/组件选择器
导航 |
nav |
nav |
子导航 |
subnav |
snav |
面包屑 |
crumb |
crm |
菜单 |
menu |
menu |
选项卡 |
tab |
tab |
标题区 |
head/title |
hd/tt |
内容区 |
body/content |
bd/ct |
列表 |
list |
lst |
表格 |
table |
tb |
表单 |
form |
fm |
热点 |
hot |
hot |
排行 |
top |
top |
登录 |
login |
log |
标志 |
logo |
logo |
广告 |
advertise |
ad |
搜索 |
search |
sch |
幻灯 |
slide |
sld |
提示 |
tips |
tips |
帮助 |
help |
help |
新闻 |
news |
news |
下载 |
download |
dld |
注册 |
regist |
reg |
投票 |
vote |
vote |
版权 |
copyright |
cprt |
结果 |
result |
rst |
标题 |
title |
tt |
按钮 |
button |
btn |
输入 |
input |
ipt |
功能选择器
浮动清除 |
clear-both |
cb |
向左浮动 |
float-left |
fl |
向右浮动 |
float-right |
fr |
内联块级 |
inline-block |
ib |
文本居中 |
text-align-center |
tac |
文本居右 |
text-align-right |
tar |
文本居左 |
text-align-left |
tal |
垂直居中 |
vertical-align-middle |
vam |
溢出隐藏 |
overflow-hidden |
oh |
完全消失 |
display-none |
dn |
字体大小 |
font-size |
fs |
字体粗细 |
font-weight |
fw |
颜色/背景选择器
字体颜色 |
font-color |
fc |
背景 |
background |
bg |
背景颜色 |
background-color |
bgc |
背景图片 |
background-image |
bgi |
背景定位 |
background-position |
bgp |
边框颜色 |
border-color |
bdc |
状态选择器
选中 |
selected |
sel |
当前 |
current |
crt |
显示 |
show |
show |
隐藏 |
hide |
hide |
打开 |
open |
open |
关闭 |
close |
close |
出错 |
error |
err |
不可用 |
disabled |
dis |
前端基础架构已经提供了基于 postcss 的后置处理器
autoprefixer(对前置 SASS 编译后的 CSS
进行再处理),因此编写样式时不再手动处理-webkit-
、-moz-
等兼容性前缀。
HTML
HTML 标签的语义化有助于形成构架良好的 DOM
结构,有助于搜索引擎优化和提升可访问性,具体的 HTML 元素语义化原则请参考
HTML5 语义化标签概览一文,尽可能保持 HTML
DOM 结构的优雅。
整体 DOM 结构
统一使用 HTML5
提供的<!DOCTYPE html>
文档类型声明,并在<head>
中使用<link>
引入外部
CSS
文件,然后在<body>
底部通过<script>
引入
JavaScript 文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <!DOCTYPE html> <html lang="zh-CN"> <head> <title>Aves</title> <meta name="renderer" content="webkit" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> </head>
<body> <div id="app"> <main id="dashboard"></main> </div> </body> </html>
|
组件 DOM 结构
每个 Vue 或者 React
根级组件的顶层元素(通常是指全局的路由视图)一律通过<main>
元素定义,因为同一个文档中<main>
标签只可以出现一次,自定义组件一律使用<div>
进行定义,id
属性命名则使用短横线连接的router-component
格式。
1 2 3 4 5
| <main id="router"> <div id="router-component"></div> </main>
|