Web 前端开发遵循的一些规范与约定

开发团队当前正在全面切换到ES6 + Vue/React + Webpack这套技术栈,小伙伴们在开发过程中对编码规范以及 ES6 使用约定方面存在诸多困惑,所以参考了 Github 以及互联网企业 UED 的相关技术文档,结合团队之前积累的大量开发约定和范式,整理了这篇 code style 并且开源出来,计划在 Team 小伙伴们熟练掌握以后,逐步在项目中推广使用 ESLint 校验代码风格。

本文撰写过程中参考了爱彼迎的 《Airbnb JavaScript Style Guide》,以及网易 NEC 团队的《CSS 基础库命名约定》等开源规范,结合笔者之前撰写的 《HTML5 语义化标签概览》一文,总体上分为总体原则JavaScript/ES6CSS/SCSSHTML四个部分,分门别类的从命名约定、选择器使用、DOM 结构组织等纬度进行阐述,以帮助小组成员形成良好的编码习惯。

总体原则

  1. 驼峰命名camelCase):用于定义 JavaScript基本数据类型函数方法
  2. 帕斯卡命名PascalCase):用于 JavaScript 声明Class类Object对象Array数组引用数据类型。
  3. 短横线命名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)"; // camelCase

const changeDate = function () {
console.log(new Date()); // camelCase
};

const YourName = {
name: "Hank", // PascalCase
};
</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; // 使用1个空格分隔运算符
})(window);
// 保留1个空行

使用美元符$作为存储jQuery 对象变量名称的前缀。

1
2
3
const menu = $("#menu"); // bad way

const $menu = $("#menu"); // good way

使用下划线_作为代码中私有变量的前缀,避免其它小伙伴的操作对该变量造成污染。

1
2
3
4
5
6
function traverse(array) {
let _index = 0; // 将数组索引声明为私有变量,防止误操作导致索引泄露
for (_index; array.length < _index; _index++) {
console.log(array[_index]);
}
}

严禁在项目中使用单个字母和拼音命名的变量和函数名称

数据类型

  • 基本数据类型: String、Number、Boolean、null、undefined,使用let进行定义,不再使用 var。

  • 引用数据类型:Object、Array、Function,引用数据类型全部通过const进行定义。

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(); // not recommend

const myObject = {}; // it is correct

对象当中的方法使用简写函数简写属性进行定义。

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指向实例对象
this.y = y;
}

// 定义类方法不需要使用function关键字进行声明
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(); // not recommend

const myArray = []; // it is correct
  • 添加数组元素时使用Arrary.push(),而不要使用索引赋值。
1
2
3
4
5
const myArray = [];

myArray[myArray.length] = "uinika"; // not recommend

myArray.push("uinika"); // it is correct
  • 使用 ES6 的扩展运算符(spread...复制数组。
1
2
3
4
5
6
7
8
9
10
const target = ["A", "B", "C", "D"];
const copy = [];

// not recommend
for (let index = 0; index < target.length; index++) {
copy[i] = target[i];
}

// it's correct
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); // ['A', 'B', 'C']

字符串

为避免频繁按下shift+'组合键,请在代码中尽可能使用单引号'声明字符串。

1
2
const name = "Hank"; // bad way
const name = "Hank"; // good way

字符串过长时,必须通过+或者\运算符进行换行缩进处理,让代码更加美观易读。

1
2
3
4
5
6
7
8
9
10
// bad way
let bad = "hank is first name and zheng is last name";

// good way
let good =
"hank is first name \
and zheng is last name";

// best way
let best = "hank is first name" + "and zheng is last name";

拼接字符串HTML 模板时,请使用模板字符串${},而非字符串连接符+

1
2
3
4
5
6
7
8
9
// bad
function sayHi(name) {
return "How are you, " + name + "?";
}

// good
function sayHi(name) {
return `How are you, ${name}?`;
}

函数

使用函数声明(function declaration)代替函数表达式(function expression),因为前者可以进行函数提升(function hoisting)。

1
2
3
4
5
// bad
const bad = function () {};

// good
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()等逻辑判断语句时需要注意。

  1. 对象类型的objectarray都计算为true,无论其是否为空对象{}或者空数组[]
  2. 字符串类型''计算为false,但非空字符串'string'计算为true
  3. 数字类型NaN0计算为false
  4. undefinednull都是false
1
2
3
if ([0]) {
// 结果为true,因为JavaScript中object和array都同属对象类型,布尔运算时对象类型都会被渲染为true。
}

利用 JavaScript 逻辑判断语句的强制数据类型转换特性,可以更加简洁的书写布尔判断。

1
2
3
4
5
6
7
8
9
if (name !== "") {
} // bad
if (name) {
} // good

if (collection.length > 0) {
} // bad
if (collection.length) {
} // good

如果通过if...else...语句使用多行代码块,则else放在if代码块关闭括号}同一行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// bad job
if (test) {
...
}
else {
...
}

// good job
if (test) {
...
} else {
...
}

注释

  • 使用/** */作为多行注释,用来标注全局功能模块的名称类型参数返回值描述等信息。
1
2
3
4
5
6
7
/**
* @name auto-resize
* @type directive
* @param 数值类型,表示缩进的像素值
* @return null
* @description 自动根据当前window大小计算页面的显示尺寸
*/
  • 使用/* */作为单行注释,标注局部代码块的参数返回值描述信息
1
2
3
4
5
6
7
8
/*
* @param 用户输入字符串
* @return null
* @description 去除目标字符串全部空格
*/
function trim(input) {
if (typeof input === "string" && input) input.replace(/\s/g, "");
}
  • 使用//作为行内注释,标记代码段信息。
1
2
3
function trustHtml($sce) {
return $sce.trustAsHtml(val); //返回被信任的HTML字符串
}

使用// TODO:格式注释描述问题本身,使用// FIXME:格式注释描述问题解决方式

1
2
3
4
5
() => {
issue(); // TODO: 描述问题本身的信息
handle(); // FIXME: 描述如何解决问题
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; // bad way
const password = user.password;
return `${name} ${name}`;
}
function getFullName2(user) {
const { name, password } = user; // good way
return `${name} ${password}`;
}
function getFullName3({ name, password }) {
return `${name} ${password}`; // best way
}

/* 对象的解构赋值 */
const array = [1, 2, 3, 4];
const array1 = array[0]; // bad way
const array2 = array[1]; // bad way
const [array1, array2] = array; // good way

函数返回值时,直接返回对象{}而非数组[],避免因为数组索引改变导致不能正确读取相应位置数据。

CSS & SCSS

本规范借鉴网易 NECCSS 基础库命名约定,结合当前开发团队多种 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
// infomation
$color-primary: $color-blue;
$color-success: #13ce66;
$color-warning: #f7ba2a;
$color-danger: #ff4949;
// scheme
$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>

Web 前端开发遵循的一些规范与约定

http://www.uinio.com/Web/Convention/

作者

Hank

发布于

2015-02-27

更新于

2016-01-13

许可协议