前端代码质量(2)-代码规范整理

2,458 阅读10分钟

前言

在项目开发中,个人的单打独斗式开发越来越少,团队合作越来越多。而在团队合作中,不同程序员之间的开发习惯是不一样的,这样会增加阅读其他成员代码困难。

为了能在团队协作中,输出可读性强、风格统一的代码,我们需要制定一些团队中的代码方面的规范。

HTML篇

html元素标记是我们再web上创建项目的基础。如果我们对初始的标记做的好,便于我们写成更容易扩展的css和JavaScript代码。

语义化

我们需要根据元素其被创造出来时的初始意义来使用它,而不是全文的divp元素。

// bad
<div id='header' >
	<div id='header-screen'>
    	<div id='header-inner'>

// good
<header>
	<section>
    	<nav>

文档规范

使用HTML5的文档声明类型<!DOCTYPE html>来开启标准模式。

若不添加该声明,浏览器会开启怪异模式,按照浏览器自己的解析方式渲染页面,那么,在不同的浏览器下面可能会有不同的样式。

meta信息

定义字符编码

我们统一使用UTF-8编码,避免乱码问题。

<meta charset="utf-8" />

IE兼容模式(x-ua-compatible)

我们设置x-ua-compatible来强制浏览器的渲染方式,这里我们更多的使用IE的最新版本和 Chrome。可以点击这里了解。

<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />

结构、表现、行为三者分离

尽量在文档和模板中只包含结构性的 HTML;而将所有表现代码,移入样式表中;将所有动作行为,移入脚本之中。

HTML只关注内容

HTML只显示展示内容信息,不要引入一些特定的HTML结构来解决一些视觉设计问题,而是尽量通过css来解决。

// bad
<span class="text-box">
  <span class="square"></span>
  See the square next to me?
</span>
// css
.square {
  display: inline-block;
  width: 1rem;
  height: 1rem;
  background-color: red;
}

// good
<span class="text-box">
  See the square next to me?
</span>
// css
.text-box:before {
  content: "";
  display: inline-block;
  width: 1rem;
  height: 1rem;
  background-color: red;
}

css篇

在设置样式时,我们会遇到一些问题:

  • 选择器优选级。无论是ID还是类名,写选择器时,需要考虑优先级问题
  • 位置依赖。选择器路径和HTML元素位置相关,意味着元素改变,样式也可能改变
  • 多重继承。某类元素的样式来源有多个,某个来源发生变化,可能也会影响这个元素样式变化

我们可以采用一些方法来解决这些问题。

模块化css

模块化css和HTML标记一起使用,让标记更清晰,更“模块化”,便于理解。

当然,这样的方法很多,可能一个项目和一个方法契合,但另一个项目契合其他的方法,所以我们可以按照实际需求自主选择设计方法。

OOCSS(Object-Oriented CSS, 面向对象的CSS)

OOCSS方法有两个原则:

  • 分离结构和外观。将外观定义为可复用的单元或皮肤
  • 分离容器和内容。不将HTML元素作为样式的限制
<div class="toggle simple">
  <div class="toggle-control active">
    <h2 class="toggle-title">Title 1</h2>

这里,simple使用直角,切换simple,可视为切换皮肤;toggle_title用于设置文本样式,不用在意文本的元素是div还是h2

SMACSS(Scalable and Modular Architecture for CSS)

SMACSS,即模块化架构的可扩展CSS方法。会将样式系统分为5个不同的类别:

  • 基础。即不添加CSS类别,HTML元素会展示什么样式
  • 布局。把页面分为一些区域
  • 模块。设计中的模块化、可复用的单元
  • 状态。在特定状态或情况下,模块或布局的展示样式
  • 主题。一个可选的视觉外观层,可以切换不同的主题
<div class="toggle toggle-simple">
  <div class="toggle-control is-active">
    <h2 class="toggle-title">Title 1</h2>

SMACSS和OOCSS有很多的相似之处,除了SMACSS把代码划分类别外,最大的不同在于SMACSS使用子模块和is前缀的状态,而OOCSS使用皮肤。

BEM(Block Element Modifier, 块元素修饰符)

BEM方法,建议每个元素都添加有以下内容的css类名:

  • 块名。所属组件的名称
  • 元素。元素在块中的名称
  • 修饰符。任何与块和元素相关的修饰符
<div class="toggle toggle--simple">
  <div class="toggle_control toggle_control--active">
    <h2 class="toggle_title">Title 1</h2>

BEM的好处是我们可以清晰的理解这个类名的含义,当然,这会让类名看起来显得累赘、冗余。

单一职责原则

你创建的所有东西必须是单一的,高度聚焦的,为某个选择器的样式应该是为单一的目的创建的。

<div class="calendar">
  <div class="primary-header">This is calendar.</div>
</div>

<div class="blog">
  <div class="primary-header">This is blog.</div>
</div>

.primary-header {
	color: red;
	font-size: 2em;
}

在上面的例子中,.primary-header被用于不止一个且不相关的元素上,这不满足我们单一职责原则。我们可以进行以下修改:

<div class="calendar">
  <div class="primary-header">This is calendar.</div>
</div>

<div class="blog">
  <div class="primary-header">This is blog.</div>
</div>

.primary-header {
	color: red;
	font-size: 2em;
}

blog .primary-header {
	color: red;
	font-size: 2em;
}

但是,.primary-header会出现多重继承的问题,针对这个问题,我们可以给每个css类名添加单一的任务。

<div class="calendar">
  <div class="calendar-header">This is calendar.</div>
</div>

<div class="blog">
  <div class="blog-header">This is blog.</div>
</div>

.calendar-header {
	color: red;
	font-size: 2em;
}

.blog-header {
	color: red;
	font-size: 2em;
}

Javascript 篇

命名

命名一般分为两种:

  • 小驼峰命名:首字母小写,比如userName
  • 大驼峰命名:首字母大写,比如UserName

变量命名

命名方式: 小驼峰式命名方法

命名规范: 类型+对象描述的方式。如果没有明确的类型,就可以使用名词+对象描述。

推荐写法:

类型写法
arraya/arr
booleanb/is
strings/str
number(整数)n
number(有浮点数)f
objecto/obj
functionfn
Dated/date
RegExpr/re

并且,我们在变量命名时,应该要使变量名具有代表意图的象征,使人易于搜索并且容易理解。

const sUserName = 'tom'
const isRead = true

// 使用名词作前缀
const userTable = 'talbe'

// 若描述对象是复数时,需要添加s
const userTables = ['talbe1', 'table2']

常量命名

命名规范: 全部大写+下划线

const USER_ADDR = '北京'

函数命名

命名方式: 小驼峰式命名方法

命名规范: 动词+方法描述的方式。

推荐动词:

动词描述返回值
is是否为某个值布尔值
can是否能执行某个操作布尔值
has是否有某个值布尔值
set设置某个值无返回值、返回是否设置成功或者返回链式对象
get获取某个值非布尔值
function setName(name) {
	this.name = name;
}

function getName() {
	return this.name;
}

若是构造函数,则使用大驼峰式命名方法。

function Student(name, age) {
	this.name = name;
    this.age = age;
}

const oStudent = new Student('Tom', 18);

类及成员命名

在ES6中,我们可以通过class来创建类,并设置私有/公有属性。

公有属性:同变量命名方法/函数命名方法。

私有属性:前缀(_)+公有属性命名方法。

class Student {
	static getId(){
        console.log('getId')
    }
	
	private _name = 'Tom';
    
    public getName() {
    	return this._name;
    }
}

变量声明

谨慎使用全局变量

全局变量是魔鬼。

如果你不小心,在代码的某一处修改了全局变量,可能会导致依赖全局变量的其它模块出错,并且出错原因难以找到。

那么,我们应该怎么解决呢?

  1. 尽可能创建一个全局变量,让其他对象和函数存在其中。
// bad
const like = 'football';

function likeDo(){
    alert(like);
}

// good
const myInfo={
    like:'football',
    likeDo:function(){
        alert(this.like);
    }
}
  1. 使用模块模式

在ES6中,我们通过import/export产生模块,可以将需要的信息放置在一个模块中,几乎可以完全屏蔽掉全局变量的使用。

// myInfo.js

export const like = 'football';

export function likeDo() {
	alert(this.like);
}

// test.js
import { likeDo, like } from 'myInfo';
  1. 闭包

当然,有时我们也可以通过闭包来减少全局变量信息。不过,我们在使用闭包时,也需要注意内存泄漏的问题。

const myInfo = (function () {
	const like = 'football';
    return {
    	like,
        likeDo: () => {
			alert(like);
		}
    }
})();

使用let/const来声明变量

以前我们使用var来声明变量,但是ES6版本以后,应该尽量使用let/const来声明变量。

我们在使用var时,会存在变量提升的情况,这可能会导致一些误解;并且,当var作用于全局作用域时,会创建一个新的全局变量作为全局对象的属性,这可能会无意中覆盖一个全局变量。

// 浏览器中
var RegExp = 'hello';
console.log(window.RegExp) // hello

const RegExp = 'hello';
console.log(window.RegExp) // ƒ RegExp() { [native code] }

使用严格等

当我们使用==来判断两个变量相等时,若两个变量不是同一类型,会触发JavaScript的强制类型转换,出现和我们预期不相符的结果(详细情况可以看这里)。而我们使用严格等===则不会出现这种情况。

// bad
0 == '0' // true
0 == '' // true

// good
0 === '0' // false
0 === '' // false

不使用eval()函数

eval函数可计算某个字符串,并执行其中的JavaScript代码,这会带来安全隐患。

代码风格及检查

上面可以算是一些强制性高的内容,但是我们仍旧会面临一些灵魂拷问:

  1. 缩进用空格还是制表符?空格的话,用两个空格还是四个空格?
  2. 句末是否需要添加分号?
  3. 每行代码最多写100行还是80行?
  4. ...

这都是一些可以让程序员争论不休的问题,每一个问题都可以列出无数个正反理由。在这里我不想去说哪种方法绝对正确,毕竟再烂的代码,只要合法,JavaScript引擎都能正确执行。

但是,为了我们的代码能够便于阅读,我们需要统一风格,并使用各种工具进行检查。

JSLint

早期,我们使用JSLint来检查我们的JavaScript代码质量。它会读取源文件并进行扫描,如果有问题会返回一个消息进行描述并指明该问题所在源文件中的大概位置。

但是,JSLint不支持规则自定义,必须遵守作者提供的规则。

JSHint

JSHint基于JSLint开发,能灵活的自定义规则。但是,它是基于JSLint开发的,自然原有的一些问题它也继承下来了,JSHint对ES6的支持度不够高,且不支持JSX。

ESLint

ESLintNicholas C. Zakas在2013年开发的JavaScript代码静态分析工具。它的初衷就是能让开发者能自定义自己的linting rules

它提供了一套相当完善的插件机制,可以自由的扩展,动态加载配置规则。它既具有JSHint的高度可配置性,同时也通过插件机制完整支持了ES6语法以及JSX,任意阶段的ECMAScript特性。

这也是我们现阶段正在使用的工具。

TSLint

TSLint用于对TypeScript做代码静态分析,后面我们统一使用ESLint,并在ESLint中加入TSLint检测插件。

Prettier

Prettier是一个代码格式化工具,我们可以通过自定义的规则,使输出代码保持风格一致,比如针对最大行数自动换行,句末添加自动分号等。

我们可以将PrettierESLint一起使用,可以点击这里看看操作。

总结

我们简单总结了在项目中,针对HTML、CSS、JavaScript的不同原则、规范及检查工具。

当然,这不是一份某种意义上的决定正确的方案,每个团队每个项目都可以设计出自己独有的规范。只要能提高我们的项目的代码质量,便于团队之间的沟通协作,一切方案都是可行的。

参考